4、元素定位基础:CSS选择器、XPath选择器、文本选择器、Playwright的get_by_*系列方法
做自动化测试,说白了就是让浏览器替我们干活。但浏览器怎么知道该点哪里、该输入什么?靠的就是元素定位。我刚开始学自动化时,花了大把时间在定位上——定位不对,脚本全废。这一章,我把最常用的几种定位方式掰开揉碎讲清楚。
4.1 CSS选择器:又快又稳的老朋友
CSS选择器是我个人最常用的定位方式。为什么?因为它快,而且语法简洁。你在写前端页面时用的那些选择器,在Playwright里几乎都能直接用。
核心原则:能用CSS选择器,优先用CSS选择器。它的执行速度通常比XPath快一个数量级。
4.1.1 基础语法
| 选择器 | 示例 | 说明 |
|---|---|---|
| 标签选择器 | button |
匹配所有button元素 |
| 类选择器 | .btn-primary |
匹配class包含btn-primary的元素 |
| ID选择器 | #submit-btn |
匹配id为submit-btn的元素 |
| 属性选择器 | [data-testid="login"] |
匹配指定属性值的元素 |
| 后代选择器 | form .input-group |
匹配form内部所有.input-group |
| 子元素选择器 | form > .input-group |
匹配form的直接子元素 |
// 实战示例
await page.locator('.login-form button[type="submit"]').click();
await page.locator('#username').fill('admin');
await page.locator('input[name="password"]').fill('123456');
我的小技巧:在Chrome开发者工具的Console里,用 $$('.your-selector') 可以快速验证CSS选择器是否写对了。这比反复跑脚本调试快得多。
4.1.2 高级用法
CSS选择器还有一些进阶玩法,我在项目中经常用到:
- 伪类选择器:
:nth-child(2)选第二个子元素,:first-child选第一个 - 否定伪类:
:not(.disabled)排除特定类 - 包含文本:CSS本身不支持文本匹配,但Playwright扩展了
:has-text()和:text()
// 高级示例
await page.locator('li:nth-child(3) a').click();
await page.locator('button:not(.disabled)').click();
// Playwright扩展的文本匹配
await page.locator('button:has-text("提交")').click();
4.2 XPath选择器:灵活但别滥用
XPath功能确实强大,能往上找父元素、能根据文本内容定位。但我建议你——能用CSS就别用XPath。为什么?因为慢。我在一个大型表格页面做过测试,CSS选择器比XPath快了将近3倍。
不过,有些场景XPath确实无可替代。比如你要根据元素包含的文本定位,或者需要从子元素反查父元素。
4.2.1 常用XPath写法
| 表达式 | 示例 | 说明 |
|---|---|---|
| 绝对路径 | /html/body/div[2]/form |
从根节点开始,脆弱易断 |
| 相对路径 | //form//input[@name="email"] |
推荐使用,更灵活 |
| 按文本定位 | //button[text()="登录"] |
匹配文本完全等于"登录" |
| 包含文本 | //button[contains(text(),"登录")] |
匹配文本包含"登录" |
| 按索引 | (//div[@class="item"])[2] |
取第二个匹配元素 |
| 轴定位 | //td[text()="价格"]/following-sibling::td |
找兄弟节点,表格常用 |
// XPath实战
await page.locator('//button[contains(@class, "submit")]').click();
await page.locator('//input[@type="text" and @placeholder="请输入用户名"]').fill('admin');
// 表格中根据某列找同行另一列
const price = page.locator('//tr[td[text()="商品A"]]/td[3]').textContent();
避坑指南:我曾经在写XPath时用了 //*[@id="form"] 这种通配符写法,结果页面结构稍微一变,脚本就挂了。尽量用具体的标签名,别偷懒用 *。
4.3 文本选择器:最直观的方式
说实话,文本选择器是我最喜欢教给新手的。为什么?因为它最符合人的直觉。你看到页面上有个"提交"按钮,直接写 page.get_by_text("提交") 就行了,不用管什么class、id。
但要注意,文本选择器也有它的脾气:
- 精确匹配:
page.get_by_text("登录", exact=True)要求文本完全一致 - 模糊匹配:默认就是模糊匹配,只要包含就行
- 忽略大小写:可以设置
ignore_case=True
// 文本选择器示例
await page.get_by_text("提交订单").click();
await page.get_by_text("登录", exact=True).click();
// 配合输入框
await page.get_by_label("用户名").fill("admin");
await page.get_by_placeholder("请输入密码").fill("123456");
我的经验:文本选择器最适合按钮、链接这种用户可见的元素。但对于隐藏文本或者动态加载的内容,它可能会找不到。这时候我会退回到CSS选择器。
4.4 Playwright的get_by_*系列方法
这是Playwright最让我舒服的地方。它提供了一套非常语义化的API,你几乎不用记什么选择器语法,直接按角色、按标签、按文本去定位就行。
4.4.1 方法一览
| 方法 | 适用场景 | 示例 |
|---|---|---|
get_by_role() |
按ARIA角色定位,最推荐 | get_by_role("button", name="提交") |
get_by_label() |
表单输入框,关联label | get_by_label("用户名") |
get_by_placeholder() |
按placeholder属性定位 | get_by_placeholder("请输入密码") |
get_by_text() |
按可见文本定位 | get_by_text("登录") |
get_by_alt_text() |
图片的alt属性 | get_by_alt_text("产品图片") |
get_by_title() |
元素的title属性 | get_by_title("帮助信息") |
get_by_test_id() |
自定义data-testid属性 | get_by_test_id("submit-btn") |
4.4.2 为什么我偏爱get_by_role
你想想看,一个按钮不管它长什么样——是<button>标签,还是<div role="button">,它的角色都是"button"。用 get_by_role 定位,你根本不用关心具体实现。
我在项目中遇到过好几次重构:前端把<button>改成了<a>,但角色没变。用CSS选择器写的脚本全挂了,用 get_by_role 的脚本一个都没改——这就是它的威力。
// get_by_role 最佳实践
await page.get_by_role("button", name="提交").click();
await page.get_by_role("link", name="首页").click();
await page.get_by_role("textbox", name="用户名").fill("admin");
await page.get_by_role("combobox", name="城市").select_option("北京");
我的建议:优先使用 get_by_role,其次是 get_by_test_id(需要开发配合加属性),最后才是CSS和XPath。这样你的脚本对页面结构变化的抵抗力最强。
4.4.3 链式定位与过滤
有时候一个页面有多个相似的按钮,比如"编辑"按钮出现了好几次。这时候可以用链式定位加过滤:
// 链式定位
const row = page.get_by_role("row").filter({ has_text: "商品A" });
await row.get_by_role("button", name="编辑").click();
// 或者用locator的filter方法
await page.locator("button").filter({ has_text: "确认" }).click();
await page.locator("li").filter({ has_not_text: "已删除" }).click();
4.5 定位策略的选择顺序
嗯,这里我总结了一个优先级,你可以参考:
- get_by_role —— 语义化,对结构变化不敏感
- get_by_test_id —— 最稳定,但需要开发配合
- get_by_label / get_by_placeholder —— 表单元素首选
- CSS选择器 —— 速度快,语法简洁
- get_by_text —— 直观,适合按钮和链接
- XPath —— 最后的选择,除非你真的需要它的特殊能力
注意:不要过度依赖文本选择器。如果页面是国际化的,文本会随语言切换而变化。这时候用 get_by_test_id 或者 get_by_role 更靠谱。
好了,这一章的内容就到这里。元素定位是自动化测试的基石,花时间把它练扎实了,后面的路会顺很多。下一章我们聊聊页面交互——点击、输入、滚动这些操作,看看Playwright是怎么让它们变得优雅的。