现在位置: 首页 > Playwright 教程 > 正文

Playwright 定位器(Locators)

Locator(定位器)是 Playwright 中最核心的概念之一,它代表了一种在页面上查找元素的方式。

本章介绍 Playwright 推荐的定位方法:通过角色、文本、标签和占位符来定位元素。


什么是 Locator

Locator 是 Playwright 用来在页面上查找元素的抽象对象。

与传统的 document.querySelector() 不同,Playwright 的 Locator 具有自动等待和自动重试能力。

当页面内容动态变化时,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)来查找元素,这是最接近用户感知的方式。

基本用法

实例

// 定位一个按钮——通过角色 "button"
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|菜鸟/ });

状态选项

除了 namegetByRole 还支持多种状态选项:

实例

// 定位已选中的复选框
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> 关联的表单控件。

实例

// HTML 结构:
// <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 属性定位输入框。

实例

// HTML 结构:<input 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 最推荐的定位方式,因为它模拟用户通过角色识别元素的方式。其次是 getByLabelgetByPlaceholder。在没有语义化元素的情况下才考虑使用 getByTestId 或 CSS 选择器。


getByAltText() 图片定位

getByAltText() 通过图片的 alt 属性来定位 <img> 元素。

实例

// HTML:<img src="logo.png" alt="RUNOOB Logo" />
await expect(page.getByAltText('RUNOOB Logo')).toBeVisible();

// 正则匹配
await expect(page.getByAltText(/logo/i)).toBeVisible();

getByTitle() 标题属性定位

getByTitle() 通过元素的 title 属性来定位。

实例

// HTML:<button title="关闭当前页面">X</button>
await page.getByTitle('关闭当前页面').click();

getByAltTextgetByTitle 使用频率较低,仅在元素具有明确的 alt 或 title 属性且无法用其他方式定位时才使用。


getByTestId() 测试 ID 定位

getByTestId()兜底方案,通过 data-testid 属性来定位元素。

实例

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

// 可以包含多个匹配(如列表项)
const items = page.getByTestId('todo-item');
await expect(items).toHaveCount(3);

自定义 testId 属性

如果你的项目不使用 data-testid,可以在 playwright.config.ts 中自定义属性名:

实例

// 文件路径: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 选择器

实例

// 通过 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 定位

实例

// 使用 XPath 定位
await page.locator('xpath=//button[@type="submit"]').click();

// 通过文本定位
await page.locator('xpath=//h1[contains(text(), "RUNOOB")]').click();

CSS 选择器和 XPath 是最后的选择,优先使用 getByRolegetByText 等语义化定位方法。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 角色
2getByLabel()表单元素有关联的 label
3getByPlaceholder()输入框有 placeholder
4getByText()元素有明确的文本内容
5getByAltText()图片有 alt 属性
6getByTitle()元素有 title 属性
7(兜底)getByTestId()无法用以上方式定位时
8(最后选择)locator()CSS 选择器 / XPath