Skip to main content

定位器 (Locators)

Locator 是 Playwright 自动等待和重试能力的核心部分。简而言之,定位器代表了一种在任何时刻在页面上查找元素的方法。

快速指南

这些是推荐的内置定位器。

await page.getByLabel('User Name').fill('John');

await page.getByLabel('Password').fill('secret-password');

await page.getByRole('button', { name: 'Sign in' }).click();

await expect(page.getByText('Welcome, John!')).toBeVisible();

每次将定位器用于某些操作时,都会在页面中定位最新的 DOM 元素。因此,在下面的代码段中,底层 DOM 元素将被定位两次,在每次操作之前。这意味着如果 DOM 在调用之间由于重新渲染而发生变化,则将使用与定位器对应的新元素。

const locator = page.getByText('Submit');
// ...
await locator.hover();
await locator.click();

严格性 (Strictness)

定位器是严格的。这意味着如果多个元素匹配给定的选择器,则暗示某些目标 DOM 元素的所有定位器操作都将抛出异常。例如,如果 DOM 中有多个按钮,则以下调用将抛出异常:

await page.getByRole('button').click();

另一方面,Playwright 了解您何时执行多元素操作,因此当定位器解析为多个元素时,以下调用完全正常。

await page.getByRole('button').count();

您可以通过 locator.first()locator.last()locator.nth(index) 告诉 Playwright 在多个元素匹配时使用哪个元素,从而显式选择退出严格性检查。不推荐使用这些方法,因为当您的页面更改时,Playwright 可能会点击您不想要的元素。相反,请遵循以下最佳实践来创建唯一标识目标元素的定位器。

定位元素

Playwright 附带多种内置方法来创建定位器。为了使测试具有弹性,我们建议优先考虑面向用户的属性和显式契约,并为它们提供专用方法,例如 page.getByText(text[, options])。通常使用 代码生成器 生成定位器,然后根据需要对其进行编辑很方便。

await page.getByText('Log in').click();

如果您绝对必须使用 CSS 或 XPath 定位器,可以使用 page.locator(selector[, options]) 创建一个定位器,该定位器采用描述如何在页面中查找元素的 选择器

请注意,所有创建定位器的方法,例如 page.getByLabel(text[, options]),也可以在 LocatorFrameLocator 类上使用,因此您可以链接它们并迭代地缩小定位器范围。

const locator = page.frameLocator('#my-frame').getByText('Submit');
await locator.click();

基于可访问属性定位

page.getByRole(role[, options]) 定位器反映了用户和辅助技术如何感知页面,例如某些元素是按钮还是复选框。当按角色定位时,通常也应该传递可访问名称,以便定位器精确定位确切的元素。

await page.getByRole('button', { name: /submit/i }).click();

await page.getByRole('checkbox', { checked: true, name: "Check me" }).check();

角色定位器遵循 ARIA roleARIA attributesaccessible name 的 W3C 规范。

请注意,角色定位器 不能替代 可访问性审核和一致性测试,而是提供有关 ARIA 准则的早期反馈。

通过标签文本定位

大多数表单控件通常都有专用的标签,可以方便地用于与表单交互。在这种情况下,您可以使用 page.getByLabel(text[, options]) 通过其关联的标签定位控件。

例如,考虑以下 DOM 结构。

<label for="password">Password:</label><input type="password" id="password">

您可以在通过标签文本定位输入后填写它:

await page.getByLabel('Password').fill('secret');

通过占位符文本定位

输入可能具有占位符属性,以向用户提示应输入什么值。您可以使用 page.getByPlaceholder(text[, options]) 定位此类输入。

例如,考虑以下 DOM 结构。

 <input id="email" name="email" type="email" placeholder="name@example.com">

您可以在通过占位符文本定位输入后填写它:

await page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");

通过文本定位

查找元素的最简单方法是查找它包含的文本。使用 page.getByText(text[, options]) 时,您可以按子字符串、精确字符串或正则表达式进行匹配。

await page.getByText('Log in').click();
await page.getByText('Log in', { exact: true }).click();
await page.getByText(/log in$/i).click();

您还可以在以其他方式定位时 按文本过滤,例如在列表中查找特定项目。

await page.getByTestId('product-item').filter({ hasText: 'Playwright Book' }).click();
note

按文本匹配总是规范化空白,即使是精确匹配。例如,它将多个空格转换为一个,将换行符转换为空格,并忽略前导和尾随空格。

通过替代文本 (Alt Text) 定位

所有图像都应具有描述图像的 alt 属性。您可以使用 page.getByAltText(text[, options]) 根据替代文本定位图像。

例如,考虑以下 DOM 结构。

<img alt="playwright logo" src="/playwright-logo.png" />

您可以在通过替代文本定位图像后点击它:

