定位器
简介
定位器 是 Playwright 自动等待和重试能力的核心部分。简而言之,定位器表示在任何时刻查找页面上的元素(或元素集)的方法。
快速指南
以下是一些推荐的内置定位器。
- page.get_by_role() 用于通过显式和隐式辅助功能属性进行定位。
- page.get_by_text() 用于通过文本内容进行定位。
- page.get_by_label() 用于通过关联标签的文本定位表单控件。
- page.get_by_placeholder() 用于通过占位符定位输入框。
- page.get_by_alt_text() 用于通过替代文本(通常是图像)定位元素。
- page.get_by_title() 用于通过 title 属性定位元素。
- page.get_by_test_id() 用于根据元素的
data-testid
属性(其他属性可以配置)定位元素。
- 同步
- 异步
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()
定位元素
Playwright 带有多个内置定位器。为了使测试更具弹性,我们建议优先考虑面向用户的属性和显式约定,例如 page.get_by_role()。
例如,考虑以下 DOM 结构。
<button>Sign in</button>
通过其名为 "登录" 的 button
角色定位元素。
- 同步
- 异步
page.get_by_role("button", name="Sign in").click()
await page.get_by_role("button", name="Sign in").click()
使用 代码生成器 生成定位器,然后根据需要进行编辑。
每次定位器用于操作时,都会在页面中定位一个最新的 DOM 元素。在下面的代码片段中,底层 DOM 元素将被定位两次,每次操作之前都会定位一次。这意味着如果 DOM 由于重新渲染而在调用之间发生变化,将使用与定位器相对应的新的元素。
- 同步
- 异步
locator = page.get_by_role("button", name="Sign in")
locator.hover()
locator.click()
locator = page.get_by_role("button", name="Sign in")
await locator.hover()
await locator.click()
请注意,所有创建定位器的方法(例如 page.get_by_label())也适用于 定位器 和 FrameLocator 类,因此您可以将它们链接在一起并逐步缩小定位器范围。
- 同步
- 异步
locator = page.frame_locator("my-frame").get_by_role("button", name="Sign in")
locator.click()
locator = page.frame_locator("#my-frame").get_by_role("button", name="Sign in")
await locator.click()
通过角色定位
page.get_by_role() 定位器反映了用户和辅助技术如何感知页面,例如某些元素是按钮还是复选框。通过角色进行定位时,您通常也应该传入可访问名称,以便定位器能够精确定位元素。
例如,考虑以下 DOM 结构。
注册
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
您可以通过其隐式角色来定位每个元素
- 同步
- 异步
expect(page.get_by_role("heading", name="Sign up")).to_be_visible()
page.get_by_role("checkbox", name="Subscribe").check()
page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
await expect(page.get_by_role("heading", name="Sign up")).to_be_visible()
await page.get_by_role("checkbox", name="Subscribe").check()
await page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
角色定位器包括 按钮、复选框、标题、链接、列表、表格和许多其他,并遵循 W3C 针对 ARIA 角色、ARIA 属性 和 可访问名称 的规范。请注意,许多 HTML 元素(如 <button>
)具有 隐式定义的角色,该角色会由角色定位器识别。
请注意,角色定位器 **不能替代** 可访问性审计和一致性测试,而是可以提供有关 ARIA 指南的早期反馈。
我们建议优先使用角色定位器来定位元素,因为它最接近用户和辅助技术感知页面的方式。
通过标签定位
大多数表单控件通常都有专门的标签,可以方便地用于与表单进行交互。在这种情况下,您可以使用 page.get_by_label() 通过关联标签来定位控件。
例如,考虑以下 DOM 结构。
<label>Password <input type="password" /></label>
通过标签文本定位输入框后,您可以填充它。
- 同步
- 异步
page.get_by_label("Password").fill("secret")
await page.get_by_label("Password").fill("secret")
定位表单字段时使用此定位器。
通过占位符定位
输入框可能具有占位符属性,以提示用户应输入什么值。您可以使用 page.get_by_placeholder() 定位此类输入框。
例如,考虑以下 DOM 结构。
<input type="email" placeholder="[email protected]" />
通过占位符文本定位输入框后,您可以填充它。
- 同步
- 异步
page.get_by_placeholder("[email protected]").fill("[email protected]")
await page.get_by_placeholder("[email protected]").fill("[email protected]")
定位没有标签但有占位符文本的表单元素时使用此定位器。
通过文本定位
通过其包含的文本查找元素。使用 page.get_by_text() 时,您可以通过子字符串、精确字符串或正则表达式进行匹配。
例如,考虑以下 DOM 结构。
<span>Welcome, John</span>
您可以通过其包含的文本定位元素
- 同步
- 异步
expect(page.get_by_text("Welcome, John")).to_be_visible()
await expect(page.get_by_text("Welcome, John")).to_be_visible()
设置精确匹配
- 同步
- 异步
expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()
await expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()
通过正则表达式匹配
- 同步
- 异步
expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visible()
await expect(
page.get_by_text(re.compile("welcome, john", re.IGNORECASE))
).to_be_visible()
通过文本匹配始终会规范化空格,即使是精确匹配也是如此。例如,它会将多个空格转换为一个空格,将换行符转换为空格,并忽略前导和尾随空格。
我们建议使用文本定位器查找非交互式元素,如 div
、span
、p
等。对于交互式元素,如 button
、a
、input
等,请使用 角色定位器。
您也可以 按文本筛选,这在尝试查找列表中的特定项目时非常有用。
通过替代文本定位
所有图像都应具有描述图像的 alt
属性。您可以使用 page.get_by_alt_text() 根据替代文本定位图像。
例如,考虑以下 DOM 结构。
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
通过替代文本定位图像后,您可以单击它。
- 同步
- 异步
page.get_by_alt_text("playwright logo").click()
await page.get_by_alt_text("playwright logo").click()
定位支持替代文本的元素(例如 img
和 area
元素)时使用此定位器。
通过标题定位
使用 page.get_by_title() 定位具有匹配 title 属性的元素。
例如,考虑以下 DOM 结构。
<span title='Issues count'>25 issues</span>
通过标题文本定位后,您可以查看问题计数。
- 同步
- 异步
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")
定位具有 title
属性的元素时使用此定位器。
通过测试 ID 定位
通过测试 ID 进行测试是最可靠的方法,即使您的文本或属性角色发生变化,测试仍将通过。QA 和开发人员应该定义显式的测试 ID,并使用 page.get_by_test_id() 查询它们。但是,通过测试 ID 进行测试不面向用户。如果角色或文本值对您很重要,请考虑使用面向用户的定位器,例如 角色 和 文本定位器。
例如,考虑以下 DOM 结构。
<button data-testid="directions">Itinéraire</button>
您可以通过其测试 ID 找到元素
- 同步
- 异步
page.get_by_test_id("directions").click()
await page.get_by_test_id("directions").click()
设置自定义测试 ID 属性
默认情况下,page.get_by_test_id() 将根据 data-testid
属性定位元素,但您可以在测试配置中或通过调用 selectors.set_test_id_attribute() 来配置它。
设置测试 ID 以使用自定义数据属性进行测试。
- 同步
- 异步
playwright.selectors.set_test_id_attribute("data-pw")
playwright.selectors.set_test_id_attribute("data-pw")
在您的 HTML 中,您现在可以使用 data-pw
作为您的测试 ID,而不是默认的 data-testid
。
<button data-pw="directions">Itinéraire</button>
然后像往常一样定位元素
- 同步
- 异步
page.get_by_test_id("directions").click()
await page.get_by_test_id("directions").click()
通过 CSS 或 XPath 定位
如果您必须使用 CSS 或 XPath 定位器,您可以使用 page.locator() 创建一个定位器,该定位器接受一个描述如何在页面中查找元素的选择器。Playwright 支持 CSS 和 XPath 选择器,如果您省略 css=
或 xpath=
前缀,它会自动检测它们。
- 同步
- 异步
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 链是导致测试不稳定的不良做法示例
- 同步
- 异步
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()
在 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>
您可以像 Shadow 根不存在一样定位它。
要单击 <div>Details</div>
- 同步
- 异步
page.get_by_text("Details").click()
await page.get_by_text("Details").click()
<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>
- 同步
- 异步
page.locator("x-details", has_text="Details" ).click()
await page.locator("x-details", has_text="Details" ).click()
<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"
- 同步
- 异步
expect(page.locator("x-details")).to_contain_text("Details")
await expect(page.locator("x-details")).to_contain_text("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() 方法按文本筛选定位器。它将在元素的某个地方(可能是在子元素中)搜索特定字符串,不区分大小写。您还可以传递正则表达式。
- 同步
- 异步
page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()
await page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()
使用正则表达式
- 同步
- 异步
page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()
await page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()
筛选不包含文本的元素
或者,按不包含文本进行筛选
- 同步
- 异步
# 5 in-stock items
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
# 5 in-stock items
await expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
按子元素/后代元素筛选
定位器支持一个选项,用于仅选择具有或没有匹配另一个定位器的后代元素的元素。因此,您可以通过任何其他定位器进行筛选,例如 locator.get_by_role()、locator.get_by_test_id()、locator.get_by_text() 等。
产品 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>
- 同步
- 异步
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
await page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
我们还可以断言产品卡片,以确保只有一个产品卡片
- 同步
- 异步
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
await expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
筛选定位器必须相对于原始定位器,并从原始定位器匹配开始查询,而不是从文档根开始查询。因此,以下操作将不起作用,因为筛选定位器从 <ul>
列表元素开始匹配,该元素位于原始定位器匹配的 <li>
列表项之外
- 同步
- 异步
# ✖ WRONG
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)
# ✖ WRONG
await expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)
筛选不包含子元素/后代元素的元素
我们还可以按不包含匹配元素进行筛选。
- 同步
- 异步
expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
await expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
请注意,内部定位器是从外部定位器开始匹配的,而不是从文档根开始匹配的。
定位器运算符
在定位器内部匹配
您可以将创建定位器的方法(如 page.get_by_text() 或 locator.get_by_role())链接起来,以缩小对页面特定部分的搜索范围。
在本例中,我们首先通过定位其 listitem
角色创建一个名为 product 的定位器。然后按文本筛选。我们可以再次使用 product 定位器按按钮的角色进行定位并单击它,然后使用断言来确保只有一个文本为 "产品 2" 的产品。
- 同步
- 异步
product = page.get_by_role("listitem").filter(has_text="Product 2")
product.get_by_role("button", name="Add to cart").click()
product = page.get_by_role("listitem").filter(has_text="Product 2")
await product.get_by_role("button", name="Add to cart").click()
您还可以将两个定位器链接在一起,例如,要在特定对话框中查找 "保存" 按钮
- 同步
- 异步
save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()
save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
await dialog.locator(save_button).click()
同时匹配两个定位器
方法 locator.and_() 通过匹配附加定位器来缩小现有定位器的范围。例如,您可以将 page.get_by_role() 和 page.get_by_title() 结合起来,通过角色和标题进行匹配。
- 同步
- 异步
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
匹配两个可选定位器中的一个
如果您想定位两个或多个元素中的一个,并且不知道是哪个元素,请使用 locator.or_() 创建匹配所有备选方案的定位器。
例如,考虑一个您想单击 "新邮件" 按钮的场景,但有时安全设置对话框会显示出来。在这种情况下,您可以等待 "新邮件" 按钮或对话框,并相应地采取行动。
如果 "新邮件" 按钮和安全对话框都出现在屏幕上,"或" 定位器将匹配它们两者,可能会抛出 "严格模式违规" 错误。在这种情况下,您可以使用 locator.first 仅匹配它们中的一个。
- 同步
- 异步
new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
expect(new_email.or_(dialog).first).to_be_visible()
if (dialog.is_visible()):
page.get_by_role("button", name="Dismiss").click()
new_email.click()
new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
await expect(new_email.or_(dialog).first).to_be_visible()
if (await dialog.is_visible()):
await page.get_by_role("button", name="Dismiss").click()
await new_email.click()
仅匹配可见元素
通常最好找到一个 更可靠的方法 来唯一地标识元素,而不是检查可见性。
考虑一个页面,它有两个按钮,第一个按钮不可见,第二个按钮 可见。
<button style='display: none'>Invisible</button>
<button>Visible</button>
-
这将找到两个按钮,并抛出 严格性 违规错误
- 同步
- 异步
page.locator("button").click()
await page.locator("button").click()
-
这将只找到第二个按钮,因为它可见,然后单击它。
- 同步
- 异步
page.locator("button").locator("visible=true").click()
await page.locator("button").locator("visible=true").click()
列表
计算列表中的项目
您可以断言定位器,以计算列表中的项目数量。
例如,考虑以下 DOM 结构
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用计数断言来确保列表中有 3 个项目。
- 同步
- 异步
expect(page.get_by_role("listitem")).to_have_count(3)
await expect(page.get_by_role("listitem")).to_have_count(3)
断言列表中的所有文本
您可以断言定位器,以查找列表中的所有文本。
例如,考虑以下 DOM 结构
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
使用 expect(locator).to_have_text() 来确保列表包含文本 "苹果"、"香蕉" 和 "橙子"。
- 同步
- 异步
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])
await expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])
获取特定项目
有很多方法可以获取列表中的特定项目。
按文本获取
使用 page.get_by_text() 方法通过其文本内容在列表中定位元素,然后单击它。
例如,考虑以下 DOM 结构
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
按文本内容定位项目并单击它。
- 同步
- 异步
page.get_by_text("orange").click()
await page.get_by_text("orange").click()
按文本筛选
使用 locator.filter() 在列表中定位特定项目。
例如,考虑以下 DOM 结构
- 苹果
- 香蕉
- 橙子
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
按 "listitem" 角色定位项目,然后按 "橙子" 的文本进行筛选,然后单击它。
- 同步
- 异步
page.get_by_role("listitem").filter(has_text="orange").click()
await page.get_by_role("listitem").filter(has_text="orange").click()
按测试 ID 获取
使用 page.get_by_test_id() 方法在列表中定位元素。如果您还没有测试 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 定位项目,然后单击它。
- 同步
- 异步
page.get_by_test_id("orange").click()
await page.get_by_test_id("orange").click()
按第 N 个项目获取
如果您有一组相同的元素,并且区分它们的唯一方法是顺序,您可以使用 locator.first、locator.last 或 locator.nth() 从列表中选择特定元素。
- 同步
- 异步
banana = page.get_by_role("listitem").nth(1)
banana = await page.get_by_role("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”的行截图
- 同步
- 异步
row_locator = page.get_by_role("listitem")
row_locator.filter(has_text="Mary").filter(
has=page.get_by_role("button", name="Say goodbye")
).screenshot(path="screenshot.png")
row_locator = page.get_by_role("listitem")
await row_locator.filter(has_text="Mary").filter(
has=page.get_by_role("button", name="Say goodbye")
).screenshot(path="screenshot.png")
您现在应该在项目的根目录中有一个“screenshot.png”文件。
罕见用例
对列表中的每个元素执行操作
迭代元素
- 同步
- 异步
for row in page.get_by_role("listitem").all():
print(row.text_content())
for row in await page.get_by_role("listitem").all():
print(await row.text_content())
使用常规 for 循环迭代
- 同步
- 异步
rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())
rows = page.get_by_role("listitem")
count = await rows.count()
for i in range(count):
print(await rows.nth(i).text_content())
在页面中评估
locator.evaluate_all() 中的代码在页面中运行,您可以在其中调用任何 DOM api。
- 同步
- 异步
rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")
rows = page.get_by_role("listitem")
texts = await rows.evaluate_all("list => list.map(element => element.textContent)")
严格性
定位器很严格。 这意味着对定位器执行的所有暗示某些目标 DOM 元素的操作,如果匹配多个元素,则会抛出异常。 例如,如果 DOM 中有几个按钮,则以下调用会抛出异常
如果有多个则会抛出错误
- 同步
- 异步
page.get_by_role("button").click()
await page.get_by_role("button").click()
另一方面,Playwright 理解您何时执行多元素操作,因此当定位器解析为多个元素时,以下调用可以正常工作。
对多个元素有效
- 同步
- 异步
page.get_by_role("button").count()
await page.get_by_role("button").count()
您可以通过告诉 Playwright 当多个元素匹配时使用哪个元素来明确选择退出严格性检查,方法是使用 locator.first、locator.last 和 locator.nth()。 不建议使用这些方法,因为当您的页面发生变化时,Playwright 可能会点击您无意点击的元素。 相反,请遵循上面的最佳实践来创建唯一标识目标元素的定位器。
更多定位器
对于不太常用的定位器,请查看 其他定位器 指南。