其它定位器

简介

查看主要的定位符指南,了解最常用和推荐的定位符。

除了推荐的定位符,如 page.getByRole()page.getByText(),Playwright 还支持本指南中描述的各种其他定位符。

CSS定位器

我们建议优先使用用户可见的定位符,如文本或可访问的角色,而不是使用与实现相关的 CSS 选择器,因为后者在页面发生变化时可能会失效。

Playwright 可以通过 CSS 选择器定位元素。

await page.locator('css=button').click();

Playwright 在两方面增强了标准的 CSS 选择器:

  1. CSS 选择器可以穿透 Shadow DOM。

  2. 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")),很可能匹配到的不是你想要的输入框,而是文本与目标输入框之间的某个空元素。

布局伪类使用边界矩形(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>,或者一个已经滚动出视口并且目前不可见的元素。

// 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" 的标签的父元素 <li>,使用 locator.filter() 是最有效的方式:

const child = page.getByText('Hello');
const parent = page.getByRole('listitem').filter({ has: child });

另外,如果无法找到适合的父元素定位器,可以使用 xpath=…​。请注意,这种方法不如 locator.filter() 可靠,因为 DOM 结构的任何变化都可能导致测试失败。因此,尽量使用 locator.filter()

const parent = page.getByText('Hello').locator('xpath=..');

React 定位器

React 定位器是实验性的,并以 _ 为前缀。其功能在未来可能会发生变化。

React 定位器允许通过组件名称和属性值查找元素。语法与 CSS 属性选择器非常相似,并支持所有 CSS 属性选择器运算符。

在 React 定位器中,组件名称使用驼峰命名法。

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 容易在页面变化时中断。

XPath 定位器相当于调用 Document.evaluate

await page.locator('xpath=//button').click();

任何以 //.. 开头的选择器字符串都被认为是 XPath 选择器。例如,Playwright 会将 //html/body 转换为 xpath=//html/body

XPath 不会穿透 Shadow 根节点。

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">。你可以通过其 "Password" 文本使用 page.getByText() 来定位标签。然而,以下操作将会在输入控件上执行,而不是标签上:

  • 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"

匹配总是会规范化空白字符。例如,它将多个空格转换为一个空格,将换行符转换为空格,并忽略前导和尾随的空白字符。

类型为 buttonsubmit 的输入元素是通过它们的值而不是文本内容来匹配的。例如,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 选择器,因此像 :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 元素。