定位器
简介
Locator 是 Playwright 自动等待和重试功能的核心部分。简而言之,定位器表示一种在任何时刻查找页面上元素(或元素集)的方法。
快速指南
以下是推荐的内置定位器。
- Page.GetByRole() 用于根据显式和隐式辅助功能属性进行定位。
- Page.GetByText() 用于根据文本内容进行定位。
- Page.GetByLabel() 用于根据关联标签的文本定位表单控件。
- Page.GetByPlaceholder() 用于根据占位符定位输入框。
- Page.GetByAltText() 用于根据备用文本定位元素,通常是图像。
- Page.GetByTitle() 用于根据 title 属性定位元素。
- Page.GetByTestId() 用于根据其
data-testid
属性定位元素(可以配置其他属性)。
await page.GetByLabel("User Name").FillAsync("John");
await page.GetByLabel("Password").FillAsync("secret-password");
await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
await Expect(Page.GetByText("Welcome, John!")).ToBeVisibleAsync();
定位元素
Playwright 带有多个内置定位器。为了使测试具有弹性,我们建议优先使用面向用户的属性和显式约定,例如 Page.GetByRole()。
例如,考虑以下 DOM 结构。
<button>Sign in</button>
根据名为“登录”的 button
角色定位元素。
await page.GetByRole(AriaRole.Button, new() { Name = "Sign in" }).ClickAsync();
使用 代码生成器 生成定位器,然后根据需要进行编辑。
每次将定位器用于操作时,都会在页面中定位最新的 DOM 元素。在下面的代码段中,底层 DOM 元素将被定位两次,在每次操作之前各一次。这意味着如果由于重新渲染导致 DOM 在调用之间发生变化,则将使用对应于定位器的新的元素。
var locator = page.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
await locator.HoverAsync();
await locator.ClickAsync();
请注意,所有创建定位器的方法(例如 Page.GetByLabel())在 Locator 和 FrameLocator 类中也可用,因此您可以将它们链接起来并迭代地缩小定位器的范围。
var locator = page
.FrameLocator("#my-frame")
.GetByRole(AriaRole.Button, new() { Name = "Sign in" });
await locator.ClickAsync();
按角色定位
Page.GetByRole() 定位器反映了用户和辅助技术如何感知页面,例如某个元素是按钮还是复选框。当按角色定位时,通常也应该传递可访问名称,以便定位器能够精确定位元素。
例如,考虑以下 DOM 结构。
注册
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
您可以根据每个元素的隐式角色对其进行定位
await Expect(Page
.GetByRole(AriaRole.Heading, new() { Name = "Sign up" }))
.ToBeVisibleAsync();
await page
.GetByRole(AriaRole.Checkbox, new() { Name = "Subscribe" })
.CheckAsync();
await page
.GetByRole(AriaRole.Button, new() {
NameRegex = new Regex("submit", RegexOptions.IgnoreCase)
})
.ClickAsync();
角色定位器包括 按钮、复选框、标题、链接、列表、表格等等,并遵循 W3C 关于 ARIA 角色、ARIA 属性 和 可访问名称 的规范。请注意,许多 HTML 元素(如 <button>
)具有 隐式定义的角色,角色定位器可以识别这些角色。
请注意,角色定位器**不能取代**辅助功能审计和一致性测试,而是提供有关 ARIA 指南的早期反馈。
我们建议优先使用角色定位器来定位元素,因为这是最接近用户和辅助技术感知页面方式的方法。
按标签定位
大多数表单控件通常都有专用的标签,可以方便地用于与表单交互。在这种情况下,您可以使用 Page.GetByLabel() 根据其关联标签定位控件。
例如,考虑以下 DOM 结构。
<label>Password <input type="password" /></label>
根据标签文本定位后,您可以填充输入框
await page.GetByLabel("Password").FillAsync("secret");
在定位表单字段时使用此定位器。
按占位符定位
输入框可能具有占位符属性,以提示用户应输入什么值。您可以使用 Page.GetByPlaceholder() 定位此类输入框。
例如,考虑以下 DOM 结构。
<input type="email" placeholder="[email protected]" />
根据占位符文本定位后,您可以填充输入框
await page
.GetByPlaceholder("[email protected]")
.FillAsync("[email protected]");
在定位没有标签但有占位符文本的表单元素时使用此定位器。
按文本定位
根据元素包含的文本查找元素。在使用 Page.GetByText() 时,您可以根据子字符串、精确字符串或正则表达式进行匹配。
例如,考虑以下 DOM 结构。
<span>Welcome, John</span>
您可以根据元素包含的文本对其进行定位
await Expect(Page.GetByText("Welcome, John")).ToBeVisibleAsync();
设置精确匹配
await Expect(Page
.GetByText("Welcome, John", new() { Exact = true }))
.ToBeVisibleAsync();
使用正则表达式匹配
await Expect(Page
.GetByText(new Regex("welcome, john", RegexOptions.IgnoreCase)))
.ToBeVisibleAsync();
按文本匹配始终会规范化空格,即使是精确匹配也是如此。例如,它将多个空格转换为一个空格,将换行符转换为空格,并忽略前导和尾随空格。
我们建议使用文本定位器查找非交互式元素,例如 div
、span
、p
等。对于交互式元素(如 button
、a
、input
等),请使用 角色定位器。
您还可以 按文本筛选,这在尝试查找列表中的特定项目时非常有用。
按备用文本定位
所有图像都应具有描述图像的 alt
属性。您可以使用 Page.GetByAltText() 根据备用文本定位图像。
例如,考虑以下 DOM 结构。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
根据备用文本定位后,您可以点击图像
await page.GetByAltText("playwright logo").ClickAsync();
如果您的元素支持备用文本(例如 img
和 area
元素),请使用此定位器。
按标题定位
使用 Page.GetByTitle() 定位具有匹配 title 属性的元素。
例如,考虑以下 DOM 结构。
<span title='Issues count'>25 issues</span>
根据标题文本定位后,您可以检查问题数量
await Expect(Page.GetByTitle("Issues count")).toHaveText("25 issues");
如果您的元素具有 title
属性,请使用此定位器。
按测试 ID 定位
通过测试 ID 进行测试是最可靠的测试方式,即使您的文本或属性的角色发生变化,测试仍然会通过。QA 和开发人员应该定义明确的测试 ID,并使用 Page.GetByTestId() 查询它们。但是,通过测试 ID 进行的测试对用户不可见。如果角色或文本值对您很重要,请考虑使用面向用户的定位器,例如 角色 和 文本定位器。
例如,考虑以下 DOM 结构。
<button data-testid="directions">Itinéraire</button>
您可以通过其测试 ID 定位元素。
await page.GetByTestId("directions").ClickAsync();
设置自定义测试 ID 属性
默认情况下,Page.GetByTestId() 将根据 data-testid
属性定位元素,但您可以在测试配置中或通过调用 Selectors.SetTestIdAttribute() 来配置它。
设置测试 ID 以使用自定义数据属性进行测试。
playwright.Selectors.SetTestIdAttribute("data-pw");
在您的 html 中,您现在可以使用 data-pw
作为您的测试 ID,而不是默认的 data-testid
。
<button data-pw="directions">Itinéraire</button>
然后像往常一样定位元素。
await page.GetByTestId("directions").ClickAsync();
通过 CSS 或 XPath 定位
如果您绝对必须使用 CSS 或 XPath 定位器,可以使用 Page.Locator() 创建一个定位器,该定位器采用一个选择器来描述如何在页面中查找元素。Playwright 支持 CSS 和 XPath 选择器,如果您省略 css=
或 xpath=
前缀,它会自动检测它们。
await page.Locator("css=button").ClickAsync();
await page.Locator("xpath=//button").ClickAsync();
await page.Locator("button").ClickAsync();
await page.Locator("//button").ClickAsync();
XPath 和 CSS 选择器可以绑定到 DOM 结构或实现。当 DOM 结构发生变化时,这些选择器可能会失效。以下是导致测试不稳定的不良做法示例,即过长的 CSS 或 XPath 链。
await page.Locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").ClickAsync();
await page.Locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").ClickAsync();
在 Shadow DOM 中定位
Playwright 中的所有定位器默认情况下都适用于 Shadow DOM 中的元素。例外情况是
- 通过 XPath 定位不会穿透阴影根。
- 封闭模式阴影根 不受支持。
考虑以下使用自定义 Web 组件的示例。
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
您可以像阴影根不存在一样进行定位。
要点击 <div>Details</div>
await page.GetByText("Details").ClickAsync();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
要点击 <x-details>
await page
.Locator("x-details", new() { HasText = "Details" })
.ClickAsync();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
要确保 <x-details>
包含文本“Details”
await Expect(Page.Locator("x-details")).ToContainTextAsync("Details");
筛选定位器
考虑以下 DOM 结构,我们希望点击第二个产品卡片上的购买按钮。为了筛选定位器以获取正确的定位器,我们有几个选项。
产品 1
产品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
按文本筛选
可以使用 Locator.Filter() 方法按文本筛选定位器。它将在元素内部(可能在子元素中)搜索特定字符串,不区分大小写。您还可以传递正则表达式。
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
使用正则表达式
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasTextRegex = new Regex("Product 2") })
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
按不包含文本筛选
或者,按不包含文本筛选。
// 5 in-stock items
await Expect(Page.getByRole(AriaRole.Listitem).Filter(new() { HasNotText = "Out of stock" }))
.ToHaveCountAsync(5);
按子元素/后代元素筛选
定位器支持一个选项,仅选择具有或不具有与另一个定位器匹配的后代元素的元素。因此,您可以按任何其他定位器进行筛选,例如 Locator.GetByRole()、Locator.GetByTestId()、Locator.GetByText() 等。
产品 1
产品 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() {
Name = "Product 2"
})
})
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
我们还可以断言产品卡片以确保只有一个。
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
筛选定位器必须相对于原始定位器,并且从原始定位器匹配开始查询,而不是从文档根开始。因此,以下方法将不起作用,因为筛选定位器从 <ul>
列表元素(位于原始定位器匹配的 <li>
列表项外部)开始匹配。
// ✖ WRONG
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
Has = page.GetByRole(AriaRole.List).GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
按不包含子元素/后代元素筛选
我们还可以按不包含匹配元素进行筛选。
await Expect(Page
.GetByRole(AriaRole.Listitem)
.Filter(new() {
HasNot = page.GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
}))
.ToHaveCountAsync(1);
请注意,内部定位器是从外部定位器开始匹配的,而不是从文档根开始。
定位器运算符
在定位器内部匹配
您可以将创建定位器的方法(如 Page.GetByText() 或 Locator.GetByRole())链接起来,以缩小对页面特定部分的搜索范围。
在此示例中,我们首先通过定位其 listitem
角色创建一个名为 product 的定位器。然后按文本筛选。我们可以再次使用 product 定位器通过按钮的角色获取并点击它,然后使用断言来确保只有一个产品包含文本“产品 2”。
var product = page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "Product 2" });
await product
.GetByRole(AriaRole.Button, new() { Name = "Add to cart" })
.ClickAsync();
您还可以将两个定位器链接在一起,例如在特定对话框内查找“保存”按钮。
var saveButton = page.GetByRole(AriaRole.Button, new() { Name = "Save" });
// ...
var dialog = page.GetByTestId("settings-dialog");
await dialog.Locator(saveButton).ClickAsync();
同时匹配两个定位器
方法 Locator.And() 通过匹配附加定位器来缩小现有定位器的范围。例如,您可以将 Page.GetByRole() 和 Page.GetByTitle() 组合起来,以同时匹配角色和标题。
var button = page.GetByRole(AriaRole.Button).And(page.GetByTitle("Subscribe"));
匹配两个备选定位器之一
如果您想定位两个或多个元素之一,并且您不知道将是哪个元素,请使用 Locator.Or() 创建一个匹配所有备选方案的定位器。
例如,考虑以下场景:您想点击“新电子邮件”按钮,但有时安全设置对话框会显示出来。在这种情况下,您可以等待“新电子邮件”按钮或对话框,然后相应地采取行动。
如果“新电子邮件”按钮和安全对话框都显示在屏幕上,“或”定位器将匹配两者,这可能会引发 “严格模式违规”错误。在这种情况下,可以使用 Locator.First 只匹配其中之一。
var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" });
var dialog = page.GetByText("Confirm security settings");
await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync();
if (await dialog.IsVisibleAsync())
await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync();
await newEmail.ClickAsync();
仅匹配可见元素
通常最好找到一种更可靠的方法来唯一标识元素,而不是检查可见性。
考虑一个页面,其中有两个按钮,第一个不可见,第二个 可见。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
这将找到这两个按钮并引发 严格性 违规错误。
await page.Locator("button").ClickAsync();
-
这将只找到第二个按钮,因为它可见,然后点击它。
await page.Locator("button").Locator("visible=true").ClickAsync();
列表
计算列表中的项目数
您可以断言定位器以计算列表中的项目数。
例如,考虑以下 DOM 结构。
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用计数断言以确保列表中有 3 个项目。
await Expect(Page.GetByRole(AriaRole.Listitem)).ToHaveCountAsync(3);
断言列表中的所有文本
您可以断言定位器以查找列表中的所有文本。
例如,考虑以下 DOM 结构。
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用 Expect(Locator).ToHaveTextAsync() 以确保列表包含文本“苹果”、“香蕉”和“橙子”。
await Expect(Page
.GetByRole(AriaRole.Listitem))
.ToHaveTextAsync(new string[] {"apple", "banana", "orange"});
获取特定项目
有很多方法可以获取列表中的特定项目。
按文本获取
使用 Page.GetByText() 方法通过其文本内容在列表中定位元素,然后单击它。
例如,考虑以下 DOM 结构。
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
通过其文本内容定位项目并单击它。
await page.GetByText("orange").ClickAsync();
按文本筛选
使用 Locator.Filter() 在列表中定位特定项目。
例如,考虑以下 DOM 结构。
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
通过“listitem”的角色定位项目,然后按“橙子”的文本筛选,然后单击它。
await page
.GetByRole(AriaRole.Listitem)
.Filter(new() { HasText = "orange" })
.ClickAsync();
按测试 ID 获取
使用 Page.GetByTestId() 方法在列表中定位元素。如果您还没有测试 ID,则可能需要修改 html 并添加一个测试 ID。
例如,考虑以下 DOM 结构。
- 苹果
- 香蕉
- 橙子
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>
通过“orange”的测试 ID 定位项目,然后单击它。
await page.GetByTestId("orange").ClickAsync();
按第 n 个项目获取
如果您有一系列相同的元素,并且唯一区分它们的方法是顺序,则可以使用 Locator.First、Locator.Last 或 Locator.Nth() 从列表中选择特定元素。
var banana = await page.GetByRole(AriaRole.Listitem).Nth(1);
但是,请谨慎使用此方法。页面可能会发生变化,定位器可能会指向与您期望的完全不同的元素。相反,尝试找到一个唯一的定位器,它将通过 严格性标准。
链接过滤器
当您有多个具有各种相似性的元素时,您可以使用 Locator.Filter() 方法选择正确的元素。您还可以链接多个过滤器来缩小选择范围。
例如,考虑以下 DOM 结构。
- John
- Mary
- John
- Mary
<ul>
<li>
<div>John</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>John</div>
<div><button>Say goodbye</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say goodbye</button></div>
</li>
</ul>
要截取包含“Mary”和“Say goodbye”的行截图
var rowLocator = page.GetByRole(AriaRole.Listitem);
await rowLocator
.Filter(new() { HasText = "Mary" })
.Filter(new() {
Has = page.GetByRole(AriaRole.Button, new() { Name = "Say goodbye" })
})
.ScreenshotAsync(new() { Path = "screenshot.png" });
您现在应该在项目的根目录中有一个“screenshot.png”文件。
罕见用例
对列表中的每个元素执行操作
迭代元素
foreach (var row in await page.GetByRole(AriaRole.Listitem).AllAsync())
Console.WriteLine(await row.TextContentAsync());
使用常规 for 循环迭代
var rows = page.GetByRole(AriaRole.Listitem);
var count = await rows.CountAsync();
for (int i = 0; i < count; ++i)
Console.WriteLine(await rows.Nth(i).TextContentAsync());
在页面中评估
Locator.EvaluateAllAsync() 内部的代码在页面中运行,您可以在其中调用任何 DOM API。
var rows = page.GetByRole(AriaRole.Listitem);
var texts = await rows.EvaluateAllAsync(
"list => list.map(element => element.textContent)");
严格性
定位器是严格的。这意味着所有暗示某些目标 DOM 元素的定位器操作,如果有多个元素匹配,则会抛出异常。例如,如果 DOM 中有多个按钮,则以下调用会抛出异常
如果有多个元素则抛出错误
await page.GetByRole(AriaRole.Button).ClickAsync();
另一方面,当您执行多元素操作时,Playwright 会理解,因此当定位器解析为多个元素时,以下调用可以完美运行。
可与多个元素一起正常使用
await page.GetByRole(AriaRole.Button).CountAsync();
您可以通过告诉 Playwright 当多个元素匹配时使用哪个元素来明确选择退出严格性检查,方法是使用 Locator.First、Locator.Last 和 Locator.Nth()。**不建议**使用这些方法,因为当您的页面发生更改时,Playwright 可能会点击您未打算点击的元素。相反,请遵循以上最佳实践来创建唯一标识目标元素的定位器。
更多定位器
对于不太常用的定位器,请查看 其他定位器 指南。