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 定位策略的选择顺序

嗯,这里我总结了一个优先级,你可以参考:

  1. get_by_role —— 语义化,对结构变化不敏感
  2. get_by_test_id —— 最稳定,但需要开发配合
  3. get_by_label / get_by_placeholder —— 表单元素首选
  4. CSS选择器 —— 速度快,语法简洁
  5. get_by_text —— 直观,适合按钮和链接
  6. XPath —— 最后的选择,除非你真的需要它的特殊能力

注意:不要过度依赖文本选择器。如果页面是国际化的,文本会随语言切换而变化。这时候用 get_by_test_id 或者 get_by_role 更靠谱。

好了,这一章的内容就到这里。元素定位是自动化测试的基石,花时间把它练扎实了,后面的路会顺很多。下一章我们聊聊页面交互——点击、输入、滚动这些操作,看看Playwright是怎么让它们变得优雅的。