在使用 Playwright 进行端到端网站测试和监控时,选择合适的定位器至关重要。正确的定位器有助于创建更可靠、稳定性更强的测试。让我们来探讨面向用户的定位器,以及如何筛选定位器以增强测试的稳健性。
示例场景
考虑一个包含标题、按钮和状态更新框的简单网页。点击按钮后,状态会更新并触发彩纸动画。我们将通过这个场景来展示如何测试。
为什么不使用 CSS 选择器查找页面元素?
如果你习惯了使用一些老旧的自动化和页面操作系统,比如 JQuery,你可能会倾向于使用 CSS 选择器。如果我们检查这个页面,确实可以看到按钮应用了一个类,我们可以通过它来选择页面上的这个按钮。
在测试中,我们可以用类似 await page.locator("button.button-frontpage-style").click()
这样的代码点击这个按钮,虽然这能奏效,但并不推荐。
这种方法有什么问题?
- 用户查看页面时会寻找按钮,而不会去看 CSS 类,因此我们的测试不再模仿用户的操作路径。
- 如果前端工程师为了样式原因将类更改为
button-hero-panel-style
,那么我们的测试会出现假阳性:报告出错但实际并无问题。 - 如果按钮文本来自内容管理系统(CMS),而文本发生故障,按钮文本可能会变为 "HEROBUTTON_TXT",此时我们的测试依然会通过,但用户界面对用户来说已经损坏了,这是一个假阴性。 基于以上原因,Playwright 项目建议不要使用 CSS 定位器,遵循项目中设定的标准是个好主意!
替代 CSS 选择器的做法:使用面向用户的定位器
Playwright 提供了一些基于页面角色的定位器,这比通过 CSS 或 HTML 查找更加功能化。使用内置的定位器,比如 getByRole
、getByText
和 getByLabel
,这些定位器的工作方式与用户的操作一致,即使用户使用的是不寻常的浏览器版本、移动设备,甚至是屏幕阅读器。
面向用户的定位器的应用
让我们将 page.locator
替换为 getByRole
,通过按钮的角色和可访问名称来定位按钮:
await page.getByRole('button', { name: 'click me' }).click();
使用 Chrome DevTools 检查元素,查看它们的可访问角色和名称。
这种方法可以确保即使类名或 HTML 结构发生变化,测试依然稳定。
一个额外的好处是,Playwright 的名称匹配是不区分大小写的,并且可以通过子字符串匹配。例如你可以将选择器改为 { name: 'click' }
,即使营销团队将按钮文本改为 "Click me now!",测试依然会通过,不会失败。
这个改变意味着,如果按钮标签变为类似 "HEROBUTTON_TXT" 的文本,测试会如预期那样失败。
当 getByRole
和 getByLabel
在你的页面上不起作用时该怎么办?
我支持使用面向用户的定位器的原因之一是,当你发现这些选择器在页面上不起作用时,它提示你页面可能存在严重的可访问性问题。如果很多页面元素没有设置角色和标签,或者所有页面元素的标签和角色都一样,这意味着页面的可访问性存在问题。毕竟,如果没有独特的标签,屏幕阅读器和键盘导航工具将无法正常工作,许多用户将无法导航你的页面。这应该引发开发者和 QA 人员之间关于如何改进页面结构的讨论,这将不仅改善测试,也改善所有用户的体验。
短期来看,可以查看 Playwright 的所有定位器,肯定有办法能找到页面上的任何元素,但我鼓励你将此作为未来讨论技术债务的起点。
处理多个匹配元素
如果定位器匹配了多个元素,Playwright 的严格模式将失败。例如,使用 getByRole('button')
可能会匹配多个按钮。你会收到类似这样的错误:
Error: locator.click: Error: strict mode violation: getByRole('button', { name: 'click' }) resolved to 2 elements:
请注意,即使第一个结果或所有结果通过了测试,严格模式下这个测试仍会失败。要解决这个问题,可以通过指定位置或筛选元素来处理。
基于位置的选择
当我们第一次遇到这个错误时,最简单的解决方案是指定列表中的某个位置。最简单的方法是选择第一个结果:
await page.getByRole('button').first().click();
我经常遇到上述错误,因此几乎每次写定位器时我都会养成在定位器中添加 first()
的习惯。😅
如果你想更具体地指定列表中的位置,可以使用:
await page.getByRole('button').nth(3).click();
.nth()
方法是从零开始的,因此 .nth(3)
将选择结果中的第四个元素。位置选择器可以工作,但如果页面内容是动态的,选择除第一个结果外的任何项都可能产生假阳性。为了更确保我们在动态列表中指向正确的元素,可以进行元素筛选。
Playwright 中的元素筛选
假设我们有一个电商界面,显示多个商品结果,但其中只有一些是可用的,使用位置选择器进行测试可能并不可靠。
如果我们检查页面,可以看到这些是列表项,因此可以使用一个选择器来确保商品是可用的,然后再检查按钮,代码如下:
const button = page.getByRole('listitem')
.filter({ hasText: 'available' })
.getByRole('button', { name: 'buy' });
await button.click();
现在,当用户看到的商品全部售罄时,这个测试将会在合适的时候失败。
定位器策略的最佳实践
没有一种通用的定位器解决方案可以适用于所有情况。这里有一些提示:
- 避免依赖实现细节:使用反映用户操作的定位器,而不是 HTML 结构。
- 使用内置定位器:
getByRole
、getByText
等定位器可以提高测试的稳定性和可维护性。 - 使用数据测试 ID:对于复杂的场景,使用数据测试 ID 可以简化定位器并提高测试的可靠性。任何仅用于调试的页面内容都应谨慎使用。
结论
选择正确的定位器是创建稳定且可维护的 Playwright 测试的关键。面向用户的定位器减少了测试的不稳定性,并鼓励良好的开发实践。如果你有兴趣加入 Playwright 开发者社区,欢迎加入 Checkly 的 Slack 频道。如果你使用 Playwright 进行端到端测试,考虑使用 Checkly 监控你的生产网站,我们有一个关于“监控即代码”的精彩故事,可以赋能你的开发者。