await page.getByAltText('playwright logo').click();

通过标题定位

使用 page.getByTitle(text[, options]) 定位具有匹配 title 属性的元素。

例如,考虑以下 DOM 结构。

<span title='Issues count'>25 issues</span>

您可以在通过标题文本定位后检查问题计数:

await expect(page.getByTitle('Issues count')).toHaveText('25 issues');

定义显式契约并使用 data-testid 属性

面向用户的属性(如文本或可访问名称)可能会随着时间的推移而改变。在这种情况下,定义显式测试 ID 并使用 page.getByTestId(testId) 查询它们很方便。

<button data-testid="directions">Itinéraire</button>
await page.getByTestId('directions').click();

默认情况下,page.getByTestId(testId) 将根据 data-testid 属性定位元素,但您可以在测试配置中配置它,或调用 selectors.setTestIdAttribute(attributeName)

在子树中定位

您可以链接创建定位器的方法,例如 page.getByText(text[, options])locator.getByRole(role[, options]),以将搜索范围缩小到页面的特定部分。

例如,考虑以下 DOM 结构:

<div data-testid='product-card'>
<span>Product 1</span>
<button>Buy</button>
</div>
<div data-testid='product-card'>
<span>Product 2</span>
<button>Buy</button>
</div>

例如,我们可以首先找到包含文本 "Product 2" 的产品卡,然后点击此特定产品卡中的按钮。

const product = page.getByTestId('product-card').filter({ hasText: 'Product 2' });

await product.getByText('Buy').click();

通过 CSS 或 XPath 选择器定位

Playwright 支持 CSS 和 XPath 选择器,如果您省略 css=xpath= 前缀,它会自动检测它们。使用 page.locator(selector[, options]) 来实现这一点:

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

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

XPath 和 CSS 选择器可以绑定到 DOM 结构或实现。当 DOM 结构发生变化时,这些选择器可能会中断。下面的长 CSS 或 XPath 链是导致测试不稳定的 不良做法 的示例:

await page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input').click();

await page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click();

相反,尝试想出一个接近用户感知页面的定位器,或 定义显式测试契约

定位包含其他元素的元素

按文本过滤

定位器可以选择按文本过滤。它将在元素内的某处(可能在后代元素中)不区分大小写地搜索特定字符串。您也可以传递正则表达式。

await page.getByTestId('product-card').filter({ hasText: 'Product 3' }).click();
await page.getByTestId('product-card').filter({ hasText: /product 3/ }).click();

按另一个定位器过滤

定位器支持仅选择具有匹配另一个定位器的后代的元素的选项。

page.getByRole('section').filter({ has: page.getByTestId('subscribe-button') })

请注意,内部定位器是从外部定位器开始匹配的,而不是从文档根目录开始。

增强现有定位器

您可以使用 locator.filter([options]) 方法按文本或另一个定位器过滤现有定位器,可能多次链接它。

const rowLocator = page.locator('tr');
// ...
await rowLocator
.filter({ hasText: 'text in column 1' })
.filter({ has: page.getByRole('button', { name: 'column 2 button' }) })
.screenshot();

在 Shadow DOM 中定位

Playwright 中的所有定位器 默认情况下 都适用于 Shadow DOM 中的元素。例外情况是:

考虑以下带有自定义 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>

您可以像 shadow root 根本不存在一样进行定位。

  • 点击 <div>Details</div>

    await page.getByText('Details').click();
  • 点击 <x-details>

    await page.locator('x-details', { hasText: 'Details' }).click();
  • 确保 <x-details> 包含文本 "Details"

    await expect(page.locator('x-details')).toContainText('Details');

列表

您还可以使用定位器来处理元素列表。

// 定位元素,此定位器指向一个列表。
const rows = page.getByRole('listitem');

// 模式 1:使用定位器方法计算整个列表上的文本。
const texts = await rows.allTextContents();

// 模式 2:对列表中的每个元素执行某些操作。
const count = await rows.count()
for (let i = 0; i < count; ++i)
console.log(await rows.nth(i).textContent());

// 模式 3:将定位器解析为页面上的元素并将它们映射到其文本内容。
// 注意:evaluateAll 内的代码在页面中运行,您可以在那里调用任何 DOM api。
const texts = await rows.evaluateAll(list => list.map(element => element.textContent));

从列表中选择特定元素

如果您有相同的元素列表,并且区分它们的唯一方法是顺序,则可以使用 locator.first()locator.last()locator.nth(index) 从列表中选择特定元素。

例如,要点击产品列表中的第三个项目:

await page.getByTestId('product-card').nth(3).click();

但是,请谨慎使用这些方法。很多时候,页面可能会发生变化,定位器将指向与您预期的完全不同的元素。相反,尝试想出一个通过 严格性标准 的唯一定位器。