其他定位器
介绍
请查看主要定位器指南,了解最常用和推荐的定位器。
除了推荐的定位器,如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>
。// Wrong, will match many elements including <body>
await page.locator(':has-text("Playwright")').click();
// Correct, only matches the <article> element
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 选择器列表将匹配列表中任一选择器可以选择的所有元素。
// Clicks a <button> that has either a "Log in" or "Sign in" text.
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"))
,很可能您不会得到要查找的输入框,而是一些位于文本和目标输入框之间的空元素。
布局伪类使用getBoundingClientRect来计算元素的距离和相对位置。
:right-of(div > button)
- 匹配位于任何与内部选择器匹配的元素右侧的元素,不限制垂直位置。:left-of(div > button)
- 匹配位于任何与内部选择器匹配的元素左侧的元素,不限制垂直位置。:above(div > button)
- 匹配位于任何与内部选择器匹配的元素上方的元素,不限制水平位置。:below(div > button)
- 匹配位于任何与内部选择器匹配的元素下方的元素,不限制水平位置。:near(div > button)
- 匹配靠近(在 50 CSS 像素范围内)任何与内部选择器匹配的元素的元素。
请注意,结果匹配是按其到锚定元素的距离排序的,因此您可以使用locator.first()选择最接近的一个。这仅在您有一系列相似元素,并且最接近的一个显然是正确的情况下才有用。但是,在其他情况下使用locator.first()很可能无法按预期工作 - 它不会定位您正在搜索的元素,而是某个碰巧最接近的其他元素,例如一个随机的空 <div>
,或者一个已滚动出视图且当前不可见的元素。
// Fill an input to the right of "Username".
await page.locator('input:right-of(:text("Username"))').fill('value');
// Click a button near the promo card.
await page.locator('button:near(.promo-card)').click();
// Click the radio input in the list closest to the "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 开始的。
// Click the third "Buy" button
await page.locator(':nth-match(:text("Buy"), 3)').click();
:nth-match()
也可用于使用locator.waitFor()等待指定数量的元素出现。
// Wait until all three buttons are visible
await page.locator(':nth-match(:text("Buy"), 3)').waitFor();
与:nth-child()
不同,元素不必是同级元素,它们可以位于页面上的任何位置。在上面的代码片段中,所有三个按钮都匹配 :text("Buy")
选择器,而 :nth-match()
选择第三个按钮。
第 N 个元素定位器
您可以使用 nth=
定位器并传递一个从零开始的索引,将查询缩小到第 n 个匹配项。
// Click first button
await page.locator('button').locator('nth=0').click();
// Click last button
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"]
- 按组件和key匹配:
_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 Roots。
XPath 合并
管道符 (|
) 可用于在 XPath 中指定多个选择器。它将匹配列表中任一选择器可以选择的所有元素。
// Waits for either confirmation dialog or load spinner.
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() 将从选择框中选择一个选项。
// Fill the input by targeting the label.
await page.getByText('Password').fill('secret');
但是,其他方法将定位标签本身,例如expect(locator).toHaveText() 将断言标签的文本内容,而不是输入字段。
// Fill the input by targeting the label.
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
// Fill an input with the id "username"
await page.locator('id=username').fill('value');
// Click an element with data-test-id "submit"
await page.locator('data-test-id=submit').click();
属性选择器不是 CSS 选择器,因此不支持任何 CSS 特有的东西,如 :enabled
。如需更多功能,请使用适当的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
元素。