大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

前言

在 HarmonyOS 应用研发与版本迭代过程中,登录、搜索等核心业务流程的回归测试往往占用了大量的测试与开发调试精力。每当更新 SDK 版本、重构基础组件库或修复上游逻辑时,手点界面进行测试不仅效率低下,且容易因遗漏边界场景导致缺陷漏测。

为了解决这一问题,HarmonyOS 官方测试体系提供了 DevEco Testing 与 Hypium 自动化测试框架。通过基于 @ohos.UiTest API 与 Hypium 测试单元编写自动化测试脚本,可以将重复的界面交互操作转化为标准的代码逻辑,实现自动化回归测试。

本文将以标准的“登录业务流程”为例,演示如何基于 ArkUI 的 UI 控件标识进行抓取,并通过 Hypium 框架完成自动输入、点击、界面等待、状态断言以及异常场景回归。

一、Hypium 与 UI 自动化测试体系概述

HarmonyOS 的 UI 自动化测试主要基于两个核心部分组成:

  1. Hypium 框架:提供测试用例的管理能力,支持标准的测试生命周期管理(如 describe、it、beforeAll、afterAll 等)以及断言库。

  2. @ohos.UiTest 模块:提供基于 UI 控件定位与模拟操作的 API。核心类包括:

    • Driver:UI 测试的控制驱动对象,用于查找控件、屏幕滑动、延时等待及元素存在性断言。
    • ON:用于描述控件特征的选择器,支持通过控件 ID、文本、类型等属性组合定位 UI 元素。
    • Component:具体的 UI 界面控件实例,提供点击(click)、文本输入(inputText)、清空文本(clearText)等交互方法。

依赖这套体系,自动化脚本能够模拟真实用户在设备上的触控与键盘输入行为,实现对界面 UI 状态变化及业务逻辑结果的快速校验。

二、被测界面与控件标识配置

UI 自动化脚本能够精准找到控件的前提,是 ArkUI 页面组件定义了清晰且唯一的控件标识。在 ArkUI 中,推荐使用 .id() 属性为测试关键控件进行标记。

1. 登录页面 ArkUI 示例

下面是一个简化的登录页面代码,主要包含:用户名输入框、密码输入框、登录按钮以及状态提示文本。

// entry/src/main/ets/pages/LoginPage.ets
@Entry
@Component
struct LoginPage {
  @State username: string = '';
  @State password: string = '';
  @State loginStatus: string = '未登录';
  @State isError: boolean = false;

