其他定位器
简介
有关最常用和推荐的定位器,请查看主要的 定位器指南。
除了 Page.GetByRole() 和 Page.GetByText() 等推荐的定位器之外,Playwright 还支持本指南中描述的其他各种定位器。
CSS 定位器
我们建议优先使用文本或可访问角色等 用户可见定位器,而不是使用与实现相关联且在页面更改时可能会失效的 CSS。
Playwright 可以通过 CSS 选择器定位元素。
await page.Locator("css=button").ClickAsync();
Playwright 以两种方式扩展了标准 CSS 选择器:
- CSS 选择器可以穿透开放的影子 DOM(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\")").ClickAsync();
// 正确,仅匹配 <article> 元素
await page.Locator("article:has-text(\"Playwright\")").ClickAsync(); -
#nav-bar :text("Home")
-:text()
伪类匹配包含指定文本的最小元素。匹配不区分大小写,会去除空白字符,并搜索子字符串。例如,这将在
#nav-bar
元素内部的某个位置找到文本为 "Home" 的元素:await page.Locator("#nav-bar :text('Home')").ClickAsync();
-
#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>
-
这将找到两个按钮,并抛出 严格性 违规错误:
await page.Locator("button").ClickAsync();
-
这将仅找到第二个按钮,因为它是可见的,然后点击它。
await page.Locator("button:visible").ClickAsync();
CSS:包含其他元素的元素
:has()
伪类是一个 实验性 CSS 伪类。如果相对于给定元素的 :scope
作为参数传递的任何选择器至少匹配一个元素,则它会返回该元素。
以下代码片段返回内部包含 <div class=promo>
的 <article>
元素的文本内容。
await page.Locator("article:has(div.promo)").TextContentAsync();
CSS:匹配其中一个条件的元素
以逗号分隔的 CSS 选择器列表将匹配可以由该列表中的任何一个选择器选中的所有元素。
// 点击文本为 "Log in" 或 "Sign in" 的 <button>。
await page.Locator("button:has-text(\"Log in\"), button:has-text(\"Sign in\")").ClickAsync();
: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\"))").FillAsync("value");
// 点击促销卡片附近的按钮。
await page.Locator("button:near(.promo-card)").ClickAsync();
// 点击列表中最靠近 “Label 3” 的单选输入框。
await page.Locator("[type=radio]:left-of(:text(\"Label 3\"))").First.ClickAsync();
所有布局伪类都支持将可选的最大像素距离作为最后一个参数。例如,button:near(:text("Username"), 120)
会匹配距离文本为 “Username” 的元素最多 120 CSS 像素的按钮。
CSS:从查询结果中选取第 n 个匹配项
通常可以通过某些属性或文本内容来区分元素,这样对页面变化的适应性更强。
有时页面包含许多相似的元素,很难选择特定的一个。例如:
<section> <button>购买</button> </section>
<article><div> <button>购买</button> </div></article>
<div><div> <button>购买</button> </div></div>
在这种情况下,:nth-match(:text("购买"), 3)
将从上述代码片段中选择第三个按钮。请注意,索引从 1 开始。
// 点击第三个“购买”按钮
await page.Locator(":nth-match(:text('购买'), 3)").ClickAsync();
:nth-match()
对于使用 Locator.WaitForAsync() 等待特定数量的元素出现也很有用。
// 等待所有三个按钮可见
await page.Locator(":nth-match(:text('购买'), 3)").WaitForAsync();
与 :nth-child()
不同,元素不必是兄弟元素,它们可以在页面的任何位置。在上述代码片段中,所有三个按钮都匹配 :text("购买")
选择器,而 :nth-match()
选择第三个按钮。
第 n 个元素定位器
你可以使用 nth=
定位器(传入从 0 开始的索引)将查询范围缩小到第 n 个匹配项。
// 点击第一个按钮
await page.Locator("button").Locator("nth=0").ClickAsync();
// 点击最后一个按钮
await page.Locator("button").Locator("nth=-1").ClickAsync();
父元素定位器
当你需要定位某个元素的父元素时,大多数情况下你应该通过子元素定位器使用 Locator.Filter()。例如,考虑以下 DOM 结构:
<li><label>Hello</label></li>
<li><label>World</label></li>
如果你想定位文本为 "Hello"
的 <label>
元素的父 <li>
元素,使用 Locator.Filter() 是最佳选择:
var child = page.GetByText("Hello");
var parent = page.GetByRole(AriaRole.Listitem).Filter(new () { Has = child });
或者,如果你找不到适合父元素的定位器,可以使用 xpath=..
。请注意,这种方法不太可靠,因为 DOM 结构的任何变化都会导致你的测试失败。尽可能优先使用 Locator.Filter()。
var parent = page.GetByText("Hello").Locator("xpath=..");
React 定位器
React 定位器尚处于实验阶段,前缀为 _
。其功能在未来可能会有所变化。
React 定位器允许通过组件名称和属性值查找元素。其语法与 CSS 属性选择器 非常相似,并支持所有 CSS 属性选择器运算符。
在 React 定位器中,组件名称采用 驼峰式命名法(CamelCase)。
await page.Locator("_react=BookItem").ClickAsync();
更多示例:
- 按 组件 匹配:
_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").ClickAsync();
更多示例:
- 按 组件 匹配:
_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").ClickAsync();
任何以//
或..
开头的选择器字符串都被视为XPath选择器。例如,Playwright会将'//html/body'
转换为'xpath=//html/body'
。
XPath无法穿透影子根(shadow roots)。
XPath联合
管道操作符 (|
) 可用于在XPath中指定多个选择器。它将匹配列表中任何一个选择器所能选中的所有元素。
// 等待确认对话框或加载指示器。
await page.Locator("//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']").WaitForAsync();
标签到表单控件的重定向
我们建议通过标签文本定位,而不是依赖标签到控件的重定向。
Playwright 中的目标输入操作会自动区分标签和控件,因此你可以定位标签,以对关联的控件执行操作。
例如,考虑以下 DOM 结构:<label for="password">Password:</label><input id="password" type="password">
。你可以使用 Page.GetByText() 通过其 “Password” 文本来定位标签。但是,以下操作将在输入框上执行,而不是在标签上:
- Locator.ClickAsync() 将点击标签并自动聚焦到输入框;
- Locator.FillAsync() 将填充输入框;
- Locator.InputValueAsync() 将返回输入框的值;
- Locator.SelectTextAsync() 将选中输入框中的文本;
- Locator.SetInputFilesAsync() 将为
type=file
的输入框设置文件; - Locator.SelectOptionAsync() 将从选择框中选择一个选项。
// 通过定位标签来填充输入框。
await page.GetByText("Password").FillAsync("secret");
但是,其他方法将定位标签本身,例如 Expect(Locator).ToHaveTextAsync() 将断言标签的文本内容,而不是输入框的。
// 通过定位标签来填充输入框。
await Expect(Page.Locator("label")).ToHaveTextAsync("Password");
旧版文本定位器
我们建议使用现代的 文本定位器。
旧版文本定位器会匹配包含传入文本的元素。
await page.Locator("text=Log in").ClickAsync();
旧版文本定位器有几种变体:
-
text=Log in
- 默认匹配不区分大小写,会修剪空白字符并搜索子字符串。例如,text=Log
会匹配<button>Log in</button>
。await page.Locator("text=Log in").ClickAsync();
-
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'").ClickAsync();
-
/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").ClickAsync();
以引号("
或 '
)开头和结尾的字符串选择器会被视为旧版文本定位器。例如,"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").FillAsync("value");
// 点击 data-test-id 为 "submit" 的元素
await page.Locator("data-test-id=submit").ClickAsync();
属性选择器不是 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
元素。