定位器 (Locators)
Locator 是 Playwright 自动等待和重试能力的核心部分。简而言之,定位器代表了一种随时在页面上查找元素的方法。
Quick Guide
这些是推荐的内置定位器。
- page.get_by_role(role, **kwargs) 通过显式和隐式可访问性属性进行定位。
- page.get_by_text(text, **kwargs) 通过文本内容进行定位。
- page.get_by_label(text, **kwargs) 通过关联标签的文本定位表单控件。
- page.get_by_placeholder(text, **kwargs) 通过占位符定位输入。
- page.get_by_alt_text(text, **kwargs) 通过替代文本定位元素(通常是图像)。
- page.get_by_title(text, **kwargs) 通过标题定位元素。
- page.get_by_test_id(test_id) 根据其
data-testid属性定位元素(可以配置其他属性)。
- Sync
- Async
page.get_by_label("User Name").fill("John")
page.get_by_label("Password").fill("secret-password")
page.get_by_role("button", name="Sign in").click()
expect(page.get_by_text("Welcome, John!")).to_be_visible()
await page.get_by_label("User Name").fill("John")
await page.get_by_label("Password").fill("secret-password")
await page.get_by_role("button", name="Sign in").click()
await expect(page.get_by_text("Welcome, John!")).to_be_visible()
每次将定位器用于某些操作时,都会在页面中定位最新的 DOM 元素。因此,在下面的代码片段中,底层 DOM 元素将被定位两次,在每次操作之前。这意味着如果 DOM 在调用之间由于重新渲染而发生变化,则将使用与定位器对应的新元素。
- Sync
- Async
locator = page.get_by_text("Submit")
locator.hover()
locator.click()
locator = page.get_by_text("Submit")
await locator.hover()
await locator.click()
Strictness
定位器是严格的。这意味着如果多个元素匹配给定的选择器,则所有暗示某些目标 DOM 元素的定位器操作都将抛出异常。例如,如果 DOM 中有多个按钮,则以下调用将抛出异常:
- Sync
- Async
page.get_by_role("button").click()
await page.get_by_role("button").click()
另一方面,Playwright 了解您何时执行多元素操作,因此当定位器解析为多个元素时,以下调用可以完美运行。
- Sync
- Async
page.get_by_role("button").count()
await page.get_by_role("button").count()
您可以通过 locator.first、locator.last 和 locator.nth(index) 告诉 Playwright 当多个元素匹配时使用哪个元素,从而显式选择退出严格性检查。这些方法 不推荐,因为当您的页面更改时,Playwright 可能会点击您不打算点击的元素。相反,请遵循以下最佳实践来创建一个唯一标识目标元素的定位器。
Locating elements
Playwright 提供了多种内置方法来创建定位器。为了使测试具有弹性,我们建议优先考虑面向用户的属性和显式契约,并为它们提供专用方法,例如 page.get_by_text(text, **kwargs)。使用 代码生成器 生成定位器,然后根据需要进行编辑通常很方便。
- Sync
- Async
page.get_by_text("Log in").click()
await page.get_by_text("Log in").click()
如果您绝对必须使用 CSS 或 XPath 定位器,可以使用 page.locator(selector, **kwargs) 创建一个定位器,该定位器接受描述如何在页面中查找元素的 选择器。
请注意,所有创建定位器的方法,例如 page.get_by_label(text, **kwargs),也可在 Locator 和 FrameLocator 类上使用,因此您可以链接它们并迭代地缩小定位器范围。
- Sync
- Async
locator = page.frame_locator("my-frame").get_by_text("Submit")
locator.click()
locator = page.frame_locator("#my-frame").get_by_text("Submit")
await locator.click()
Locate based on accessible attributes
page.get_by_role(role, **kwargs) 定位器反映了用户和辅助技术如何感知页面,例如某个元素是按钮还是复选框。按角色定位时,通常还应传递可访问名称,以便定位器精确定位确切的元素。
- Sync
- Async
page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
page.get_by_role("checkbox", checked=True, name="Check me").check()
await page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
await page.get_by_role("checkbox", checked=True, name="Check me").check()
角色定位器遵循 ARIA 角色、ARIA 属性 和 可访问名称 的 W3C 规范。
请注意,角色定位器 不能替代 可访问性审核和一致性测试,而是提供有关 ARIA 指南的早期反馈。
Locate by label text
大多数表单控件通常具有专用标签,可以方便地用于与表单交互。在这种情况下,您可以使用 page.get_by_label(text, **kwargs) 通过其关联标签定位控件。
例如,考虑以下 DOM 结构。
<label for="password">Password:</label><input type="password" id="password">
您可以在通过标签文本定位输入后填写它:
- Sync
- Async
page.get_by_label("Password").fill("secret")
await page.get_by_label("Password").fill("secret")
Locate by placeholder text
输入可能具有占位符属性,以向用户提示应输入什么值。您可以使用 page.get_by_placeholder(text, **kwargs) 定位此类输入。
例如,考虑以下 DOM 结构。
<input id="email" name="email" type="email" placeholder="name@example.com">
您可以在通过占位符文本定位输入后填写它:
- Sync
- Async
page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
await page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
Locate by text
查找元素的最简单方法是查找它包含的文本。使用 page.get_by_text(text, **kwargs) 时,您可以按子字符串、精确字符串或正则表达式进行匹配。
- Sync
- Async
page.get_by_text("Log in").click()
page.get_by_text("Log in", exact=True).click()
page.get_by_text(re.compile("Log in", re.IGNORECASE)).click()
await page.get_by_text("Log in").click()
await page.get_by_text("Log in", exact=True).click()
await page.get_by_text(re.compile("Log in", re.IGNORECASE)).click()
您还可以在以其他方式定位时 按文本过滤,例如在列表中查找特定项目。
- Sync
- Async
page.get_by_test_id("product-item").filter(has_text="Playwright Book").click()
await page.get_by_test_id("product-item").filter(has_text="Playwright Book").click()
按文本匹配总是规范化空白,即使是精确匹配也是如此。例如,它将多个空格转换为一个,将换行符转换为空格,并忽略前导和尾随空白。
Locate by alt text
所有图像都应具有描述图像的 alt 属性。您可以使用 page.get_by_alt_text(text, **kwargs) 根据替代文本定位图像。
例如,考虑以下 DOM 结构。
<img alt="playwright logo" src="/playwright-logo.png" />
您可以在通过替代文本定位图像后点击它:
- Sync
- Async
page.get_by_alt_text("playwright logo").click()
await page.get_by_alt_text("playwright logo").click()
Locate by title
使用 page.get_by_title(text, **kwargs) 定位具有匹配 title 属性的元素。
例如,考虑以下 DOM 结构。
<span title='Issues count'>25 issues</span>
您可以在通过标题文本定位后检查问题计数:
- Sync
- Async
expect(page.get_by_title("Issues count")).to_have_text("25 issues")
await expect(page.get_by_title("Issues count")).to_have_text("25 issues")
Define explicit contract and use a data-testid attribute
面向用户的属性(如文本或可访问名称)可能会随时间而变化。在这种情况下,定义显式测试 ID 并使用 page.get_by_test_id(test_id) 查询它们很方便。
<button data-testid="directions">Itinéraire</button>
- Sync
- Async
page.get_by_test_id("directions").click()
await page.get_by_test_id("directions").click()
默认情况下,page.get_by_test_id(test_id) 将根据 data-testid 属性定位元素,但您可以在测试配置中配置它或调用 selectors.set_test_id_attribute(attribute_name)。
Locate in a subtree
您可以链接创建定位器的方法,例如 page.get_by_text(text, **kwargs) 或 locator.get_by_role(role, **kwargs),以将搜索范围缩小到页面的特定部分。
例如,考虑以下 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" 的产品卡,然后单击此特定产品卡中的按钮。
- Sync
- Async
product = page.get_by_test_id("product-card").filter(has_text="Product 2")
product.get_by_text("Buy").click()
product = page.get_by_test_id("product-card").filter(has_text="Product 2")
await product.getByText("Buy").click()
Locate by CSS or XPath selector
Playwright 支持 CSS 和 XPath 选择器,如果您省略 css= 或 xpath= 前缀,则会自动检测它们。为此使用 page.locator(selector, **kwargs):
- Sync
- Async
page.locator("css=button").click()
page.locator("xpath=//button").click()
page.locator("button").click()
page.locator("//button").click()
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 链是导致测试不稳定的 不良实践 的示例:
- Sync
- Async
page.locator("#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input").click()
page.locator("//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input").click()
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()
相反,尝试想出一个接近用户感知页面的定位器,或者 定义显式测试契约。
Locate elements that contain other elements
Filter by text
定位器可以选择按文本过滤。它将在元素内部的某个位置(可能在后代元素中)不区分大小写地搜索特定字符串。您也可以传递正则表达式。
- Sync
- Async
page.get_by_test_id("product-card").filter(has_text="Product 3").click()
page.get_by_test_id("product-card").filter(has_text=re.compile("Product 3")).click()
await page.get_by_test_id("product-card").filter(has_text="Product 3").click()
await page.get_by_test_id("product-card").filter(has_text=re.compile("Product 3")).click()
Filter by another locator
定位器支持一个选项,仅选择具有匹配另一个定位器的后代的元素。
- Sync
- Async
page.get_by_role("section").filter(has=page.get_by_test_id("subscribe-button"))
page.get_by_role("section").filter(has=page.get_by_test_id("subscribe-button"))
请注意,内部定位器是从外部定位器开始匹配的,而不是从文档根目录开始。
Augment an existing locator
您可以使用 locator.filter(**kwargs) 方法按文本或另一个定位器过滤现有定位器,可能多次链接它。
- Sync
- Async
row_locator = page.locator("tr")
# ...
row_locator
.filter(has_text="text in column 1")
.filter(has=page.get_by_role("button", name="column 2 button"))
.screenshot()
row_locator = page.locator("tr")
# ...
await row_locator
.filter(has_text="text in column 1")
.filter(has=page.get_by_role("button", name="column 2 button"))
.screenshot()
Locate elements in Shadow DOM
Playwright 中的所有定位器 默认情况下 都适用于 Shadow DOM 中的元素。例外情况包括:
- 按 XPath 定位不会穿透 shadow root。
- 不支持 闭合模式 shadow root。
考虑以下带有自定义 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 一样进行定位。
Click
<div>Details</div>- Sync
- Async
page.get_by_text("Details").click()await page.get_by_text("Details").click()Click
<x-details>- Sync
- Async
page.locator("x-details", has_text="Details" ).click()await page.locator("x-details", has_text="Details" ).click()Ensure that
<x-details>contains text "Details"- Sync
- Async
expect(page.locator("x-details")).to_contain_text("Details")await expect(page.locator("x-details")).to_contain_text("Details")
Lists
您还可以使用定位器来处理元素列表。
- Sync
- Async
# Locate elements, this locator points to a list.
rows = page.get_by_role("listitem")
# Pattern 1: use locator methods to calculate text on the whole list.
texts = rows.all_text_contents()
# Pattern 2: do something with each element in the list.
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())
# Pattern 3: resolve locator to elements on page and map them to their text content.
# Note: the code inside evaluateAll runs in page, you can call any DOM apis there.
texts = rows.evaluate_all("list => list.map(element => element.textContent)")
# Locate elements, this locator points to a list.
rows = page.get_by_role("listitem")
# Pattern 1: use locator methods to calculate text on the whole list.
texts = await rows.all_text_contents()
# Pattern 2: do something with each element in the list.
count = await rows.count()
for i in range(count):
print(await rows.nth(i).text_content())
# Pattern 3: resolve locator to elements on page and map them to their text content.
# Note: the code inside evaluateAll runs in page, you can call any DOM apis there.
texts = await rows.evaluate_all("list => list.map(element => element.textContent)")
Picking specific element from a list
如果您有一个相同元素的列表,并且区分它们的唯一方法是顺序,则可以使用 locator.first、locator.last 或 locator.nth(index) 从列表中选择特定元素。
例如,要点击产品列表中的第三项:
- Sync
- Async
page.get_by_test_id("product-card").nth(3).click()
await page.get_by_test_id("product-card").nth(3).click()
但是,请谨慎使用这些方法。很多时候,页面可能会发生变化,定位器将指向与您预期的完全不同的元素。相反,尝试想出一个可以通过 严格性标准 的唯一定位器。