4、元素定位进阶:XPath定位、CSS Selector定位、相对定位与链式定位
好,咱们继续往下走。上一章我们聊了基础定位方法,说实话,那些在简单页面上够用了。但金融系统嘛,你懂的——页面结构复杂,动态内容多,有时候一个元素藏得比保险柜里的现金还深。这时候,就得拿出进阶武器了。
我个人习惯把元素定位分为三个层次:能跑就行、稳定可靠、优雅高效。基础定位属于第一层,今天讲的这些,能帮你冲到第二层甚至第三层。
4.1 XPath定位:从入门到精通
XPath,全称 XML Path Language。说白了,就是用路径表达式在文档里找节点。我刚开始接触时觉得它语法有点怪,但用顺手后,真香。
4.1.1 绝对路径 vs 相对路径
先看个例子。假设我们要定位一个登录按钮:
// 绝对路径(不推荐)
/html/body/div[2]/form/div[3]/button
// 相对路径(推荐)
//button[@id='loginBtn']
为什么我不推荐绝对路径?因为金融系统的页面结构经常变。加个弹窗、改个布局,绝对路径就挂了。我在项目中遇到过,一个同事写了满屏的绝对路径,结果系统升级后全部失效,加班到凌晨两点才改完。嗯,从那以后,我们团队就立了规矩:禁止使用绝对路径。
4.1.2 常用XPath函数
这里我整理了几个高频使用的函数,你直接拿去用:
| 函数 | 作用 | 示例 |
|---|---|---|
contains() |
模糊匹配属性值 | //button[contains(@class, 'submit')] |
text() |
匹配文本内容 | //span[text()='确认转账'] |
starts-with() |
匹配属性开头 | //input[starts-with(@id, 'amount_')] |
not() |
排除某些元素 | //div[not(@class='hidden')] |
举个例子,金融系统里经常有动态生成的ID,比如 order_20240315_001、order_20240315_002。用 starts-with() 就稳了:
//div[starts-with(@id, 'order_')]
4.1.3 轴定位(Axes)
轴定位是XPath的杀手锏。你想想看,有时候我们想找的元素没有明显特征,但它旁边的兄弟元素有。这时候轴定位就派上用场了。
我常用的几个轴:
- following-sibling:找后面的兄弟节点
- preceding-sibling:找前面的兄弟节点
- parent:找父节点
- ancestor:找所有祖先节点
// 找"转账金额"标签后面的输入框
//label[text()='转账金额']/following-sibling::input
// 找"确认"按钮的父级表单
//button[text()='确认']/parent::form
4.2 CSS Selector定位:又快又稳
CSS Selector 是我个人最偏好的定位方式。为什么?因为它比 XPath 快,语法也更简洁。浏览器原生支持 CSS 选择器,执行效率更高。
4.2.1 基础语法
| 选择器 | 含义 | 示例 |
|---|---|---|
#id |
ID选择器 | #loginBtn |
.class |
类选择器 | .submit-btn |
tag |
标签选择器 | button |
[attribute] |
属性选择器 | [type='submit'] |
parent > child |
直接子元素 | form > button |
4.2.2 高级用法
CSS Selector 也能做模糊匹配,只是写法不同:
// 属性值以某字符串开头
input[name^='amount']
// 属性值以某字符串结尾
input[name$='_value']
// 属性值包含某字符串
input[name*='order']
我曾经用这个技巧解决过一个棘手问题。某个金融系统的输入框,ID 每次都变,但 name 属性始终以 trans_ 开头。一行 CSS 选择器搞定:
input[name^='trans_']
4.3 相对定位:从"找元素"到"找关系"
相对定位,说白了就是不直接找目标,而是找一个容易找的参照物,再通过关系找到目标。这个概念我在 Selenium 4 的 Relative Locator 里用得最多。
4.3.1 Selenium 4 的相对定位
// 找"确认转账"按钮上方的提示信息
WebElement confirmBtn = driver.findElement(By.id("confirmBtn"));
WebElement tip = driver.findElement(withTagName("div")
.above(confirmBtn));
// 找输入框右侧的金额单位
WebElement amountInput = driver.findElement(By.name("amount"));
WebElement unit = driver.findElement(withTagName("span")
.toRightOf(amountInput));
你想想看,这在金融系统里多好用。比如一个复杂的交易页面,有十几个输入框,每个输入框旁边都有对应的校验提示。用相对定位,先定位输入框,再找它旁边的提示,代码可读性直接拉满。
4.3.2 Playwright 的 locator 链式调用
Playwright 在这方面做得更优雅。它支持链式定位,就像搭积木一样:
// 先找到表单,再找表单里的输入框,再找输入框旁边的按钮
page.locator('form#transferForm')
.locator('input[name="amount"]')
.locator('..')
.locator('button[type="submit"]');
4.4 链式定位:优雅地组合定位策略
链式定位,就是把多种定位方式组合起来,像流水线一样一步步缩小范围。我个人觉得这是最优雅的定位方式。
4.4.1 实战案例:定位一个复杂的表格单元格
假设我们要定位一个交易记录表格中,某笔特定交易的"撤销"按钮。页面结构大概是:
<table id="tradeTable">
<tr>
<td class="trade-id">TX20240315001</td>
<td class="amount">10,000.00</td>
<td class="status">待处理</td>
<td><button class="cancel-btn">撤销</button></td>
</tr>
</table>
用链式定位的思路:
// 第一步:找到表格
// 第二步:找到包含目标交易ID的行
// 第三步:在该行内找到撤销按钮
// XPath 链式写法
//table[@id='tradeTable']//tr[.//td[text()='TX20240315001']]//button[text()='撤销']
// Playwright 链式写法
page.locator('#tradeTable')
.locator('tr', { has: page.locator('td', { hasText: 'TX20240315001' }) })
.locator('button.cancel-btn');
4.4.2 链式定位的黄金法则
我总结了几条经验:
- 从大到小:先定位容器,再定位内部元素
- 用唯一标识做锚点:比如表格ID、表单ID
- 避免过度依赖文本:文本可能国际化,用属性更稳
- 适当使用 has/hasText 过滤:Playwright 的 has 选项特别好用
4.5 总结与选择建议
好了,四种进阶定位方式都聊完了。最后给个选择建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单属性匹配 | CSS Selector | 性能好,语法简洁 |
| 文本匹配/模糊匹配 | XPath | contains/text() 函数强大 |
| 找兄弟/父级元素 | XPath 轴定位 | 唯一支持轴定位的方式 |
| 复杂页面结构 | 链式定位 | 可读性强,易于维护 |
| 动态内容/弹窗 | 相对定位 | 不依赖固定路径 |
说实话,没有银弹。我在不同项目里用过不同的组合,最终发现:理解每种定位方式的本质,比死记硬背语法重要得多。你想想看,金融系统的页面千变万化,但元素之间的关系是相对固定的。掌握了"找关系"的思路,什么页面都不怕。
下一章,我们会聊等待策略——毕竟金融系统里,网络延迟和异步加载是家常便饭。到时候见。