  build() {
    Column({ space: 16 }) {
      Text('账号登录')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      // 用户名输入框
      TextInput({ placeholder: '请输入用户名', text: this.username })
        .id('input_username')
        .width('80%')
        .onChange((value: string) => {
          this.username = value;
        })

      // 密码输入框
      TextInput({ placeholder: '请输入密码', text: this.password })
        .id('input_password')
        .type(InputType.Password)
        .width('80%')
        .onChange((value: string) => {
          this.password = value;
        })

      // 登录按钮
      Button('登录')
        .id('btn_login')
        .width('80%')
        .onClick(() => {
          this.handleLogin();
        })

      // 状态反馈文本
      Text(this.loginStatus)
        .id('txt_login_status')
        .fontSize(16)
        .fontColor(this.isError ? Color.Red : Color.Green)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  private handleLogin(): void {
    if (this.username === 'admin' && this.password === '123456') {
      this.loginStatus = '登录成功';
      this.isError = false;
    } else {
      this.loginStatus = '用户名或密码错误';
      this.isError = true;
    }
  }
}

在上面的代码中,通过 .id('input_username')、.id('input_password') 和 .id('btn_login') 明确指明了 UI 测试要寻找的目标对象。这种定位方式比纯依赖文字匹配更加稳定,不易受到国际化或 UI 文本微调的影响。

三、编写 Hypium UI 自动化测试用例

自动化测试脚本通常放置在工程目录下的 entry/src/ohosTest/ets/test 模块内。系统会基于单元测试执行器拉起应用并执行脚本。

下面展示一个完整的测试套件,涵盖以下验证场景:

  1. 成功登录流程(查找控件 -> 输入 -> 点击 -> 延迟等待 -> 断言文本)。
  2. 异常登录流程(错误密码 -> 模拟输入 -> 断言错误提示)。
  3. 多次重复测试逻辑。
// entry/src/ohosTest/ets/test/LoginUiTest.test.ets
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
import { Driver, ON, Component } from '@ohos.UiTest';

export default function loginUiTest() {
  describe('LoginUiTestSuite', () => {

    beforeAll(async () => {
      // 在所有用例开始前,初始化驱动或拉起应用
    });

    /**
     * 测试用例:模拟正常账号密码登录成功
     */
    it('testLoginSuccess', 0, async (done: Function) => {
      // 1. 获取 UiTest 驱动实例
      let driver: Driver = Driver.create();
      await driver.delayMs(1000);

      // 2. 查找用户名与密码输入框控件
      let usernameInput: Component = await driver.findComponent(ON.id('input_username'));
      let passwordInput: Component = await driver.findComponent(ON.id('input_password'));
      let loginBtn: Component = await driver.findComponent(ON.id('btn_login'));

      // 检查控件是否存在
      expect(usernameInput !== null).assertTrue();
      expect(passwordInput !== null).assertTrue();
      expect(loginBtn !== null).assertTrue();

      // 3. 自动输入正确凭据
      await usernameInput.inputText('admin');
      await passwordInput.inputText('123456');

      // 4. 自动点击登录按钮
      await loginBtn.click();

      // 5. 等待页面状态更新/UI组件渲染
      await driver.delayMs(1000);

      // 6. 断言结果:校验 txt_login_status 的文本内容是否改变为 "登录成功"
      await driver.assertComponentExist(ON.id('txt_login_status').text('登录成功'));

      done();
    });

    /**
     * 测试用例:模拟错误密码登录失败场景
     */
    it('testLoginFailure', 0, async (done: Function) => {
      let driver: Driver = Driver.create();
      await driver.delayMs(1000);

      let usernameInput: Component = await driver.findComponent(ON.id('input_username'));
      let passwordInput: Component = await driver.findComponent(ON.id('input_password'));
      let loginBtn: Component = await driver.findComponent(ON.id('btn_login'));

      // 清空原有内容并重置测试输入
      await usernameInput.clearText();
      await passwordInput.clearText();

      // 输入错误数据
      await usernameInput.inputText('admin');
      await passwordInput.inputText('wrong_password');

      // 触发登录
      await loginBtn.click();
      await driver.delayMs(1000);

      // 断言是否出现错误提示
      await driver.assertComponentExist(ON.id('txt_login_status').text('用户名或密码错误'));

      done();
    });

    /**
     * 测试用例:多次重复执行压力/稳定性验证
     */
    it('testRepeatedLoginAttempts', 0, async (done: Function) => {
      let driver: Driver = Driver.create();
      await driver.delayMs(1000);

      let usernameInput: Component = await driver.findComponent(ON.id('input_username'));
      let passwordInput: Component = await driver.findComponent(ON.id('input_password'));
      let loginBtn: Component = await driver.findComponent(ON.id('btn_login'));

      const repeatTimes = 3;
      for (let i = 0; i < repeatTimes; i++) {
        await usernameInput.clearText();
        await usernameInput.inputText(`test_user_${i}`);
        
        await passwordInput.clearText();
        await passwordInput.inputText('error_pass');

        await loginBtn.click();
        await driver.delayMs(500);

        // 每次循环断言结果一致性
        await driver.assertComponentExist(ON.id('txt_login_status').text('用户名或密码错误'));
      }

      done();
    });

  });
}

四、核心流程与关键接口拆解

1. 驱动与组件查找(Driver & ON)

在 Hypium 中,UI 测试的控制通过 Driver.create() 获取全局驱动控制器。在查找元素时,推荐优先使用 ON.id(),其次是 ON.text(),也可以通过链式调用的方式增加条件过滤,如:

ON.id('btn_login').type('Button').enabled(true)

这种方式可以大幅降低因为 UI 重构导致的组件找寻失配问题。

2. 异步交互操作与状态模拟

所有的 @ohos.UiTest 行为(如点击、清空、文本输入、延时)均为异步操作,必须配合 async/await 使用,以保证测试动作的执行次序正确。例如:

  • await component.clearText():在重新填充内容之前清空现有组件框内的文本。
  • await component.inputText('text'):向获取焦点的 TextInput 输入指定字符。
  • await component.click():在控件中心坐标触发单次点击事件。

3. 等待机制与断言

由于 UI 渲染、网络请求或数据响应依赖异步调度,点击操作发生后界面不一定会瞬间更新。

  • await driver.delayMs(milliseconds):可以通过固定延时等待页面加载。
  • await driver.assertComponentExist(ON...):断言界面当前视图树中必须包含符合匹配条件的 UI 控件,若未能找到对应元素,测试框架将自动判定该用例失败。

五、自动化测试中的关键注意事项

  1. 页面可搜索性与组件隐藏:
    如果页面控件处于屏幕可视区域外(例如长列表内未绘制的 Item),findComponent 可能无法直接返回。此时可以通过 driver.scrollSearch() 等滑动检索能力定位目标控件后再进行交互。

  2. 硬件弹窗与系统权限干扰:
    自动化测试流程极易被系统弹窗(例如“网络访问权限许可”、“通知权限申请”)阻断。可以在测试套件的 beforeAll 中提前注入权限申请代理逻辑,或使用 UiDriver 提前响应系统弹窗。

  3. 测试数据隔离与清理:
    正如测试用例 testLoginFailure 所示,在进行多步连续测试时,必须通过 clearText() 等方式清理上一次用例留下的缓存或状态输入,避免测试用例之间产生耦合干扰。

  4. 避免过分依赖 UI 文本:
    如果应用计划做多语言支持,断言尽量避开对繁杂的中文文本匹配,可以结合 .id() 匹配以及业务变量状态联动校验,提升自动化脚本的可维护性。

六、从手工回归到自动化回归的演进

通过将针对“登录/搜索/提交”等频繁变动的业务路径整理成 Hypium UI 测试脚本,开发与测试团队可以将手动点击校验沉淀为代码化的工程能力。

每次构建应用包包体、更新 API Level 或执行 CI/CD 持续集成流程时,在工程中直接运行 ohosTest 任务,即可自动唤醒模拟器或真机完成核心路径的验证,快速输出测试结果报告,从而避免因人工测试疏漏将基础功能回归问题遗留至发布阶段。

参考资料

  1. 《自动化测试框架 Hypium 概化与指导》 华为开发者联盟
    链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ohtest-hypium-0000001774120158
  2. 《@ohos.UiTest (UI测试) API 参考》 华为开发者联盟
    链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-uitest-0000001774281582
  3. 《ArkUI 组件通用属性:id》 华为开发者联盟
    链接:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-component-id-0000001821000801

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