第4章:元素定位基础

元素定位,说白了就是告诉自动化框架:「嘿,去点那个按钮!」。但小程序的环境比较特殊——它跑在微信的WebView里,DOM结构和普通网页不太一样。我刚开始做小程序自动化时,就吃过这个亏。

4.1 使用CSS选择器定位元素

CSS选择器是我个人最常用的定位方式。为什么?因为它快,而且写法简洁。小程序里大部分元素都能用CSS搞定。

核心原则:能用CSS就别用XPath。CSS选择器在性能上通常优于XPath,尤其在复杂页面中。

来看几个实战例子。假设我们有这样一个页面结构:

<view class="container">
  <text class="title">商品列表</text>
  <view class="item" data-id="1001">
    <image class="thumb" src="..." />
    <text class="name">iPhone 15</text>
    <text class="price">¥6999</text>
  </view>
</view>

常用的CSS选择器写法:

  • 类选择器.title → 定位到第一个class为title的元素
  • 标签+类text.title → 更精确,只匹配text标签下的title类
  • 后代选择器.container .price → 定位container下的price元素
  • 属性选择器[data-id="1001"] → 根据自定义属性定位
  • 伪类选择器.item:first-child → 第一个item

我在项目中遇到过一个问题:同一个class名出现在多个页面,导致定位到错误元素。后来我养成了一个习惯——优先用「父元素 + 子元素」的组合选择器,比如 .product-list .item .name,这样能大幅降低误命中率。

小技巧:在微信开发者工具的Console里输入 document.querySelectorAll('.your-selector'),可以快速验证选择器是否生效。我每次写定位前都会先跑一遍。

4.2 使用XPath定位元素

XPath虽然慢一点,但有些场景非它不可。比如:

  • 需要根据文本内容定位(「点那个写着'提交订单'的按钮」)
  • 需要定位父元素或兄弟元素
  • CSS选择器搞不定的复杂层级

常用的XPath写法:

// 绝对路径(不推荐,太脆弱)
//view/view[2]/text[3]

// 相对路径 + 属性
//text[@class='price']

// 包含文本
//text[contains(text(), 'iPhone')]

// 根据索引
(//view[@class='item'])[1]

// 找父元素
//text[text()='¥6999']/..

嗯,这里要注意:XPath的索引从1开始,不是0。我第一次写XPath时踩过这个坑,死活定位不到第二个元素,后来才发现索引写成了0。

避坑指南:我曾经在某个项目中用XPath定位一个动态生成的弹窗,结果每次运行都报错。查了半天发现是弹窗的class名里带了随机数。后来改用 contains(@class, 'modal') 才搞定。记住——优先用部分匹配,别写死完整属性值

什么时候用CSS,什么时候用XPath?我个人的经验法则:

场景 推荐方式 原因
通过class/id定位 CSS 简洁、性能好
通过文本内容定位 XPath CSS不支持文本匹配
定位父/兄弟元素 XPath CSS只能向下查找
简单层级(2-3层) CSS 可读性强
复杂层级(4层以上) XPath CSS写起来太啰嗦

4.3 元素属性的获取与断言

定位到元素只是第一步。你想想看,自动化测试的核心是什么?是验证。验证这个按钮是不是可点的,验证这个文本是不是预期的,验证这个图片有没有加载出来。

小程序里常用的属性获取方法:

// 获取文本内容
const text = await page.getText('.price')
// 返回: "¥6999"

// 获取属性值
const dataId = await page.getAttribute('.item', 'data-id')
// 返回: "1001"

// 获取样式
const color = await page.getStyle('.title', 'color')
// 返回: "rgba(0, 0, 0, 0.9)"

// 判断是否可见
const isVisible = await page.isVisible('.submit-btn')
// 返回: true/false

断言怎么写?我一般这样组织:

// 断言文本内容
const price = await page.getText('.price')
expect(price).toBe('¥6999')

// 断言元素存在
await expect(page.locator('.item')).toBeVisible()

// 断言属性值
const id = await page.getAttribute('.item', 'data-id')
expect(id).toBe('1001')

// 断言元素数量
const items = await page.locator('.item').count()
expect(items).toBeGreaterThan(0)

重要提醒:断言一定要有明确的预期值。我见过有人写 expect(element).toBeTruthy() 就完事了——这等于没测。你要验证的是「这个按钮显示'提交订单'」,而不是「这个按钮存在」。

说到属性获取,我记得有一次排查线上bug,发现某个按钮在某些机型上不显示。后来用 getStyle 获取了 display 属性,发现是条件渲染没处理好,导致按钮被设成了 display: none。你看,属性获取不仅能做断言,还能帮我们定位问题。

我的习惯:每个页面元素至少做3个断言——存在性断言、文本/属性断言、状态断言(可见/可点)。这样覆盖全面,漏测概率低。

最后说一句:定位和断言是自动化测试的基石。你想想看,如果连元素都定位不准,后面的操作全是白搭。所以,花时间把这块练扎实了,后面写用例会顺手很多。