其他定位器
简介
查看主要的 定位器指南 了解最常见和推荐的定位器。
除了推荐的定位器如 page.getByRole() 和 page.getByText() 之外,Playwright 还支持本指南中描述的各种其他定位器。
CSS 定位器
我们建议优先使用 用户可见的定位器(如文本或可访问角色),而不是使用与实现绑定的 CSS 选择器,因为后者可能在页面变更时失效。
Playwright 可以通过 CSS 选择器定位元素。
await page.locator('css=button').click();
Playwright 在两个方面增强了标准 CSS 选择器:
- CSS 选择器可以穿透 shadow DOM
- Playwright 添加了自定义伪类如
:visible
、:has-text()
、:has()
、:is()
、:nth-match()
等
CSS: 通过文本匹配
Playwright 提供了一系列 CSS 伪类来根据元素的文本内容进行匹配:
-
article:has-text("Playwright")
-:has-text()
匹配包含指定文本的任何元素,文本可能位于子元素或后代元素中。匹配不区分大小写,会去除空白并搜索子字符串。例如,
article:has-text("Playwright")
会匹配<article><div>Playwright</div></article>
。注意
:has-text()
应该与其他 CSS 选择器一起使用,否则会匹配所有包含指定文本的元素,包括<body>
。// 错误用法,会匹配包括<body>在内的多个元素
await page.locator(':has-text("Playwright")').click();
// 正确用法,仅匹配<article>元素
await page.locator('article:has-text("Playwright")').click(); -
#nav-bar :text("Home")
-:text()
伪类匹配包含指定文本的最小元素。匹配不区分大小写,会去除空白并搜索子字符串。例如,以下代码会在
#nav-bar
元素内查找包含 "Home" 文本的最小元素:await page.locator('#nav-bar :text("Home")').click();
-
#nav-bar :text-is("Home")
-:text-is()
伪类匹配文本完全匹配的最小元素。完全匹配区分大小写,会去除空白并搜索完整字符串。例如,
:text-is("Log")
不会匹配<button>Log in</button>
,因为<button>
包含的单个文本节点"Log in"
不等于"Log"
。但是,:text-is("Log")
会匹配<button> Log <span>in</span></button>
,因为<button>
包含一个文本节点" Log "
。同理,
:text-is("Download")
不会匹配<button>download</button>
,因为它是区分大小写的。 -
#nav-bar :text-matches("reg?ex", "i")
-:text-matches()
伪类匹配文本内容符合 类 JavaScript 正则表达式 的最小元素。例如,
:text-matches("Log\s*in", "i")
会匹配<button>Login</button>
和<button>log IN</button>
。
:::注意
文本匹配总是会规范化空白字符。例如,它会将多个空格转换为一个,将换行符转换为空格,并忽略开头和结尾的空白。 :::
:::注意
类型为 button
和 submit
的输入元素是通过它们的 value
值而不是文本内容来匹配的。例如,:text("Log in")
会匹配 <input type=button value="Log in">
。
:::
CSS: 仅匹配可见元素
Playwright 在 CSS 选择器中支持 :visible
伪类。例如,css=button
会匹配页面上的所有按钮,而 css=button:visible
仅匹配可见的按钮。这对于区分外观相似但可见性不同的元素非常有用。
考虑一个包含两个按钮的页面,第一个不可见,第二个可见:
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
这会找到两个按钮并抛出 严格模式 违规错误:
await page.locator('button').click();
-
这只会找到第二个按钮(因为它是可见的)并点击它:
await page.locator('button:visible').click();
CSS: 包含其他元素的元素
:has()
伪类是一个 实验性 CSS 伪类。如果作为参数传递的任何选择器相对于给定元素的 :scope
匹配至少一个元素,则返回该元素。
以下代码片段返回包含 <div class=promo>
的 <article>
元素的文本内容:
await page.locator('article:has(div.promo)').textContent();
CSS: 匹配多个条件之一的元素
逗号分隔的 CSS 选择器列表将匹配能被列表中任一选择器选中的所有元素。
// 点击包含文本 "Log in" 或 "Sign in" 的 <button> 元素
await page.locator('button:has-text("Log in"), button:has-text("Sign in")').click();
:is()
伪类是一个 实验性 CSS 伪类,可用于在元素上指定额外的条件列表。
CSS: 基于布局匹配元素
基于布局的匹配可能会产生意外结果。例如,当布局变化一个像素时,可能会匹配到不同的元素。
有时,当目标元素缺乏显著特征时,很难为其编写合适的选择器。这种情况下,使用 Playwright 的布局 CSS 伪类会有所帮助。这些伪类可以与常规 CSS 结合使用,从多个选项中精确定位一个元素。
例如,input:right-of(:text("Password"))
会匹配位于"Password"文本右侧的输入框 - 这在页面有多个难以区分的输入框时非常有用。
请注意,布局伪类通常需要与其他选择器结合使用,比如 input
。如果单独使用布局伪类,如 :right-of(:text("Password"))
,很可能匹配到的不是你想要的输入框,而是文本和目标输入框之间的某个空元素。
布局伪类使用 边界客户端矩形 来计算元素的距离和相对位置:
:right-of(div > button)
- 匹配位于内部选择器匹配元素右侧的元素,垂直位置不限:left-of(div > button)
- 匹配位于内部选择器匹配元素左侧的元素,垂直位置不限:above(div > button)
- 匹配位于内部选择器匹配元素上方的元素,水平位置不限:below(div > button)
- 匹配位于内部选择器匹配元素下方的元素,水平位置不限:near(div > button)
- 匹配靠近(50 CSS 像素范围内)内部选择器匹配元素的元素
注意,匹配结果会按照与锚元素的距离排序,因此可以使用 locator.first() 选择最近的一个。这仅在类似元素列表的场景下有用,其中最近的一个明显是正确的选择。但在其他情况下使用 locator.first() 很可能不会按预期工作 - 它不会定位到你搜索的元素,而是碰巧最近的某个其他元素,比如随机的空 <div>
,或是已滚动出当前视图区域的元素。
// 填充"Username"右侧的输入框
await page.locator('input:right-of(:text("Username"))').fill('value');
// 点击促销卡附近的按钮
await page.locator('button:near(.promo-card)').click();
// 点击列表中离"Label 3"最近的单选按钮
await page.locator('[type=radio]:left-of(:text("Label 3"))').first().click();
所有布局伪类都支持可选的像素距离作为最后一个参数。例如 button:near(:text("Username"), 120)
会匹配距离"Username"文本元素最多 120 CSS 像素的按钮。
CSS:从查询结果中选择第N个匹配项
通常可以通过某些属性或文本内容来区分元素,这种方式对页面变更更具适应性。
有时页面包含多个相似元素,很难选中特定的某一个。例如:
<section> <button>Buy</button> </section>
<article><div> <button>Buy</button> </div></article>
<div><div> <button>Buy</button> </div></div>
在这种情况下,:nth-match(:text("Buy"), 3)
会选择上面代码片段中的第三个按钮。注意索引是从1开始的。
// 点击第三个"Buy"按钮
await page.locator(':nth-match(:text("Buy"), 3)').click();
:nth-match()
也可以配合 locator.waitFor() 使用,等待指定数量的元素出现。
// 等待直到三个按钮都可见
await page.locator(':nth-match(:text("Buy"), 3)').waitFor();
与 :nth-child()
不同,元素不必是同级元素,它们可以位于页面的任何位置。在上面的代码片段中,所有三个按钮都匹配 :text("Buy")
选择器,而 :nth-match()
选择了第三个按钮。
第N个元素定位器
你可以使用 nth=
定位器并传入从零开始的索引来缩小查询范围至第N个匹配项。
// 点击第一个按钮
await page.locator('button').locator('nth=0').click();
// 点击最后一个按钮
await page.locator('button').locator('nth=-1').click();
父元素定位器
当需要定位某个元素的父元素时,大多数情况下你应该使用 locator.filter() 通过子元素定位器进行筛选。例如,考虑以下 DOM 结构:
<li><label>Hello</label></li>
<li><label>World</label></li>
如果你想定位包含文本 "Hello"
的 label 的父元素 <li>
,使用 locator.filter() 是最佳选择:
const child = page.getByText('Hello');
const parent = page.getByRole('listitem').filter({ has: child });
或者,如果找不到合适的父元素定位器,可以使用 xpath=..
。请注意这种方法不够可靠,因为任何 DOM 结构的改变都会导致测试失败。在可能的情况下,优先使用 locator.filter()。
const parent = page.getByText('Hello').locator('xpath=..');
React 定位器
React 定位器目前处于实验阶段,前缀为 _
。其功能在未来可能会发生变化。
React 定位器允许通过组件名称和属性值来查找元素。其语法与 CSS 属性选择器非常相似,并支持所有 CSS 属性选择器运算符。
在 React 定位器中,组件名称使用 驼峰命名法(CamelCase) 表示。
await page.locator('_react=BookItem').click();
更多示例:
- 通过组件名匹配:
_react=BookItem
- 通过组件和精确属性值匹配(区分大小写):
_react=BookItem[author = "Steven King"]
- 仅通过属性值匹配(不区分大小写):
_react=[author = "steven king" i]
- 通过组件和真值属性匹配:
_react=MyButton[enabled]
- 通过组件和布尔值匹配:
_react=MyButton[enabled = false]
- 通过属性值子串匹配:
_react=[author *= "King"]
- 通过组件和多个属性匹配:
_react=BookItem[author *= "king" i][year = 1990]
- 通过嵌套属性值匹配:
_react=[some.nested.value = 12]
- 通过组件和属性值前缀匹配:
_react=BookItem[author ^= "Steven"]
- 通过组件和属性值后缀匹配:
_react=BookItem[author $= "Steven"]
- 通过组件和键值匹配:
_react=BookItem[key = '2']
- 通过属性值正则表达式匹配:
_react=[author = /Steven(\\s+King)?/i]
要查看 React 元素树中的组件名称,可以使用 React DevTools。
React 定位器支持 React 15 及以上版本。
React 定位器以及 React DevTools 仅适用于未压缩的应用程序构建版本。
Vue 定位器
Vue 定位器目前是实验性功能,使用 _
前缀。其功能可能在将来发生变化。
Vue 定位器允许通过组件名称和属性值来查找元素。其语法与 CSS 属性选择器非常相似,并支持所有 CSS 属性选择器运算符。
在 Vue 定位器中,组件名称使用 短横线命名法(kebab-case) 表示。
await page.locator('_vue=book-item').click();
更多示例:
- 通过组件名匹配:
_vue=book-item
- 通过组件和精确属性值匹配(区分大小写):
_vue=book-item[author = "Steven King"]
- 仅通过属性值匹配(不区分大小写):
_vue=[author = "steven king" i]
- 通过组件和真值属性匹配:
_vue=my-button[enabled]
- 通过组件和布尔值匹配:
_vue=my-button[enabled = false]
- 通过属性值子串匹配:
_vue=[author *= "King"]
- 通过组件和多个属性匹配:
_vue=book-item[author *= "king" i][year = 1990]
- 通过嵌套属性值匹配:
_vue=[some.nested.value = 12]
- 通过组件和属性值前缀匹配:
_vue=book-item[author ^= "Steven"]
- 通过组件和属性值后缀匹配:
_vue=book-item[author $= "Steven"]
- 通过属性值正则表达式匹配:
_vue=[author = /Steven(\\s+King)?/i]
要查看 Vue 元素在树结构中的名称,可以使用 Vue DevTools。
Vue 定位器支持 Vue2 及以上版本。
Vue 定位器与 Vue DevTools 一样,仅适用于未压缩的应用程序构建版本。
XPath 定位器
我们建议优先使用 用户可见的定位器(如文本或可访问角色),而不是使用与实现细节紧密耦合且容易因页面变更而失效的 XPath。
XPath 定位器等同于调用 Document.evaluate
。
await page.locator('xpath=//button').click();
任何以 //
或 ..
开头的选择器字符串都会被自动识别为 XPath 选择器。例如,Playwright 会将 '//html/body'
转换为 'xpath=//html/body'
。
XPath 无法穿透 Shadow DOM 根节点。
XPath 联合查询
管道运算符 (|
) 可用于在 XPath 中指定多个选择器。它将匹配能被列表中任一选择器选中的所有元素。
// 等待确认对话框或加载旋转图标出现
await page.locator(
`//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']`
).waitFor();
标签到表单控件的重定向
我们建议通过标签文本定位,而不是依赖标签到控件的重定向。
Playwright 中的目标输入操作会自动区分标签和控件,因此您可以定位标签来对关联的控件执行操作。
例如,考虑以下 DOM 结构:<label for="password">Password:</label><input id="password" type="password">
。您可以使用 page.getByText() 通过 "Password" 文本来定位标签。但是,以下操作将在输入框上执行,而不是在标签上:
- locator.click() 会点击标签并自动聚焦到输入框;
- locator.fill() 会填充输入框;
- locator.inputValue() 会返回输入框的值;
- locator.selectText() 会在输入框中选择文本;
- locator.setInputFiles() 会为
type=file
的输入框设置文件; - locator.selectOption() 会从选择框中选择一个选项。
// 通过定位标签来填充输入框
await page.getByText('Password').fill('secret');
然而,其他方法将直接作用于标签本身,例如 expect(locator).toHaveText() 会断言标签的文本内容,而不是输入框的内容。
// 验证标签的文本内容
await expect(page.locator('label')).toHaveText('Password');
传统文本定位器
我们推荐使用现代的 文本定位器。
传统文本定位器用于匹配包含指定文本的元素。
await page.locator('text=Log in').click();
传统文本定位器有以下几种变体:
-
text=Log in
- 默认匹配方式不区分大小写,会去除空白字符并搜索子字符串。例如,text=Log
可以匹配<button>Log in</button>
。await page.locator('text=Log in').click();
-
text="Log in"
- 文本内容可以用单引号或双引号包裹,用于在去除空白字符后精确匹配文本节点内容。例如,
text="Log"
不会匹配<button>Log in</button>
,因为<button>
包含的是单个文本节点"Log in"
,与"Log"
不相等。但是,text="Log"
可以匹配<button> Log <span>in</span></button>
,因为<button>
包含文本节点" Log "
。这种精确模式意味着区分大小写,所以text="Download"
不会匹配<button>download</button>
。引号包裹的内容遵循常规转义规则,例如在双引号字符串中使用
\"
来转义双引号:text="foo\"bar"
。await page.locator('text="Log in"').click();
-
/Log\s*in/i
- 内容可以是类似 JavaScript 正则表达式的格式,用/
符号包裹。例如,text=/Log\s*in/i
可以匹配<button>Login</button>
和<button>log IN</button>
。await page.locator('text=/Log\\s*in/i').click();
以引号("
或 '
)开头和结尾的字符串选择器会被视为传统文本定位器。例如,"Log in"
在内部会被转换为 text="Log in"
。
匹配时总会规范化空白字符。例如,将多个空格合并为一个,将换行符转为空格,并忽略首尾空白。
类型为 button
和 submit
的输入元素会通过其 value
属性而非文本内容进行匹配。例如,text=Log in
可以匹配 <input type=button value="Log in">
。
id、data-testid、data-test-id、data-test 选择器
我们推荐使用通过测试 ID 定位的方式替代。
Playwright 支持使用特定属性来选择元素的简写形式。目前仅支持以下属性:
id
data-testid
data-test-id
data-test
// 填充 id 为 "username" 的输入框
await page.locator('id=username').fill('value');
// 点击 data-test-id 为 "submit" 的元素
await page.locator('data-test-id=submit').click();
属性选择器不是 CSS 选择器,因此不支持像 :enabled
这样的 CSS 特性。如需更多功能,请使用正确的 css 选择器,例如 css=[data-test="login"]:enabled
。
链式选择器
我们推荐使用链式定位器的方式替代。
定义为 engine=body
或短格式的选择器可以通过 >>
标记进行组合,例如 selector1 >> selector2 >> selectors3
。当选择器以链式组合时,下一个选择器会相对于前一个选择器的结果进行查询。
例如:
css=article >> css=.bar > .baz >> css=span[attr=value]
等价于:
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]');
如果选择器需要在主体中包含 >>
,则应将其在字符串中转义以避免与链式分隔符混淆,例如 text="some >> text"
。
中间匹配
我们推荐使用 通过子元素/后代元素过滤 来定位包含其他元素的元素。
默认情况下,链式选择器会解析为最后一个选择器查询到的元素。在选择器前添加 *
前缀可以捕获中间选择器查询到的元素。
例如,css=article >> text=Hello
会捕获包含文本 Hello
的元素,而 *css=article >> text=Hello
(注意 *
)会捕获包含某个具有 Hello
文本元素的 article
元素。