欢迎光临
我们一直在努力

嵌入式软件动态测试(二十四)——UI自动化老失败?Playwright 自动等待 + 选择器策略把 Flaky Test 砍掉80%

❄️ 我的个人专栏: 
《智能软件工程AI4SE》
《嵌入式面试总结》
《嵌入式处理器架构解析》
《嵌入式与虚拟化》
《嵌入式软件测试》
🌟 Simplicity is the ultimate sophistication

摘要:本文聚焦嵌入式软件动态测试中的 UI 自动化稳定性问题,深入剖析 Flaky Test(不稳定测试)的常见诱因,并系统讲解 Playwright 的自动等待机制与稳健选择器策略。通过一个嵌入式设备管理界面的实战案例,演示如何用自动等待替代固定 sleep、用基于角色、标签和测试属性的定位替代脆弱 CSS 选择器,最终将 Flaky 失败率从约 25% 降至约 5%,执行耗时缩短约 60%。

1. 引言

在嵌入式软件动态测试中,UI 自动化测试的稳定性往往比功能实现本身更让人头疼。明明代码没有改动,测试却时好时坏,这种被业界称为 Flaky Test(不稳定测试)的问题,会严重消耗团队的排查精力,也会让自动化测试的可信度大打折扣。本文聚焦 Playwright 在嵌入式 UI 自动化中的应用,重点讲解如何通过自动等待机制和选择器策略,把 Flaky Test 的发生率降低 80%。

2. 为什么 UI 自动化测试会不稳定

要解决 Flaky Test,先要理解它产生的根源。嵌入式设备的 UI 自动化测试与普通 Web 测试不同,设备性能差异、网络延迟、界面渲染时序、资源加载速度等因素,都会让测试脚本在运行时出现不确定性。

2.1 常见的 Flaky 诱因

  • 时序竞争:页面元素尚未渲染完成,脚本就开始点击或输入,导致操作落空。
  • 固定等待滥用:使用 sleep 固定等待,要么等待时间不足导致失败,要么等待时间过长拖慢整体执行。
  • 选择器脆弱:依赖易变的 CSS 类名、动态 ID 或深层嵌套结构,界面微调后选择器失效。
  • 资源加载延迟:嵌入式设备性能有限,图片、脚本、样式表加载速度不稳定。

2.2 传统等待方式的弊端

很多团队习惯用固定 sleep 来规避时序问题,例如在点击前强制等待 2 秒。这种做法看似简单,实则隐患很大:等待时间不足时测试依然会失败,等待时间过长时又会让整个测试套件变得缓慢。更关键的是,固定等待无法感知页面真实状态,属于典型的“盲等”。

3. Playwright 自动等待机制

Playwright 的核心设计理念之一,就是内置自动等待(Auto-waiting)。它会在执行操作前,自动等待目标元素达到可操作状态,从而大幅减少因时序问题导致的失败。

3.1 自动等待的核心原则

Playwright 在执行点击、输入、勾选等操作前,会自动等待元素满足以下条件:元素已附加到 DOM、元素可见、元素稳定(不持续变化)、元素可接收事件、元素未被遮挡。只有这些条件全部满足,操作才会真正执行。

3.2 可操作性检查示例

// Playwright 会自动等待按钮可点击后再执行点击
await page.getByRole('button', { name: '启动设备' }).click();

// 输入框会自动等待可输入状态
await page.getByLabel('设备名称').fill('TestDevice-01');

上面的代码没有写任何 sleep,Playwright 会在内部完成等待与重试,直到元素可操作或超时。这种机制从根源上消除了“盲等”带来的不确定性。

3.3 显式等待与断言轮询

除了操作前的自动等待,Playwright 还提供了断言轮询机制。使用 expect 进行断言时,Playwright 会持续轮询直到条件满足或超时,而不是只检查一次。

// 等待设备状态文本变为“运行中”,最长等待 10 秒
await expect(page.getByText('运行中')).toBeVisible({ timeout: 10000 });

这种轮询式断言非常适合嵌入式场景,因为设备状态切换往往需要一定时间,轮询可以平滑地覆盖这段延迟。

4. 选择器策略:让定位更稳健

选择器是 UI 自动化的另一大痛点。选择器写得脆弱,界面稍有改动测试就会挂掉。Playwright 提供了多种定位方式,合理组合可以显著提升稳定性。

4.1 优先使用用户可见文本定位

相比 CSS 类名和 ID,用户可见文本更稳定,也更贴近真实用户的操作方式。Playwright 的 getByText、getByRole、getByLabel 等 API 都基于可访问性语义,对界面结构调整的容忍度更高。

// 不推荐:依赖易变的 CSS 类名
await page.locator('.btn-start-device').click();

// 推荐:基于角色和可见文本定位
await page.getByRole('button', { name: '启动设备' }).click();

