Playwright 定位网页元素:getByText / getByRole 比 CSS Selector 稳在哪

用 Playwright 自动化网页时,很多人习惯用 CSS selector(page.locator(".btn-submit")),但这类 selector 抗变化能力差——前端一改 class 名就失效。Playwright 提供了一套语义化定位器,稳定性高很多。

语义化定位器

getByText

按可见文本定位,最常用:

await page.getByText('登录').click();
await page.getByText('提交订单').click();

支持正则:

await page.getByText(/确认.*订单/).click();

getByRole

按 ARIA role 定位,最语义化:

await page.getByRole('button', { name: '提交' }).click();
await page.getByRole('link', { name: '首页' }).click();
await page.getByRole('textbox', { name: '搜索' }).fill('hello');

常用 role:buttonlinktextboxcheckboxlistboxdialogheading

getByPlaceholder

按 input placeholder 定位:

await page.getByPlaceholder('请输入手机号').fill('138xxxx');
await page.getByPlaceholder('密码').fill('password123');

getByLabel

<label> 文本定位关联的 input:

await page.getByLabel('邮箱').fill('test@example.com');
await page.getByLabel('记住我').check();

getByTestId

data-testid 属性,测试环境最稳定的方案:

// HTML: <button data-testid="submit-btn">提交</button>
await page.getByTestId('submit-btn').click();

为什么比 CSS Selector 稳

定位方式变化敏感度说明
.btn-primary-v2class 名经常随版本变
#submit > div > buttonDOM 结构一变就失效
getByText('提交')文本变了才失效
getByRole('button', {name: '提交'})语义稳定
getByTestId('submit')最低开发者明确标记

处理复杂场景

多个相同文本时,用 nth 或组合定位:

// 第二个"删除"按钮
await page.getByText('删除').nth(1).click();

// 在某个容器内查找
await page.locator('.order-list').getByRole('button', { name: '删除' }).first().click();

等待元素可交互:

await page.getByRole('button', { name: '提交' }).waitFor({ state: 'visible' });
await page.getByRole('button', { name: '提交' }).click();

真正难处理的场景

语义化定位器解决不了的几种情况,才需要 AI Vision Agent:

  • Canvas 元素(无 DOM 结构)
  • Shadow DOM 内的元素
  • 动态随机 class 名(某些 React 框架)
  • 无文字的图标按钮
  • iframe 跨域内容

对于这些场景,可以用 Stagehand 等 AI 驱动的自动化工具:

await page.act("点击右上角的关闭按钮");

让大模型看截图决定点击位置,不依赖 selector。

实际推荐策略

  1. 优先用 getByRole + getByText,覆盖 80% 常规场景
  2. 次选 getByPlaceholder / getByLabel(表单场景)
  3. 开发阶段加 data-testid,测试最稳定
  4. Shadow DOM / Canvas 等场景才用 AI Vision

不要一开始就上 AI Vision,定位器够用的情况下更快、更稳、Token 消耗为零。