为了不做“手点回归”:HarmonyOS 7 UI 自动化测试

以前我们的发版流程是这样的:测试同学拿着两台手机,把登录、下单、支付等几十个流程手动点一遍。点完半天过去了。

这周我决定把核心流程自动化。查了半天,鸿蒙主要用的是 @ohos/hypium 结合 @ohos/uitest。写起来像写脚本一样,但跑起来的时候,各种玄学问题层出不穷。

这篇复盘,记录我用 UITest 踩过的坑。

https://placeholder-01.jpg/

给组件加 ID 是个好习惯

UI 测试的第一步是找组件。如果你用文本去找,比如 ON.text('登录'),极其脆弱。文案一改,你的脚本全废。

正确的做法是,在所有的业务组件上,老老实实加上 .id():

typescript

复制代码
// 在开发页面时
Button('登录')
  .id('btn_login') 

在测试脚本里,直接用 ID 定位:

typescript

复制代码
import { Driver, ON } from '@ohos.uitest';

const driver = Driver.create();
const btn = await driver.findComponent(ON.id('btn_login'));
await btn.click();

导致白屏的罪魁祸首

我写的第一版脚本,是这样的:

typescript

复制代码
// 错误示范
await driver.findComponent(ON.id('btn_login')).click();
// 立刻去断言首页元素
const title = await driver.findComponent(ON.text('首页'));
expect(title !== null).assertTrue();

跑一次,失败一次。因为网络请求是异步的,点击登录后,页面还在转圈,系统根本找不到"首页"这两个字,脚本直接超时崩溃。

重构方案: 必须使用 waitForComponent。它会在指定时间内轮询查找,直到元素出现。

typescript

复制代码
await driver.findComponent(ON.id('btn_login')).click();
// 最多等待 5000 毫秒
const title = await driver.waitForComponent(ON.text('首页'), 5000);
expect(title !== null).assertTrue();

系统弹窗与权限

跑自动化时,最怕的不是业务逻辑不对,而是系统突然弹个框。

比如你正在测"扫一扫",系统弹个"是否允许使用相机"。你的脚本一懵,直接卡死在那里。

处理办法: 在测试框架的 beforeAll 钩子里,或者每个测试用例开始前,先做一次防御性检查。

typescript

复制代码
async function handleSystemDialog(driver: Driver) {
  // 找一下有没有“允许”按钮,如果有就点掉
  const allowBtn = await driver.findComponent(ON.text('允许'));
  if (allowBtn) {
    await allowBtn.click();
  }
}

另外,测试用例之间一定要有独立性。测试完登录,要把数据清理掉,退回初始状态,别让上一个用例的状态影响下一个。

  1. UI 测试不要贪多,挑最核心的链路写(如登录、支付),边缘业务还是靠人工。

  2. 所有操作必须包裹 waitForComponent,否则跑一次挂一次。

  3. 将测试脚本接入 CI/CD(比如 GitLab CI),每次提 PR 自动跑一遍,那酸爽,谁用谁知道。