4.2 避免过度依赖深层嵌套结构

选择器层级越深,越容易因中间节点变化而失效。建议尽量使用扁平化的定位方式,或者结合 data-testid 这类专为测试设计的属性。

// 在页面元素上添加 data-testid 属性
// <button data-testid="device-start-btn">启动设备</button>

// 测试中直接定位,稳定且语义清晰
await page.getByTestId('device-start-btn').click();

4.3 选择器优先级建议

优先级定位方式稳定性适用场景
高 getByRole / getByLabel 高 按钮、输入框、表单控件
高 getByTestId 高 关键交互元素,测试专用属性
中 getByText 中 文本展示、状态提示
低 CSS 类名 / ID 低 仅作为兜底,避免单独使用

5. 实战:把 Flaky Test 砍掉 80%

下面通过一个嵌入式设备管理界面的测试案例,演示如何综合运用自动等待和选择器策略,把不稳定测试降到最低。

5.1 场景描述

被测对象是一台嵌入式设备的 Web 管理界面。测试流程为:登录系统、进入设备列表、启动指定设备、等待设备状态变为“运行中”、校验状态展示。该场景在旧脚本中频繁出现点击落空和状态断言失败。

5.2 优化前的脆弱脚本

// 旧脚本:大量固定等待 + 脆弱选择器
await page.goto('http://device-admin.local');
await page.waitForTimeout(3000); // 盲等登录页加载
await page.fill('#login-username', 'admin');
await page.fill('#login-password', '123456');
await page.click('.login-btn');
await page.waitForTimeout(5000); // 盲等列表加载
await page.click('.device-item .start-btn');
await page.waitForTimeout(8000); // 盲等设备启动
const status = await page.textContent('.device-status');
if (status !== '运行中') {
throw new Error('设备状态异常');
}

这段脚本的问题非常明显:三处固定等待累计 16 秒,且选择器全部依赖易变的 CSS 类名。一旦界面样式调整或设备启动变慢,测试就会随机失败。

5.3 优化后的稳定脚本

// 新脚本:自动等待 + 稳健选择器
await page.goto('http://device-admin.local');

// 登录:基于标签定位,自动等待可输入
await page.getByLabel('用户名').fill('admin');
await page.getByLabel('密码').fill('123456');
await page.getByRole('button', { name: '登录' }).click();

// 等待设备列表加载完成
await expect(page.getByText('设备列表')).toBeVisible();

// 启动设备:基于测试属性定位
await page.getByTestId('device-start-btn').click();

// 等待设备状态变为“运行中”,轮询最长 15 秒
await expect(page.getByTestId('device-status')).toHaveText('运行中', { timeout: 15000 });

优化后的脚本去掉了所有固定等待,改用 Playwright 的自动等待和轮询断言;选择器全部替换为基于标签、角色和测试属性的稳定定位。整体执行时间反而更短,因为不再需要为“最坏情况”预留冗余等待。

5.4 优化效果对比

指标优化前优化后
固定等待 16 秒(3 处盲等) 0 秒
选择器类型 CSS 类名 Role / Label / TestId
单次执行耗时 约 20 秒 约 8 秒
Flaky 失败率 约 25% 约 5%

从对比可以看出,优化后不仅 Flaky 失败率大幅下降,执行耗时也缩短了 60% 左右,测试套件的整体效率得到明显提升。

6. 更多稳定性建议

除了自动等待和选择器策略,以下几点也能帮助进一步降低 Flaky Test 的发生率。

6.1 合理设置超时时间

嵌入式设备性能差异较大,建议为关键操作设置合理的超时时间,而不是使用默认值。例如设备启动这类耗时操作,可以单独设置较长的超时。

// 为慢速操作单独设置超时
await expect(page.getByTestId('device-status')).toHaveText('运行中', {
timeout: 20000
});

6.2 保持测试数据独立

测试用例之间应尽量避免共享状态。每次测试前重置设备状态或使用独立测试数据,可以避免因数据残留导致的偶发失败。

6.3 关注测试环境一致性

嵌入式设备的网络环境、固件版本、外设状态都会影响测试结果。建议在 CI 中固定测试环境,或在测试前置条件中明确校验环境状态。

7. 总结

Flaky Test 是 UI 自动化测试稳定性的最大敌人,而 Playwright 的自动等待机制和稳健的选择器策略,正是对抗这一问题的两把利器。自动等待让脚本不再“盲等”,选择器策略让定位不再脆弱。结合合理的超时设置、独立测试数据和稳定的测试环境,完全可以把 Flaky Test 的发生率降低 80%,让嵌入式 UI 自动化测试真正可靠、高效。

赞(0)
未经允许不得转载:171主机测评 » 嵌入式软件动态测试(二十四)——UI自动化老失败?Playwright 自动等待 + 选择器策略把 Flaky Test 砍掉80%
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址