其他 Locator 定位器
介绍
查看主要的定位器指南,了解最常见和推荐的定位器。
除了推荐的定位器如 page.get_by_role() 和 page.get_by_text() 之外,Playwright 还支持本指南中描述的各种其他定位器。
CSS 定位器
我们建议优先使用用户可见的定位器,如文本或可访问角色,而不是使用与实现绑定的 CSS,因为页面更改时可能会导致其失效。
Playwright 可以通过 CSS 选择器定位元素。
- 同步
- 异步
page.locator("css=button").click()
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>
page.locator(':has-text("Playwright")').click()
# 正确,仅匹配 <article> 元素
page.locator('article:has-text("All products")').click()# 错误,将匹配许多元素,包括 <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" 的元素:- 同步
- 异步
page.locator("#nav-bar :text('Home')").click()
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'>不可见</button>
<button>可见</button>
-
这将找到两个按钮,并抛出一个严格性违规错误:
- 同步
- 异步
page.locator("button").click()
await page.locator("button").click()
-
这将仅找到第二个按钮,因为它是可见的,然后点击它。
- 同步
- 异步
page.locator("button:visible").click()
await page.locator("button:visible").click()
CSS:包含其他元素的元素
:has()
伪类是一个实验性的 CSS 伪类。如果传递的选择器参数中相对于给定元素的 :scope
匹配至少一个元素,则返回该元素。
以下代码片段返回包含 <div class=promo>
的 <article>
元素的文本内容。
- 同步
- 异步
page.locator("article:has(div.promo)").text_content()
await page.locator("article:has(div.promo)").text_content()
CSS:匹配任一条件的元素
逗号分隔的 CSS 选择器列表将匹配列表中任一选择器可以选择的所有元素。
- 同步
- 异步
# 点击具有 "Log in" 或 "Sign in" 文本的 <button>。
page.locator('button:has-text("Log in"), button:has-text("Sign in")').click()
# 点击具有 "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"))
,很可能匹配到的不是目标输入框,而是文本和目标输入框之间的某个空元素。
布局伪类使用 bounding client rect 来计算元素之间的距离和相对位置。
: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" 右侧的输入框。
page.locator("input:right-of(:text(\"Username\"))").fill("value")
# 点击靠近促销卡片的按钮。
page.locator("button:near(.promo-card)").click()
# 点击列表中最靠近 "Label 3" 的单选框。
page.locator("[type=radio]:left-of(:text(\"Label 3\"))").first.click()
# 填充位于 "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" 按钮
page.locator(":nth-match(:text('Buy'), 3)").click()
# 点击第三个 "Buy" 按钮
await page.locator(":nth-match(:text('Buy'), 3)").click()
:nth-match()
也可用于等待指定数量的元素出现,使用 locator.wait_for()。
- 同步
- 异步
# 等待所有三个按钮可见
page.locator(":nth-match(:text('Buy'), 3)").wait_for()
# 等待所有三个按钮可见
await page.locator(":nth-match(:text('Buy'), 3)").wait_for()
与 :nth-child()
不同,元素不需要是兄弟元素,它们可以位于页面上的任何位置。在上面的代码片段中,所有三个按钮都匹配 :text("Buy")
选择器,而 :nth-match()
选择第三个按钮。
N-th 指定序列定位器
您可以使用 nth=
定位器并传递从零开始的索引来缩小查询范围到第 n 个匹配项。
- 同步
- 异步
# 点击第一个按钮
page.locator("button").locator("nth=0").click()
# 点击最后一个按钮
page.locator("button").locator("nth=-1").click()
# 点击第一个按钮
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"
的标签的父 <li>
元素,使用 locator.filter() 是最佳选择:
- 同步
- 异步
child = page.get_by_text("Hello")
parent = page.get_by_role("listitem").filter(has=child)
child = page.get_by_text("Hello")
parent = page.get_by_role("listitem").filter(has=child)
或者,如果您无法为父元素找到合适的定位器,可以使用 xpath=..
。请注意,此方法不太可靠,因为对 DOM 结构的任何更改都会破坏您的测试。尽可能优先使用 locator.filter()。
- 同步
- 异步
parent = page.get_by_text("Hello").locator('xpath=..')
parent = page.get_by_text("Hello").locator('xpath=..')
React 定位器
React 定位器是实验性的,并以 _
为前缀。其功能可能在未来发生变化。
React 定位器允许通过组件名称和属性值查找元素。其语法与 CSS 属性选择器 非常相似,并支持所有 CSS 属性选择器操作符。
在 React 定位器中,组件名称以 CamelCase(驼峰命名法)表示。
- 同步
- 异步
page.locator("_react=BookItem").click()
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(短横线命名法)表示。
- 同步
- 异步
page.locator("_vue=book-item").click()
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
。
- 同步
- 异步
page.locator("xpath=//button").click()
await page.locator("xpath=//button").click()
任何以 //
或 ..
开头的选择器字符串都会被视为 xpath 选择器。例如,Playwright 会将 '//html/body'
转换为 'xpath=//html/body'
。
XPath 不会穿透 shadow root。
XPath 联合
管道符(|
)可用于在 XPath 中指定多个选择器。它会匹配列表中任一选择器可以选中的所有元素。
- 同步
- 异步
# 等待确认对话框或加载中的 spinner 出现。
page.locator("//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']").wait_for()
# 等待确认对话框或加载中的 spinner 出现。
await page.locator("//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']").wait_for()
标签到表单控件的重定向
我们建议通过标签文本定位,而不是依赖标签到控件的重定向。
Playwright 中的输入操作会自动区分标签和控件,因此你可以通过标签来操作其关联的控件。
例如,考虑如下 DOM 结构:<label for="password">Password:</label><input id="password" type="password">
。你可以通过 page.get_by_text() 使用 "Password" 文本定位标签。然而,以下操作会作用于输入框而不是标签本身:
- locator.click() 会点击标签并自动聚焦输入框;
- locator.fill() 会填充输入框;
- locator.input_value() 会返回输入框的值;
- locator.select_text() 会选中输入框中的文本;
- locator.set_input_files() 会为
type=file
的输入框设置文件; - locator.select_option() 会从下拉框中选择选项。
- 同步
- 异步
# 通过定位标签来填充输入框。
page.get_by_text("Password").fill("secret")
# 通过定位标签来填充输入框。
await page.get_by_text("Password").fill("secret")
但是,其他方法会直接作用于标签本身,例如 expect(locator).to_have_text() 会断言标签的文本内容,而不是输入框的值。
- 同步
- 异步
# 通过定位标签断言其文本内容。
expect(page.locator("label")).to_have_text("Password")
# 通过定位标签断言其文本内容。
await expect(page.locator("label")).to_have_text("Password")
旧版文本定位器
我们推荐使用现代的文本定位器。
旧版文本定位器用于匹配包含指定文本的元素。
- 同步
- 异步
page.locator("text=Log in").click()
await page.locator("text=Log in").click()
旧版文本定位器有几种变体:
-
text=Log in
—— 默认匹配为不区分大小写,修剪空白并进行子字符串搜索。例如,text=Log
匹配<button>Log in</button>
。- 同步
- 异步
page.locator("text=Log in").click()
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"
。- 同步
- 异步
page.locator("text='Log in'").click()
await page.locator("text='Log in'").click()
-
/Log\s*in/i
—— 内容可以是用/
包裹的类 JavaScript 正则表达式。例如,text=/Log\s*in/i
匹配<button>Login</button>
和<button>log IN</button>
。- 同步
- 异步
page.locator("text=/Log\s*in/i").click()
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" 的输入框
page.locator('id=username').fill('value')
# 点击 data-test-id 为 "submit" 的元素
page.locator('data-test-id=submit').click()
# 填充 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 >> selector3
。当选择器链式调用时,下一个选择器会相对于上一个选择器的结果进行查询。
例如:
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
元素。