为啥点了“运行”,我还得手点来回测?——用 DevEco Studio 做自动化 UI 端到端测试(E2E)一把梭

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
❓ 前言 🧪🚀
写 ArkUI/ArkTS 应用最怕啥?改一行样式、加一个事件,结果某个页面交互“隐性报废”,只有上线后才被用户一顿差评😵。别再“手点回归”内耗啦——这篇手把手用 DevEco Studio 带你搭起 UI 自动化端到端测试:
- ✅ 测试工具配置与工程结构
- ✅ UI 自动化脚本编写与执行(ArkTS)
- ✅ 断言与结果校验、失败截图、稳定性“去抖动”
- ✅ 示例:验证页面交互(输入、点击、滚动、跳转、Toast 校验)
读完你就能把「打开 App → 点击/输入/滑动 → 断言 UI」全自动跑起来,夜里让机器搬砖,白天你专心写更酷的功能😉。
🧭 目录
- 🧰 一、DevEco Studio 测试工具配置与使用
- 🧱 二、测试工程结构与关键依赖
- ✍️ 三、编写 UI 自动化测试:用例、选择器、手势、断言
- 🧪 四、示例:端到端脚本验证页面交互(可直接粘贴跑)
- 🧷 五、断言与结果校验:日志、截图、等待策略
- ⚙️ 六、执行与集成:本地、真机/模拟器、CI 持续集成
- 🧠 七、稳定性与性能:消除“脆弱测试”的 10 条建议
- ✅ 八、常用选择器与 API 速查表
🧰 一、DevEco Studio 测试工具配置与使用
1) 开启测试能力
- 打开 DevEco Studio(下称 DES),
File → New → Module,选择你已有应用的 Entry 模块,确保启用了 Test 目录(若模板未生成,手动创建src/test/ets)。 - 连接 模拟器或真机(推荐真机,UI 延迟与动画更真实)。
2) 安装依赖(ArkTS 测试)
在 oh-package.json5 中加入常用 UI 测试与断言框架依赖(版本号按你 SDK 对应调整):
{
"dependencies": {
"@ohos/hypium": "^1.0.0", // 测试框架:describe/it/expect
"@ohos/uitest": "^2.0.0" // UI 自动化:选择器/驱动/手势
}
}
@ohos/hypium提供 BDD 风格用例与断言,@ohos/uitest提供 UI Driver(查找控件、点击输入、滑动、截图)。
3) 运行入口
- 在 Run/Debug Configurations 中新增 Module Tests(选择 Entry 模块)。
- 工具栏选择刚创建的测试配置,点 ▶️ 运行;结果在 Run 或 Tests 窗口查看。
🧱 二、测试工程结构与关键依赖
典型结构(仅测试相关):
entry/
src/
main/ets/... # 你的业务代码
test/ets/ # UI/E2E 测试
e2e/
page-objects/ # 页面对象(可选)
HomePage.ts
DetailPage.ts
utils/
waiters.ts # 通用等待/重试
reporters.ts # 日志/截图工具
home.e2e.spec.ets # 示例:首页交互测试
detail.e2e.spec.ets # 示例:详情页交互测试
✍️ 三、编写 UI 自动化测试:用例、选择器、手势、断言
1) 基本模板(Hypium)
import { describe, it, beforeAll, afterAll, expect } from '@ohos/hypium'
import uitest from '@ohos/uitest'
let driver: uitest.UiDriver
describe('E2E: Home interactions', () => {
beforeAll(async () => {
driver = await uitest.getUiDriver() // 连接被测应用
})
it('should show initial title', async () => {
const title = await driver.findComponent(uitest.By.text('首页'))
expect(!!title).assertTrue()
})
})
2) 常用选择器
By.text('文案'):按可见文本By.id('countText'):按组件 id(确保你在 UI 代码中为关键节点设置 id)By.type('Button'):按类型By.desc('描述'):按无障碍描述(contentDescription)- 可链式:
By.id('list').hasChild(By.text('标题1'))
3) 手势与输入
await btn.click()
await input.inputText('Hello ArkUI')
await driver.swipe({ fromX: 300, fromY: 1200, toX: 300, toY: 300, duration: 300 })
await driver.scrollTo(By.text('加载更多')) // 列表滚动直达
4) 等待与重试(很关键)
UI 有动画/网络延迟,直接找元素容易“抢早”。建议封装等待工具:
export async function waitFor(driver: uitest.UiDriver, selector: uitest.Selector, timeout=3000) {
const t0 = Date.now()
while (Date.now() - t0 < timeout) {
const node = await driver.findComponent(selector, 200)
if (node) return node
await new Promise(r => setTimeout(r, 100))
}
throw new Error(`waitFor timeout: ${selector}`)
}
🧪 四、示例:端到端脚本验证页面交互(可直接用)
目标页面(示例):
- 一个计数器:文本 id 为
countText、按钮文案+1(点击自增)- 一个输入框 id
nameInput,按钮提交,点击后弹出 Toast “已提交:{name}”- 列表可滚动到“加载更多”,点击后出现 “已加载更多”
业务页面(示意 ArkTS UI,便于你对照测试脚本设置 id):
// 业务侧示例(片段):请确保关键节点设置 id
@Entry
@Component
struct HomePage {
@State count: number = 0
@State name: string = ''
@State more: boolean = false
build() {
Column() {
Text(`计数:${this.count}`).id('countText').fontSize(20)
Button('+1').onClick(() => this.count++)
TextInput({ placeholder: '输入姓名' })
.id('nameInput')
.onChange(v => this.name = v)
Button('提交').onClick(() => {
prompt.showToast({ message: `已提交:${this.name}` })
})
List().width('100%').height(400).id('list') {
ForEach(Array.from({ length: 30 }, (_, i) => i), (i: number) => ListItem() {
Text(`条目 ${i}`)
})
ListItem() {
if (!this.more) {
Button('加载更多').id('loadMoreBtn').onClick(() => this.more = true)
} else {
Text('已加载更多').id('loadedTag')
}
}
}
}.padding(16)
}
}
测试脚本(E2E):entry/src/test/ets/e2e/home.e2e.spec.ets
import { describe, it, beforeAll, afterAll, expect } from '@ohos/hypium'
import uitest from '@ohos/uitest'
async function waitFor(driver: uitest.UiDriver, selector: uitest.Selector, timeout=4000) {
const t0 = Date.now()
while (Date.now() - t0 < timeout) {
const node = await driver.findComponent(selector, 200)
if (node) return node
await new Promise(r => setTimeout(r, 80))
}
throw new Error(`waitFor timeout: ${selector}`)
}
let driver: uitest.UiDriver
describe('E2E: Home interactions', () => {
beforeAll(async () => {
driver = await uitest.getUiDriver()
})
it('计数器点击自增', async () => {
const label = await waitFor(driver, uitest.By.id('countText'))
const btn = await waitFor(driver, uitest.By.text('+1'))
const before = await label.getText()
await btn.click()
const after = await label.getText()
// 断言:数字增加
const n1 = parseInt(before.replace(/\D+/g, '') || '0', 10)
const n2 = parseInt(after.replace(/\D+/g, '') || '0', 10)
expect(n2 > n1).assertTrue()
})
it('输入 + 提交后出现 Toast', async () => {
const input = await waitFor(driver, uitest.By.id('nameInput'))
await input.inputText('Rebecca')
const submit = await waitFor(driver, uitest.By.text('提交'))
await submit.click()
// Toast 捕获(不同系统版本可能表现不同:用 By.text 配合短等待)
const toast = await driver.findComponent(uitest.By.text('已提交:Rebecca'), 1500)
expect(!!toast).assertTrue()
})
it('滚动到“加载更多”并验证加载完成状态', async () => {
const list = await waitFor(driver, uitest.By.id('list'))
// 滚动到底(多次小步,减少一次性大滑引发的 flake)
for (let i = 0; i < 4; i++) {
await driver.scroll(list, { dx: 0, dy: -600, speed: 300 }) // 上滑(坐标系以设备为准)
}
const moreBtn = await waitFor(driver, uitest.By.id('loadMoreBtn'))
await moreBtn.click()
const loaded = await waitFor(driver, uitest.By.id('loadedTag'))
expect(await loaded.getText()).assertEqual('已加载更多')
})
afterAll(async () => {
// 失败时可截图留证(示例:无论成功失败都存一张)
const png = await driver.takeScreenshot()
// 测试框架通常把文件保存在应用沙箱,可按需要写入固定路径或上传到 CI 附件
console.info(`screenshot bytes: ${png?.byteLength ?? 0}`)
})
})
这份脚本涵盖:查找元素 → 点击/输入 → 滚动 → 断言文本/Toast。
运行方式:在 DES 选择测试运行配置,点 ▶️,连接模拟器或真机即可。
🧷 五、断言与结果校验:日志、截图、等待策略
-
断言库(Hypium)
expect(x).assertTrue()/assertEqual(a, b)/assertContain(sub, str)
-
等待策略
- 固定等待不可取:避免
sleep(2000)。用waitFor(selector, timeout)轮询; - 交互后用小延时 + 轮询,上限超时即失败,避免卡死;
- 固定等待不可取:避免
-
失败截图
driver.takeScreenshot()获取ArrayBuffer,保存为文件(便于 CI 附件);
-
稳定日志
- 在关键步骤
console.info('step: ...');失败回看 Run 窗口/设备日志(hilog)定位。
- 在关键步骤
⚙️ 六、执行与集成:本地、真机/模拟器、CI
-
本地运行:
- 连接设备(USB/同网段模拟器),在 DES 选择测试配置运行;
- 多设备并行:建议一次一台,避免抢占/竞争引发 flake;
-
CI 持续集成(思路):
- 在构建机安装 SDK/CLI,启动 Headless 模拟器 或接入农场设备;
- 命令行触发测试任务,收集 JUnit/自定义 测试报告 + 截图附件;
- 失败重跑策略:失败用例重跑 1 次过滤偶发抖动。
🧠 七、稳定性与性能:消除“脆弱测试”的 10 条建议
- 为关键控件设置稳定 id,少依赖易变文案;
- 所有交互后做等待(轮询 + 超时),别用硬 sleep;
- 切小动作:大滑动拆成多次短滑,减少越界;
- Toast/弹窗 断言给更宽松时间窗(1–2 秒);
- 网络依赖 mock 或固定测试环境,降低不确定性;
- 动画影响:可在测试构建里降低时长(配置依项目而定);
- 列表/虚拟化:先滚动到可见,再断言;
- 并行度:单设备串行执行,避免抢焦点;
- 失败立刻截图 + 打点日志;
- Page Object 模式:把选择器和操作封装成页面对象,减少脚本重复与维护成本。
✅ 八、常用选择器与 API 速查表
| 能力 | 用法(示例) | 说明 |
|---|---|---|
| 获取驱动 | driver = await uitest.getUiDriver() |
连接被测应用 |
| 找元素 | await driver.findComponent(By.id('xxx'), 1000) |
支持 By.text/desc/id/type |
| 点击 | await node.click() |
单击 |
| 长按 | await node.longClick(800) |
毫秒 |
| 输入 | await node.inputText('Hello') |
输入字符 |
| 滑动 | await driver.swipe({ fromX, fromY, toX, toY, duration }) |
屏幕坐标滑动 |
| 滚动到 | await driver.scrollTo(By.text('目标')) |
列表滚动查找 |
| 获取文本 | await node.getText() |
用于断言 |
| 截图 | await driver.takeScreenshot() |
返回二进制数据 |
| 等待 | await driver.wait(By.id('xxx'), 3000) |
某些版本提供内建 wait,可用自封装 |
实际 API 以你当前 SDK/插件版本为准;命名上可能存在小差异,但能力与思路一致。
🎉 收尾
自动化 UI 测试的价值不在“能不能跑”,而在“稳定、可读、可维护”。用 DevEco Studio + ArkTS 测试栈,把“点击—输入—滚动—断言”固化成脚本,每次提交自动跑一遍,你就能放心地重构 UI、改动交互而不怕“牵一发而动全身”。
反问一句:当回归从一下午缩短到十几分钟,你会把多出来的时间用来修一个老痛点,还是上一个新功能?😉
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-
- 点赞
- 收藏
- 关注作者
评论(0)