元素选择器
选择器是指向页中元素的字符串。它们用于通过 page.click(selector[, options])、page.fill(selector, value[, options])等方法对这些元素执行操作。所有这些方法都接受选择器 selector
作为它们的第一个参数。
快速指南
- 文本选择器
await page.click('text=Log in');
page.click('text=Log in')
了解有关 文本选择器 的更多信息。
- CSS 选择器
await page.click('button');
await page.click('#nav-bar .contact-us-item');
page.click('button')
page.click('#nav-bar .contact-us-item')
了解更多关于 CSS 选择器 的信息。
- 按属性选择,使用 CSS 选择器
await page.click('[data-test=login-button]');
await page.click('[aria-label="Sign in"]');
page.click('[data-test=login-button]')
page.click('[aria-label="Sign in"]')
了解更多关于 CSS 选择器 的信息。
- 组合 CSS 和文本选择器
await page.click('article:has-text("Playwright")');
await page.click('#nav-bar :text("Contact us")');
page.click('article:has-text("Playwright")')
page.click('#nav-bar :text("Contact us")')
了解有关 :has-text()
和 :text()
伪类 的更多信息。
- 元素,该元素包含另一个具有 CSS 选择器的
await page.click('.item-description:has(.item-promo-banner)');
page.click('.item-description:has(.item-promo-banner)')
了解有关 :has()
伪类的更多信息。
- 使用 CSS 选择器根据布局进行选择
await page.click('input:right-of(:text("Username"))');
page.click('input:right-of(:text("Username"))')
了解更多布局选择器。
- 仅可见元素,带有 CSS 选择器
await page.click('.login-button:visible');
page.click('.login-button:visible')
了解更多选择可见元素。
- 选择第N个匹配项
await page.click(':nth-match(:text("Buy"), 3)');
page.click(':nth-match(:text("Buy"), 3)')
了解有关 :nth-match()
伪类的更多信息。
- XPath 选择器
await page.click('xpath=//button');
page.click('xpath=//button')
了解有关 XPath 选择器 的更多信息。
- React 选择器(实验性)
await page.click('_react=ListItem[text *= "milk" i]');
page.click('_react=ListItem[text *= "milk" i]')
了解有关 React 选择器 的更多信息。
- VUE 选择器(实验性)
await page.click('_vue=list-item[text *= "milk" i]');
page.click('_vue=list-item[text *= "milk" i]')
了解更多关于 vue 选择器 的信息。
文本选择器
文本选择器定位包含传递文本的元素。
await page.click('text=Log in');
#
page.click('text=Log in')
文本选择器有一些变化:
text=Log in
:默认匹配不区分大小写,并搜索子字符串。例如,text=Log
和<button>Log in</button>
。text="Log in"
:文本主体可以使用单引号或双引号进行转义,以搜索具有确切内容的文本节点。例如,text="Log"
不匹配<button>Log in</button>
,因为<button>
包含不等于"Log"
的单个文本节点"Log in"
。但是,text="Log"
匹配<button>Log<span>in</span></button>
,因为<button>
包含文本节点"Log"
。
引用的主体遵循通常的转义规则,例如 \"
,用于转义双引号字符串中的双引号: text="foo\"bar"
。
await page.click('text="foo\"bar"');
page.click('text="foo\"bar"')
"Log in"
:以引号(或""
''
)开始和结束的选择器被认为是文本选择器。例如,"Log in"
转换为text="Log in"
内部。
await page.click('"Log in"');
page.click('"Log in"')
/Log\s*in/i
文本可以是JavaScript 正则表达式字符串——即用/
符号包装的正则表达式字符串。例如,text=/Log\s*in/i
匹配项<button>Login</button>
和<button>log IN</button>
。
await page.click('text=/Log\\s*in/i');
page.click('text=/Log\\s*in/i')
article:has-text("Playwright")
::has-text()
伪类可以在 CSS 选择器中使用。它匹配内部某处包含指定文本的任何元素,可能在子元素或后代元素中。例如,article:has-text("Playwright")
火柴<article><div>Playwright</div></article>
。
请注意,:has-text()
应与其他 css
选择器一起使用,比如p.notes:has-text("警告")
。否则它将匹配包含指定文本的所有元素,包括 <body>
。
// Wrong, will match many elements including <body>
await page.click(':has-text("Playwright")');
// Correct, only matches the <article> element
await page.click('article:has-text("Playwright")');
# Wrong, will match many elements including <body>
page.click(':has-text("Playwright")')
# Correct, only matches the <article> element
page.click('article:has-text("Playwright")')
#nav-bar :text("Home")
::text()
伪类可以在 CSS 选择器中使用。它匹配包含指定文本的最小元素。此示例等效于text=Home
,但位于#nav-bar
元素内部。
await page.click('#nav-bar :text("Home")');
page.click('#nav-bar :text("Home")')
#nav-bar :text-is("Home")
+:text-is()
伪类可以在 CSS 选择器中使用,用于严格的文本节点匹配。此示例等效于text="Home"
(注意引号),但位于#nav-bar
元素内部。#nav-bar :text-matches("reg?ex", "i")
::text-matches()
伪类可以在 CSS 选择器中使用,用于基于正则表达式的匹配。此示例等效于text=/reg?ex/i
,但位于#nav-bar
元素内部。
匹配始终规范化空格,例如,它将多个空格转换为一个,将换行符转换为空格,并忽略前导空格和尾随空格。
对于
type
为submit
和button
的<input>
元素,文本选择器会通过其value
属性而不是显示的文本内容进行匹配。例如,text=Log in
会匹配元素<input type=button value="Log in">
。
CSS 选择器
Playwright以两种方式增加标准 CSS 选择器:
- 默认情况下,
css
引擎穿透打开的Shadown DOM. - Playwright添加了自定义伪类
:visible
,如,:text
等等。
await page.click('button');
page.click('button')
筛选可见元素
使用Playwright选择可见元素有两种方法:
- CSS 选择器中
:visible
的伪类; visible=
选择器引擎;
如果你的选择器是 CSS,并且不想依赖于链式选择器,使用 :visible
伪类,如: input:visible
。如果你更喜欢组合选择器引擎,请使用 input >> visible=true
。后者允许你 text=
将、 xpath=
和其他选择器引擎与可见性过滤器相结合。
例如,input
匹配页面上的所有输入,而 input:visible
和 input >> visible=true
仅匹配可见输入。这对于区分非常相似但可见性不同的元素非常有用。
通常最好遵循最佳实践并找到一种更可靠的方法来唯一标识元素。
考虑一个有两个按钮的页面,第一个按钮不可见,第二个按钮可见。
<button style='display: none'>Invisible</button>
<button>Visible</button>
- 这将找到第一个按钮,因为它是 DOM 顺序中的第一个按钮。然后,它将在单击之前等待按钮变为可见,或者在等待时超时:
await page.click('button');
# 第一个按钮
page.click('button')
这也是常见的卡住情况,如果选择器正确但是实际执行时超时退出了,很可能是因为页面中有其它的隐藏元素被匹配到了,从而使执行引擎会一直等待该隐藏元素出现,从而超时。
- 这些将找到第二个按钮,因为它是可见的,然后单击它。
await page.click('button:visible');
await page.click('button >> visible=true');
page.click('button:visible')
page.click('button >> visible=true')
选择包含目标元素的元素
:has()
伪类是一个实验 CSS 伪类,反过来根据子元素定位父元素,传入:has()
的参数为另一个选择器。
假设完整的选择器写作form:has(#username)
,则会去匹配id
属性为"username"
的表单<form>
元素。
下面的代码片段返回内部包含 <div class=promo>
的元素的 <article>
文本内容。
await page.textContent('article:has(div.promo)');
page.text_content('article:has(div.promo)')
使用多个选择器同时匹配元素
:is()
伪类是一个实验 CSS 伪类,能够实现使用多个选择器同时匹配,返回满足任意一个选择器的元素。该函数将选择器列表(逗号,
分隔选择器)作为其参数,并选择可由该列表中的选择器之一选择的任何元素。这对于以更紧凑的形式编写大型选择器非常有用。
// Clicks a <button> that has either a "Log in" or "Sign in" text.
await page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))');
# Clicks a <button> that has either a "Log in" or "Sign in" text.
page.click(':is(button:has-text("Log in"), button:has-text("Sign in"))')
在 Shadown DOM 中选择元素
默认情况下,我们的 css
和 text
引擎穿透Shadow DOM:
- 首先,它们按照迭代顺序搜索 Light DOM 中的元素,然后
- 然后,它们按照迭代顺序在开放的Shadow Root内进行递归搜索。
特别在 css
引擎中,任何后代组合或子组合穿透任意数量的开放Shadow Root节点,包括选择器起始处的隐式后代组合子。它不会在封闭的Shadow Root或 iframe 中进行搜索。
如果你想退出这种行为,你可以使用 :light
CSS 扩展或 text:light
选择器引擎。它们不会穿透Shadow Root。
await page.click(':light(.article > .header)');
page.click(':light(.article > .header)')
更高级的Shadow DOM 用例:
<article>
<div>In the light dom</div>
<div slot='myslot'>In the light dom, but goes into the shadow slot</div>
#shadow-root
<div class='in-the-shadow'>
<span class='content'>
In the shadow dom
#shadow-root
<li id='target'>Deep in the shadow</li>
</span>
</div>
<slot name='myslot'></slot>
</article>
"article div"
与":light(article div)"
两者都匹配第一个<div>In the light dom</div>
。"article > div"
和":light(article > div)"
匹配div
两个元素,这两个元素是的article
直接子元素。"article.in-the-shadow"
匹配<div class='in-the-shadow'>
,穿透Shadow Root,而":light(article.in-the-shadow)"
不匹配任何东西。":light(article div > span)"
不匹配任何内容,因为两个 light-domdiv
元素都不包含span
。"article div > span"
匹配<span class='content'>
,穿透Shadow Root部。"article >.in-the-shadow"
不匹配任何内容,因为<div class='in-the-shadow'>
不是的article
直接子级- 与任何东西都
":light(article >.in-the-shadow)"
不匹配。 "article li#target"
匹配<li id='target'>Deep in the shadow</li>
,穿透两个Shadow Root。
根据布局选择元素
Playwright可以根据页面布局选择元素,常用于表单的字段填写时,根据字段名定位输入框。这些可以与常规 CSS 选择器结合以获得更好的结果,例如 input:right-of(:text("Password"))
匹配"密码"文本右侧的<input>
元素。
布局选择器取决于页面布局,可能会产生意外的结果。例如,当布局改变一个像素时,可以匹配不同的元素。
布局选择器用于bounding client rect计算元素的距离和相对位置。
:right-of(inner > selector)
:匹配与内部选择器匹配的任何元素右侧的元素。:left-of(inner > selector)
:匹配与内部选择器匹配的任何元素左侧的元素。:above(inner > selector)
:匹配位于匹配内部选择器的任何元素之上的元素。:below(inner > selector)
:匹配位于与内部选择器匹配的任何元素之下的元素。:near(inner > selector)
:匹配与内部选择器匹配的任何元素附近(50 个 CSS 像素内)的元素。
// Fill an input to the right of "Username".
await page.fill('input:right-of(:text("Username"))', 'value');
// Click a button near the promo card.
await page.click('button:near(.promo-card)');
# Fill an input to the right of "Username".
page.fill('input:right-of(:text("Username"))', 'value')
# Click a button near the promo card.
page.click('button:near(.promo-card)')
所有布局选择器都支持将可选的最大像素距离作为最后一个参数。例如 button:near(:text("Username"), 120)
,将距离元素最多 120 像素的按钮与文本"username"匹配。
XPath 选择器
XPath是Web中用于描述元素所处的层次(相对于根节点<body>
的层次),因此就有了XPath选择器来定位元素,相当于调用 Document.evaluate
。例如: xpath=//html/body
。
以 ..
或 //
开头的选择器被假定为 XPath 选择器。例如,Playwright会自动将 '//html/body'
转换为 'xpath=//html/body'
。
xpath
不穿透Shadow Root
第N个元素选择器
你可以使用 nth=
选择器定位第N个匹配项,一般与其他的选择器一起调用,与 CSS :nth-child
不同, nth=
选择器的索引是从0开始的,即nth=0
表示选中第一个元素。比如可以用于定位列表<ul>
元素中的第2个列表项,选择器可以写作ul >> nth=1
。
// Click first button
await page.click('button >> nth=0');
// Click last button
await page.click('button >> nth=-1');
# Click first button
page.click('button >> nth=0')
# Click last button
page.click('button >> nth=-1')
React选择器
React 选择器是实验性的,其前缀为
_
。该功能将来可能会发生变化。
React 选择器允许按组件名称和属性值选择元素。该语法与所有属性选择器操作符非常相似属性选择器,并且支持所有属性选择器操作符。
在 React 选择器中,组件名称使用驼峰式命名(CamelCase)格式。
选择器示例:
- 按组件(Component)名称匹配:
_react=BookItem
- 按组件名称和准确的属性值匹配,区分大小写:
_react=BookItem[author = "Steven King"]
- 按不区分大小写的属性值匹配:
_react=[author = "steven king" i]
- 按组件和属性值匹配:
_react=MyButton[enabled]
- 按组件和指定的布尔值(Boolean)匹配:
_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 元素名称,请使用React 开发工具。
React 选择器支持 React 15 及以上版本。
React 选择器,以及React 开发工具,只适用于未编译为最小包的应用程序。
Vue 选择器
Vue 选择器是实验性的,其前缀为
_
。该功能将来可能会发生变化。
Vue 选择器允许通过组件名称和属性值来选择元素。该语法与所有属性选择器操作符非常相似属性选择器,并且支持所有属性选择器操作符。
在 Vue 选择器中,组件名称使用连字符-
间隔式命名(kebab-case)格式。
选择器示例:
- 按组件名称(Component)匹配:
_vue=book-item
- 按组件和准确的属性值匹配,区分大小写:
_vue=book-item[author = "Steven King"]
- 按不区分大小写的属性值匹配:
_vue=[author = "steven king" i]
- 按组件和属性值匹配:
_vue=my-button[enabled]
- 按组件和指定的布尔值(Boolean)匹配:
_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 元素名称,请使用Vue 开发工具。
Vue 选择器支持 Vue2 及更高版本。
Vue 选择器,以及Vue 开发工具,只适用于未最小化构建的应用程序。
id
, data-testid
, data-test-id
, data-test
选择器
Playwright支持使用某些属性选择元素的简写。目前,仅支持以下属性:
id
data-testid
data-test-id
data-test
// Fill an input with the id "username"
await page.fill('id=username', 'value');
// Click an element with data-test-id "submit"
await page.click('data-test-id=submit');
# Fill an input with the id "username"
page.fill('id=username', 'value')
# Click an element with data-test-id "submit"
page.click('data-test-id=submit')
属性选择器不是 CSS 选择器,因此不支持任何特定
:enabled
于 CSS 的东西。有关更多功能,请使用适当的 CSS 选择器,例如css=[data-test="login"]:enabled
属性选择器穿透Shadown DOM.要选择退出此行为,
:light
请在属性后使用后缀,例如page.click('data-test-id:light=submit')
从查询结果中选择第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.click(':nth-match(:text("Buy"), 3)');
# Click the third "Buy" button
page.click(':nth-match(:text("Buy"), 3)')
使用 Page.WaitForSelector(Selector[,Options])等待指定数量的元素出现 :nth-match()
也很有用。
// Wait until all three buttons are visible
await page.waitForSelector(':nth-match(:text("Buy"), 3)');
# Wait until all three buttons are visible
page.waitForSelector(':nth-match(:text("Buy"), 3)')
与
:nth-child()
不同,元素不必是同级,它们可以位于页面上的任何位置。在上面的代码片段中,所有三个按钮都匹配:text("Buy")
选择器,并:nth-match()
选择第三个按钮。通常可以通过某些属性或文本内容来区分元素。在这种情况下,最好使用 文本 或 CSS 选择器,而不是
:nth-match()
。
链式选择器
多个选择器可以用>>
字符拼接,例如 selector1 >> selector2 >> selectors3
。当选择器被链接时,会相对于前一个选择器的结果来查询下一个选择器。
例如,
css=article >> css=.bar > .baz >> css=span[attr=value]
相当于JavaScript中DOM操作:
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]')
如果选择器需要包含 >>
在主体中,则应在字符串中进行转义,以免与链接分隔符混淆,例如 text="some >> text"
。
中间匹配
默认情况下,链式选择器返回的是由最后一个选择器查询到的元素。但是也可以为选择器添加前缀*
,来捕获并返回中间的选择器查询到元素。
例如,css=article >> text=Hello
捕获带有文本 Hello
的元素,并 *css=article >> text=Hello
(注意 *
)捕获包含带有文本 Hello
的某些元素的 article
元素。
最佳实践
选择器的选择决定了自动化脚本的弹性。为了减少维护负担,我们建议优先考虑面向用户的属性和测试属性。
优先考虑面向用户的属性
文本内容、输入占位符、Accessibility Roles和标签等属性是面向用户的属性,很少更改。这些属性不受 DOM 结构更改的影响。
// queries "Login" text selector
await page.click('text="Login"');
await page.click('"Login"'); // short-form
// queries "Search GitHub" placeholder attribute
await page.fill('css=[placeholder="Search GitHub"]', 'query');
await page.fill('[placeholder="Search GitHub"]', 'query'); // short-form
// queries "Close" accessibility label
await page.click('css=[aria-label="Close"]');
await page.click('[aria-label="Close"]'); // short-form
// combine role and text queries
await page.click('css=nav >> text=Login');
# queries "Login" text selector
page.click('text="Login"')
page.click('"Login"') # short-form
# queries "Search GitHub" placeholder attribute
page.fill('css=[placeholder="Search GitHub"]', 'query')
page.fill('[placeholder="Search GitHub"]', 'query') # short-form
# queries "Close" accessibility label
page.click('css=[aria-label="Close"]')
page.click('[aria-label="Close"]') # short-form
# combine role and text queries
page.click('css=nav >> text=Login')
需要注意的是,如果应用涉及到多语言的使用场景,文本内容就不应该作为一个理想的选择器方案了。
定义测试属性
当面向用户的属性频繁更改时,建议使用测试属性ID,如 data-testid
、data-test-id
、data-test
。CSS选择器支持这些 data-*
属性。这块通常需要与开发人员沟通来为被测应用暴露或引入这些测试属性。
并且,这些测试属性在录制时会优先作为选择器,否则如果目标元素既没有id
也没有上述测试属性,就会尝试使用文本选择器或较长的CSS选择器。
<button data-test-id="directions">Itinéraire</button>
// queries data-test-id attribute with css
await page.click('css=[data-test-id=directions]');
await page.click('[data-test-id=directions]'); // short-form
// queries data-test-id with id
await page.click('data-test-id=directions');
# queries data-test-id attribute with css
page.click('css=[data-test-id=directions]')
page.click('[data-test-id=directions]') # short-form
# queries data-test-id with id
page.click('data-test-id=directions')
选择器应当避免与结构相关
XPath 和 CSS 可以绑定到 DOM 结构或实现。当 DOM 结构发生变化时,这些选择器可能会中断。
// avoid long css or xpath chains
await page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input');
await page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input');
# avoid long css or xpath chains
page.click('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input')
page.click('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input')
诊断选择器
在以下情况中,你可能希望能够选择更合适的选择器,或修复选择器中的错误:
- 选择器经常超时:最常见的情况,发生的原因也很多;
- 选择器过长:通常是文本选择器生成了大段文本,或使用了CSS链式选择器;
- 选择器不易读:通常是元素的可用属性太少,导致需要使用多层选择器来描述元素位置。
下面将从实践经验的角度介绍如何生成和诊断脚本中的选择器,从而顺利的完成Web自动化工作。
生成选择器
首先我们需要了解常用的几种生成选择器的途径。
通过录制生成选择器 最常用也是最方便的生成途径,只需要在CukeTest中启动Web录制,就能够生成每一步操作的脚本以及被操作元素的选择器。当然缺点就是这些选择器只能保证在被录制的流程中可用,如果修改了脚本使得流程操作改变,有可能导致选择器失效。
比如,操作1输入了用户名
"user1"
,操作2点击了输入框生成了选择器text=user1
。那么如果后续将操作1改为输入其它用户名,操作2的选择器就会失效,因为"user1"
并不存在与输入框中。通过CukeTest侦测器生成选择器 在录制过程中,如果希望调试某个选择器或为某个元素生成选择器,可以使用CukeTest侦测器,侦测器支持以上所有选择器语法,能够生成与录制过程中相同的选择器。
- 通过浏览器开发者工具生成选择器
另外一种简单的方式就是直接使用浏览器的开发者工具,这种方式只能生成和调试Web原生的选择器语法(即不支持[文本选择器]和
nth
选择器等)。在浏览器开发者工具中调试选择器的方式可以参考开发者工具调试选择器。如果希望在Electron应用中使用开发者工具,需要开发提供相应的支持,参考Electron开发者工具扩展。
调试选择器
如果选择器没有正确匹配到元素,也不会立刻抛错,绝大部分情况下都会等待直到超时才会退出。这是因为Playwright有自动等待的机制,因此即使选择器没有匹配到任何元素,依然会在超时时间内反复的查询是否有匹配的元素出现。
如果是单纯的选择器存在语法错误,那么在运行到该选择器对应的脚本时会立刻抛错,这个时候跟着错误提示修正即可。
因此下面通过分析几种常见情况出现选择器匹配元素超时的情况,来帮助你了解调试选择器的流程。
- 选择器包含了易变内容,从而导致本次回放的环境与录制时的/上一次或更久以前的环境不同
- 选择器匹配到不正确的元素,从而导致了自动化没有带来正确的结果。
- 选择器匹配到多个元素,并且第一个元素不为目标元素,常见于将补录的脚本插入到现有的脚本中时出现。由于录制时,自动生成的选择器不总是能准确匹配到目标元素,也会生成目标元素正好为第一个结果的简短选择器,这种选择器在其它脚本中使用时则不一定有效,因为第一个结果不再一定是目标元素。可以使用
element.screenshot()
来将目标元素截图下来分析。 - 选择器匹配到了不可见的元素。这种情况通常是上一种情况的延伸,当错误匹配到的元素处在隐藏状态时,Playwright会一直等待到其出现为止,因此会带来超时。这种情况可以为当前选择器加上一个筛选可见选择器来忽略掉不可见的元素。
- 选择器匹配到多个元素,并且第一个元素不为目标元素,常见于将补录的脚本插入到现有的脚本中时出现。由于录制时,自动生成的选择器不总是能准确匹配到目标元素,也会生成目标元素正好为第一个结果的简短选择器,这种选择器在其它脚本中使用时则不一定有效,因为第一个结果不再一定是目标元素。可以使用