Playwright 定位器(Locators)
Locator(定位器)是 Playwright 中最核心的概念之一,它代表了一种在页面上查找元素的方式。
本章介绍 Playwright 推荐的定位方法:通过角色、文本、标签和占位符来定位元素。
什么是 Locator
Locator 是 Playwright 用来在页面上查找元素的抽象对象。
与传统的 document.querySelector() 不同,Playwright 的 Locator 具有自动等待和自动重试能力。
当页面内容动态变化时,Locator 会不断重新查找元素,直到找到匹配的元素或超时。
实例
const button = page.getByRole('button', { name: '提交' });
// 执行操作时,Playwright 会自动等待按钮出现并变为可操作状态
await button.click();
Locator 的工作原理
创建 Locator 时,Playwright 只记录了一个查找规则。
当你调用 .click()、.fill() 等操作或 expect(locator) 断言时,Locator 才真正开始查找元素。
如果找不到元素,Locator 会持续重试,直到操作超时为止。
按角色定位:getByRole()
getByRole() 是 Playwright 最推荐的定位方式。
它通过元素的可访问性角色(ARIA Role)来查找元素,这是最接近用户感知的方式。
基本用法
实例
const submitBtn = page.getByRole('button', { name: '提交' });
await submitBtn.click();
// 定位一个链接——通过角色 "link"
const homeLink = page.getByRole('link', { name: '首页' });
await homeLink.click();
// 定位一个标题——通过角色 "heading"
const mainTitle = page.getByRole('heading', { name: '欢迎使用 RUNOOB' });
// 定位一个文本框——通过角色 "textbox"
const searchBox = page.getByRole('textbox', { name: '搜索' });
await searchBox.fill('Playwright');
常见 ARIA Role
| Role | 对应 HTML 元素 | 使用示例 |
|---|---|---|
'button' | <button>、role="button" | getByRole('button', { name: '登录' }) |
'link' | <a> | getByRole('link', { name: '了解更多' }) |
'heading' | <h1>~<h6> | getByRole('heading', { name: 'RUNOOB 教程' }) |
'textbox' | <input type="text">、<textarea> | getByRole('textbox', { name: '用户名' }) |
'checkbox' | <input type="checkbox"> | getByRole('checkbox', { name: '记住我' }) |
'radio' | <input type="radio"> | getByRole('radio', { name: '男' }) |
'combobox' | <select> | getByRole('combobox', { name: '城市' }) |
'img' | <img> | getByRole('img', { name: 'Logo' }) |
'list' | <ul>、<ol> | getByRole('list') |
'listitem' | <li> | getByRole('listitem') |
'navigation' | <nav> | getByRole('navigation') |
'table' | <table> | getByRole('table') |
name 选项
name 选项是通过元素的可访问名称来缩小范围。
可访问名称的来源包括:元素文本内容、aria-label 属性、关联的 <label> 等。
实例
page.getByRole('button', { name: '登录' });
// 通过 aria-label 匹配
page.getByRole('button', { name: '关闭对话框' });
// 正则表达式匹配
page.getByRole('link', { name: /RUNOOB|菜鸟/ });
状态选项
除了 name,getByRole 还支持多种状态选项:
实例
page.getByRole('checkbox', { checked: true });
// 定位已禁用的按钮
page.getByRole('button', { disabled: true });
// 定位展开的下拉菜单
page.getByRole('button', { expanded: true });
// 定位选中的选项
page.getByRole('option', { selected: true });
// 组合多个状态
page.getByRole('checkbox', { name: '同意协议', checked: false });
按文本定位:getByText()
getByText() 通过元素的文本内容来定位。
实例
page.getByText('欢迎访问 RUNOOB');
// 部分匹配(默认)
page.getByText('RUNOOB');
// 强制精确匹配
page.getByText('RUNOOB', { exact: true });
// 正则表达式匹配
page.getByText(/RUNOOB|菜鸟教程/);
getByText 匹配的是元素的完整文本内容,所以 getByText('RUNOOB', { exact: true }) 只会匹配文本内容刚好为 "RUNOOB" 的元素。
默认不做精确匹配时,子字符串匹配即可(如 getByText('RUNOOB') 会匹配 "欢迎访问 RUNOOB")。
按标签定位:getByLabel()
getByLabel() 用于定位与 <label> 关联的表单控件。
实例
// <label for="username">用户名</label>
// <input id="username" type="text" />
// 通过 label 文本定位 input
await page.getByLabel('用户名').fill('runoob_user');
// HTML 结构:
// <label>密码 <input type="password" /></label>
// 嵌套的 label 同样有效
await page.getByLabel('密码').fill('password123');
它也支持 { exact: true } 精确匹配选项。
按占位符定位:getByPlaceholder()
getByPlaceholder() 通过 placeholder 属性定位输入框。
实例
await page.getByPlaceholder('请输入搜索关键词').fill('Playwright');
// HTML 结构:<textarea placeholder="说说你的想法..."></textarea>
await page.getByPlaceholder('说说你的想法...').fill('RUNOOB 教程很棒!');
getByLabel vs getByPlaceholder 的选择
| 方法 | 适用场景 | 优先级 |
|---|---|---|
getByLabel() | 有 <label> 元素时 | 优先使用 |
getByPlaceholder() | 没有 label,但有 placeholder 时 | 次选 |
getByRole 是 Playwright 最推荐的定位方式,因为它模拟用户通过角色识别元素的方式。其次是 getByLabel 和 getByPlaceholder。在没有语义化元素的情况下才考虑使用 getByTestId 或 CSS 选择器。
getByAltText() 图片定位
getByAltText() 通过图片的 alt 属性来定位 <img> 元素。
实例
await expect(page.getByAltText('RUNOOB Logo')).toBeVisible();
// 正则匹配
await expect(page.getByAltText(/logo/i)).toBeVisible();
getByTitle() 标题属性定位
getByTitle() 通过元素的 title 属性来定位。
实例
await page.getByTitle('关闭当前页面').click();
getByAltText和getByTitle使用频率较低,仅在元素具有明确的 alt 或 title 属性且无法用其他方式定位时才使用。
getByTestId() 测试 ID 定位
getByTestId() 是兜底方案,通过 data-testid 属性来定位元素。
实例
await page.getByTestId('submit-btn').click();
// 可以包含多个匹配(如列表项)
const items = page.getByTestId('todo-item');
await expect(items).toHaveCount(3);
自定义 testId 属性
如果你的项目不使用 data-testid,可以在 playwright.config.ts 中自定义属性名:
实例
export default defineConfig({
use: {
// 使用 data-cy 而不是 data-testid(配合 Cypress 迁移)
testIdAttribute: 'data-cy',
// 或者使用 id 作为 testId
// testIdAttribute: 'id',
},
});
page.locator() CSS 与 XPath 定位
page.locator() 支持 CSS 选择器和 XPath,是更灵活的备用方案。
CSS 选择器
实例
await page.locator('.submit-btn').click();
// 通过 ID
await page.locator('#username').fill('runoob_user');
// 通过属性
await page.locator('[data-type="primary"]').click();
// 通过标签 + 类名组合
await page.locator('button.primary').click();
// 通过父子关系
await page.locator('nav a').first().click();
// 包含特定文本的 CSS 选择器
await page.locator('button:has-text("提交")').click();
XPath 定位
实例
await page.locator('xpath=//button[@type="submit"]').click();
// 通过文本定位
await page.locator('xpath=//h1[contains(text(), "RUNOOB")]').click();
CSS 选择器和 XPath 是最后的选择,优先使用
getByRole、getByText等语义化定位方法。CSS 选择器容易因样式重构而失效,XPath 可读性差且性能较慢。
Locator 链式操作
Playwright 的 Locator 支持链式调用,可以在当前匹配结果上进一步筛选。
.first()、.last()、.nth()
实例
const firstItem = page.getByRole('listitem').first();
// 取最后一个
const lastItem = page.getByRole('listitem').last();
// 取第 2 个(索引从 0 开始)
const secondItem = page.getByRole('listitem').nth(1);
.filter() 过滤
实例
page.getByRole('button').filter({ hasText: '保存' });
// 筛选不包含特定文本的
page.getByRole('listitem').filter({ hasNotText: '已删除' });
// 筛选包含特定子元素的
page.getByRole('listitem').filter({ has: page.getByRole('button') });
// 筛选不包含特定子元素的
page.getByRole('listitem').filter({ hasNot: page.getByTestId('badge') });
.locator() 子元素定位
实例
const list = page.getByTestId('todo-list');
const deleteBtn = list.locator('button.delete');
await deleteBtn.click();
// 多级嵌套
page
.getByRole('dialog')
.locator('div.content')
.locator('button')
.filter({ hasText: '确认' })
.click();
.and() 和 .or() 逻辑组合(1.63+)
实例
const btn = page.getByRole('button')
.and(page.getByText('提交'));
// 满足任意一个条件
const target = page.getByText('RUNOOB')
.or(page.getByTitle('RUNOOB'));
Locator 定位方式优先级总结
按照推荐程度从高到低排列:
| 优先级 | 方法 | 使用条件 |
|---|---|---|
| 1(最推荐) | getByRole() | 元素具有明确的 ARIA 角色 |
| 2 | getByLabel() | 表单元素有关联的 label |
| 3 | getByPlaceholder() | 输入框有 placeholder |
| 4 | getByText() | 元素有明确的文本内容 |
| 5 | getByAltText() | 图片有 alt 属性 |
| 6 | getByTitle() | 元素有 title 属性 |
| 7(兜底) | getByTestId() | 无法用以上方式定位时 |
| 8(最后选择) | locator() | CSS 选择器 / XPath |
