用 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:button、link、textbox、checkbox、listbox、dialog、heading
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-v2 | 高 | class 名经常随版本变 |
#submit > div > button | 高 | DOM 结构一变就失效 |
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。
实际推荐策略
- 优先用
getByRole+getByText,覆盖 80% 常规场景 - 次选
getByPlaceholder/getByLabel(表单场景) - 开发阶段加
data-testid,测试最稳定 - Shadow DOM / Canvas 等场景才用 AI Vision
不要一开始就上 AI Vision,定位器够用的情况下更快、更稳、Token 消耗为零。
