UI 自动化的基本功:元素定位的原则、策略与实战经验

在自动化测试中,我们与其说是在"操作网页",不如说是在与元素打交道。定位元素可以说是自动化测试工程师的基本功

大家可能会觉得可以通过浏览器、录制工具或者AI辅助进行元素定位,是的这些工具确实能提高效率,但如果缺乏对定位策略和原理 的理解,就容易导致测试脚本脆弱、不可维护

本文内容概览:

  • 定位策略的基本准则与选取思路(稳定属性、语义优先、确保唯一性、锚点与组合、避免过度复杂、少用索引与文本)
  • CSS与XPath定位的详细对比
  • 真实场景的实战案例

选择定位方式的基本准则(最佳实践)

如果页面元素都提供了稳定的属性(例如 id、data-testid),定位就非常简单了 ------ 基本不会遇到太多挑战,但是很多页面结构复杂,class都是使用UI库动态生成、缺少可用稳定的属性。此时选择最佳定位方式往往比想象中困难,以下是几条定位时的准则可以帮助判断

  1. 使用不会频繁变化的属性
    避免动态class、随机 id,优先使用稳定的通常不会更改的属性。

  2. 优先基于"语义"而不是"样式"定位
    尽量使用对于被测应用有领域或产品属性语义的属性,比如在测试在线商店应用时使用price、product等class就优于 hover-light、col-md-4 等样式词。

  3. 确保唯一性
    任何时候只定位到一个或一组目标元素,你应该使用插件或jquery去测试定位器确保返回的元素中不会包含你不想要的元素

  4. 为复杂结构选好"锚点"
    当需要定位的元素没有合适的属性时,事情就开始变得复杂了。我们需要找一个临近的属性作为参考以提高定位器的稳定,这个参考就是锚点。在选择锚点时同样需要参考以上3个准则

  5. 合理使用组合定位器
    当单个属性不足够确保唯一性时,可以使用元素中的不同属性来形成组合。比如

    //button[@class='btn' and @type='submit' and @data-action='add-to-cart' and @data-product-id='123']

  6. 避免过度复杂的定位表达式
    请不要使用特别复杂的定位表达式,尤其是在使用Xpath时,首先切记不要使用太长的绝对定位方式(是不是直接从浏览器里右键复制过来的?)。个人实践经验:
    定位路径尽量控制在 2~3 层(两三个 / 或 //)以内。若超过 4 层,请先检查是否可以引入 data-testid 或选择更近的锚点。适度使用锚点与组合,过度复杂反而容易导致不稳定。

  7. 明确区分"内部查找"与"直接子节点"
    当要处理从上往下寻找子节点时,需要区分是内部查找还是直接子节点。
    内部查找(descendant) :查找该节点下所有符合条件的节点,也就是相对路径

    • CSS:.parent a
    • XPath://parent//a
      直接子节点(child):只查找第一层子节点
    • CSS:.parent > a
    • XPath://parent/a
      当一个父节点内部存在多个类似结构时,使用内部查找容易返回多余的元素,此时应优先使用"直接子节点"来确保精准定位。
  8. 文本定位作为最后手段
    使用文本作为定位往往是最后的手段,因为文本变动大、并且对多语言不友好,所以能不用尽量不用。
    除非你明确需要通过某个业务字段来定位,例如通过用户的name、在列表中找到目标行,否则应避免依赖文本内容进行定位。
    如不得不按文本匹配,优先匹配规范化文本:normalize-space(.)='xxx',并用确保多语言稳定。"

  9. 避免用数字定位(index-based)
    除非逻辑上列表永远确保顺序,比如第一条/最后一条。

  10. 与开发沟通,添加testid
    尽量与开发沟通添加 data-testid。一个简单的属性就能大幅减少测试工程师定位元素的工作量,降低测试脚本的不稳定性。


CSS与XPath定位方式对比

CSS和Xpath定位是自动化测试常用到的两种定位方式,该如何选择呢,其实答案非常简单:CSS定位是常规手段,Xpath定位是特殊手段。

什么是CSS Selector

CSS定位是一种基于标签、class、id、属性及层级关系的选择规则,原本用于网页样式,但在自动化测试中也被广泛用于定位元素,语法简洁且性能高。

CSS定位的优势

  • 性能更快(浏览器原生支持 CSS Selector,无需额外解析)
  • 语法更简洁、更易读、更易维护
  • 与现代前端测试框架兼容性好(Playwright、Cypress、Puppeteer 都鼓励使用 CSS)

CSS定位的缺点

  • 无法向上查找父节点(CSS只能从父到子,不能反向)
  • 不支持基于文本内容的定位(无法像 XPath 的 text() 或 contains() 那样直接通过文本查找元素)
  • 复杂逻辑表达能力有限(虽然可以组合多个选择器,但在处理复杂的条件判断时不如 XPath 灵活)

什么是XPath

是一种用于在 XML/HTML 文档中导航的查询语言,可以通过路径、层级、属性甚至文本内容来定位元素,相对来说功能强大。

XPath定位的优势

  • 可以双向遍历 DOM
    XPath能从子节点找父节点,这是 CSS 无法实现的。
  • 拥有更丰富的运算符与过滤条件
    支持复杂逻辑组合:and、or、数值运算、位置判断等。可以实现通过逻辑查询来定位元素
  • 支持函数(Functions)进行文本处理
    如 text(), contains(), starts-with(),能实现基于文本内容的定位(CSS 不行)。
  • 更强的结构导航能力(Axes)
    parent::, following-sibling::, ancestor::

XPath定位的缺点

尽管XPath功能强大且灵活,但在实际使用中也存在一些明显的不足:

  • 表达式往往过于复杂,尤其是在处理深层嵌套的元素时,XPath定位器会变得冗长且难以阅读和维护。
  • 虽然使用相对路径的XPath能够在很大程度上解决动态元素定位的问题,但当页面结构频繁变化时仍然无法完全避免定位失败的可能

详细对比:XPath vs CSS Selector

下表详细对比了 XPath 与 CSS Selector,帮助大家在自动化测试中根据实际需求选择最佳定位方式

对比项 XPath Selector CSS Selector
方向性(Direction) 双向:可以从父节点找到子节点,也可以从子节点反向找到父节点或兄弟节点。 单向:只能从父节点向下查找子节点,无法反向寻找父节点。
性能(Performance ) 通常比 CSS 慢,因为浏览器需要额外解析 XPath 表达式。 更快,浏览器原生支持 CSS 查询。
易读性(Ease of Understanding) 表达式往往更复杂,不如 CSS 简洁。 语法更简单,易于理解和维护。
Axes(轴) 支持丰富的**Axes(轴)**来处理结构关系,如 parent::、following-sibling::。 不支持 Axes,依赖属性和结构组合来定位元素。
精准性(Precision) 可通过文本、层级、逻辑精确定位元素(功能更强大)。 多依赖结构与属性定位元素,需要上下文结构更稳定。
可选择范围(Scope of Selection) 可以选择元素、属性节点、文本节点(如 text())。 无法直接选择文本节点,只能选 DOM 元素。

定位实战案例

以下是常见的复杂定位方式与组合技巧,这些都是我在实际自动化测试中总结出来的实战经验

1. 组合定位(Combination Selectors)

当单个属性不足以保证定位的精准时,可以通过组合多个属性来定位元素

例如:

在一个电商网站中,需要定位"加入购物车"按钮,但页面上有多个按钮,它们都有相同的class名称。
HTML结构:

复制代码
<div class="product-card">
  <button class="btn" type="submit" data-action="add-to-cart">加入购物车</button>
</div>

<div class="product-card">
  <button class="btn" type="button" data-action="view-details">查看详情</button>
</div>

如果只用 .btn,会匹配到所有按钮;而使用[type="submit"] 又可能匹配到其他提交按钮。

通过组合标签名 + class + type + data-action 四个维度,可以精准定位到目标按钮,避免误匹配其他元素。

CSS:

复制代码
button.btn[type="submit"][data-action="add-to-cart"]

XPath:

复制代码
//button[@class='btn' and @type='submit' and @data-action='add-to-cart']

2. 基于部分属性(模糊匹配)

在许多情况下,元素的属性值可能包含一些特定且比较稳定的内容,例如URL中的部分link、特定的类名、placehoder等。此时,使用模糊匹配可以帮助我们更灵活地定位元素。
示例 1:URL Link 模糊匹配
HTML 结构:

复制代码
<a href="/product?item=123">产品详情</a>

目标: 定位所有包含 product 关键字的 URL。

CSS:

复制代码
a[href*="product"]

XPath:

复制代码
//a[contains(@href, 'product')]

示例 2:Placeholder 模糊匹配
HTML 结构:

复制代码
<input type="text" placeholder="请输入您的用户名">
<input type="email" placeholder="请输入联系邮箱">

目标: 定位所有 placeholder 属性包含 用户 关键字的输入框。

CSS:

复制代码
input[placeholder*="用户"]

XPath:

复制代码
//input[contains(@placeholder, '用户')]

示例 3:Class 类名模糊匹配
HTML 结构:

复制代码
<button class="btn btn-primary btn-large active">提交</button>
<div class="notification notification-error">错误提示</div>

目标: 定位所有 class 中包含 notification 的div。

CSS:

复制代码
div[class*="notification"]

XPath:

复制代码
//div[contains(@class, 'notification')]

3 通过锚点定位

锚点是一个附近或上下层中比较容易定位的元素,如果需要定位的元素没有一个比较稳定的属性通过锚点开始定位能大幅提高稳定性。

3.1 从父元素寻找子元素

实际场景

在商品列表页面,每个商品卡片都包含商品名称、价格和购买按钮。假设需要点击某个特定商品(例如"Book")的购买按钮,但所有购买按钮都使用相同的class名称btn-buy,没有唯一标识。

这种情况下,可以先定位到包含该商品的父级容器(div.product),然后在该容器内查找购买按钮,从而实现精准定位。

HTML:

复制代码
<div class="product">
  <h3>Book</h3>
  <button class="btn-buy">Buy</button>
</div>

CSS:

复制代码
.product .btn-buy

XPath:

复制代码
//div[contains(@class,'product')]//button[contains(@class,'btn-buy')]

3.2 从子元素往上找父元素

当需要找到父级容器(如产品卡片或表格行)时,可以通过子元素反向查找。这种方式只能使用 XPath的ancestor实现

复制代码
//span[text()='Product Name']/ancestor::div[contains(@class,'product')]

3.3 寻找兄弟节点(Sibling)

适用于输入框较多,label与输入框并列出现但输入框则没有合适的属性。

HTML:

复制代码
<label>Username</label>
<input type="text">

CSS:

复制代码
label + input

XPath:

复制代码
//label[text()='Username']/following-sibling::input

4.按文本内容定位(XPath独有)

如之前所述,文本定位应作为最后手段,不要优先选择(尤其在多语言应用中)。通过元素的text内容定位是CSS无法实现的功能,因此是XPath常见的使用场景。

XPath模糊匹配文本:

复制代码
//button[contains(text(),'Download')]

5.表格中的定位

在数据驱动的应用中,列表和表格是常见的元素结构。典型场景是先定位到特定的某一行,再点击该行中的按钮。

例如:根据Name或Id找到对应行的"Edit"按钮。此时需要使用XPath先定位包含特定文本的列,然后再定位该列所在行中的按钮。

HTML:

复制代码
<tr>
  <td>John Doe</td>
  <td><button>Edit</button></td>
</tr>

XPath:

复制代码
//tr[td[text()='John Doe']]//button[text()='Edit']

6.列表定位(第 N 项、最后一项)

在实际的自动化测试中,经常需要操作列表中的特定项。例如,在一个待办事项应用中,需要验证或操作第一个任务、最后一个任务,或者特定位置的任务。

如前文最佳实践所述,应尽量避免依赖索引(index)来定位元素,除非是第一个或最后一个这类位置相对固定的。因为Inde对应的内容可能随数据变化而改变。
实际场景:

假设有一个任务列表,每个任务都包含标题和操作按钮。需要分别定位第一个任务、最后一个任务和第三个任务来执行不同的验证。
HTML结构:

复制代码
<ul class="task-list">
  <li><span>完成项目文档</span><button>完成</button></li>
  <li><span>代码审查</span><button>完成</button></li>
  <li><span>编写测试用例</span><button>完成</button></li>
  <li><span>部署到生产环境</span><button>完成</button></li>
</ul>

CSS:

复制代码
/* 第一个任务 */
ul.task-list li:first-child

/* 最后一个任务 */
ul.task-list li:last-child

/* 第三个任务 */
ul.task-list li:nth-child(3)

XPath:

复制代码
// 第一个任务
//ul[@class='task-list']/li[1]

// 最后一个任务
//ul[@class='task-list']/li[last()]

// 第三个任务
//ul[@class='task-list']/li[3]

这种定位方式特别适用于需要验证列表顺序、测试列表边界情况(首项/末项)或按位置执行操作的场景。


总结

元素定位是UI自动化测试的核心技能,直接影响测试脚本的稳定性和可维护性。通过遵循"稳定属性优先、语义化定位、避免脆弱选择器"的原则,结合CSS和XPath各自的优势,我们能够构建出更加健壮的自动化测试框架。在实际应用中,应根据具体场景灵活选择定位策略,善用组合定位、锚点定位等技巧,在保证准确性的同时兼顾代码的可读性和维护性。 请根据我现在的文章,再重新为我生成合适的标题

相关推荐
乘云数字DATABUFF3 天前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
荣--5 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森5 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜6 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB7 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode8 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220709 天前
如何搭建本地yum源(上)
运维
大树8812 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠12 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质12 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务