HarmonyOS 开始使用
本指南将带你完成使用 Midscene 自动化 HarmonyOS 设备所需的一切:通过 HDC 连接真机、配置模型 API Key、体验零代码 Playground,并运行你的首个 JavaScript 脚本。
配置 AI 模型服务
将你的模型配置写入环境变量,可参考 模型策略 了解更多细节。
export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1"
export MIDSCENE_MODEL_API_KEY="替换为你的 API Key"
export MIDSCENE_MODEL_NAME="替换为你的模型名称"
export MIDSCENE_MODEL_FAMILY="替换为你的模型系列"
更多配置信息请参考 模型策略 和 模型配置。
准备工作
在编写脚本前,先确认 HDC 能够连接设备且设备信任当前电脑。
安装 HDC
HDC(HarmonyOS Device Connector)是 HarmonyOS 提供的命令行工具,用于与 HarmonyOS 设备通信。安装方式:
验证 HDC 是否安装成功:
出现版本号表示安装成功。
配置 HDC 路径
如果 hdc 不在系统 PATH 中,你可以设置 HDC_HOME 环境变量指向 HDC 所在目录:
export HDC_HOME=/path/to/hdc/directory
启用开发者模式并验证设备
在 HarmonyOS 设备的设置中进入 开发者选项,开启 USB 调试,然后用数据线连接设备。
验证连接:
出现设备 ID 代表连接成功:
试用 Playground(零代码)
Playground 是验证连接、观察 AI Agent 的最快方式,无需编写代码。它与 @midscene/harmony 共享相同的代码实现,因此在 Playground 上验证通过的流程,用脚本运行时也完全一致。
- 启动 Playground CLI:
npx --yes @midscene/harmony-playground
- 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有 API Key,请回到 模型配置 获取。
开始体验
配置完成后,你可以立即开始体验 Midscene。它提供了多个关键操作 Tab:
- Act: 与网页进行交互,这就是自动规划(Auto Planning),对应于
aiAct 方法。比如
在搜索框中输入 Midscene,执行搜索,跳转到第一条结果
- Query: 从界面中提取 JSON 结构的数据,对应于
aiQuery 方法。
类似的方法还有 aiBoolean(), aiNumber(), aiString(),用于直接提取布尔值、数字和字符串。
提取页面中的用户 ID,返回 { id: string } 结构的 JSON 数据
- Assert: 理解页面,进行断言,如果不满足则抛出错误,对应于
aiAssert 方法。
页面上存在一个登录按钮,它的下方有一个用户协议的链接
- Tap: 在某个元素上点击,这就是即时操作(Instant Action),对应于
aiTap 方法。
关于自动规划(Auto Planning)和即时操作(Instant Action)的区别,请参考 API 文档。
集成 Midscene Agent
当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。
第 1 步:安装依赖
npm install @midscene/harmony --save-dev
yarn add @midscene/harmony --save-dev
pnpm add @midscene/harmony --save-dev
bun add @midscene/harmony --save-dev
deno add npm:@midscene/harmony --save-dev
第 2 步:编写脚本
下面的示例会在设备上打开设置应用,并执行滚动操作。
./demo.ts
import {
HarmonyAgent,
HarmonyDevice,
getConnectedDevices,
} from '@midscene/harmony';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
Promise.resolve(
(async () => {
const devices = await getConnectedDevices();
const device = new HarmonyDevice(devices[0].deviceId, {});
const agent = new HarmonyAgent(device, {
aiActionContext:
'这是一台鸿蒙设备,系统语言为中文。如果出现弹窗,点击同意或关闭。',
});
await device.connect();
// 打开设置应用
await agent.launch('com.huawei.hmos.settings');
await sleep(2000);
// 向下滚动列表
await agent.aiAct('scroll down one screen');
// 查询页面内容
const items = await agent.aiQuery(
'string[], 列表中可见的所有设置项名称',
);
console.log('设置项列表', items);
// 断言
await agent.aiAssert('页面中有设置项列表');
})(),
);
第 3 步:运行示例
第 4 步:查看报告
脚本成功后会输出 Midscene - report file updated: /path/to/report/some_id.html。在浏览器中打开该 HTML 文件即可回放每一步交互、查询与断言。
进阶
当你需要自定义设备行为、把 Midscene 接入独立框架,或排查 HDC 问题时,可参考本节内容。更多构造函数参数可前往 API 参考(HarmonyOS)。
扩展 HarmonyOS 上的 Midscene
使用 defineAction() 定义自定义手势,并通过 customActions 传入。Midscene 会把自定义动作追加到规划器中,让 AI 可以调用你领域特定的动作名。
import { getMidsceneLocationSchema, z } from '@midscene/core';
import { defineAction } from '@midscene/core/device';
import { HarmonyAgent, HarmonyDevice, getConnectedDevices } from '@midscene/harmony';
const ContinuousClick = defineAction({
name: 'continuousClick',
description: 'Click the same target repeatedly',
paramSchema: z.object({
locate: getMidsceneLocationSchema(),
count: z.number().int().positive().describe('How many times to click'),
}),
async call(param) {
const { locate, count } = param;
console.log('click target center', locate.center);
console.log('click count', count);
},
});
const devices = await getConnectedDevices();
const device = new HarmonyDevice(devices[0].deviceId, {});
await device.connect();
const agent = new HarmonyAgent(device, {
customActions: [ContinuousClick],
});
await agent.aiAct('click the red button five times');
关于自定义动作和动作 Schema 的更多解释,请参阅 与任意界面集成。
更多
完整示例(Vitest + HarmonyAgent)
import type { TestStatus } from '@midscene/core';
import { ReportMergingTool } from '@midscene/core/report';
import { sleep } from '@midscene/core/utils';
import {
HarmonyAgent,
HarmonyDevice,
getConnectedDevices,
} from '@midscene/harmony';
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
it,
} from 'vitest';
describe('HarmonyOS Settings Test', () => {
let device: HarmonyDevice;
let agent: HarmonyAgent;
let itTestStatus: TestStatus = 'passed';
const reportMergingTool = new ReportMergingTool();
beforeAll(async () => {
const devices = await getConnectedDevices();
device = new HarmonyDevice(devices[0].deviceId);
await device.connect();
});
beforeEach((ctx) => {
agent = new HarmonyAgent(device, {
groupName: ctx.task.name,
});
});
afterEach((ctx) => {
if (ctx.task.result?.state === 'pass') {
itTestStatus = 'passed';
} else if (ctx.task.result?.state === 'skip') {
itTestStatus = 'skipped';
} else if (ctx.task.result?.errors?.[0].message.includes('timed out')) {
itTestStatus = 'timedOut';
} else {
itTestStatus = 'failed';
}
reportMergingTool.append({
reportFilePath: agent.reportFile as string,
reportAttributes: {
testId: `${ctx.task.name}`,
testTitle: `${ctx.task.name}`,
testDescription: 'description',
testDuration: (Date.now() - ctx.task.result?.startTime!) | 0,
testStatus: itTestStatus,
},
});
});
afterAll(() => {
reportMergingTool.mergeReports('my-harmony-setting-test-report');
});
it('toggle WLAN', async () => {
await device.home();
await sleep(1000);
await device.launch('com.huawei.hmos.settings');
await sleep(1000);
await agent.aiAct('找到并进入 WLAN 设置');
await agent.aiAct(
'切换 WLAN 状态一次,如果 WLAN 关闭则打开,否则关闭。',
);
});
it('toggle Bluetooth', async () => {
await device.home();
await sleep(1000);
await device.launch('com.huawei.hmos.settings');
await sleep(1000);
await agent.aiAct('找到并进入蓝牙设置');
await agent.aiAct(
'切换蓝牙状态一次,如果蓝牙关闭则打开,否则关闭。',
);
});
});
Tip
合并报告默认存放在 midscene_run/report 目录。在 CI 中运行时可通过 MIDSCENE_RUN_DIR 覆盖。
常见问题
输入文本后,输入框内容被清空或消失
Midscene 在输入文本后会自动隐藏键盘(默认发送 BACK 按键事件)。如果你的输入框监听了 BACK 事件并执行了清空或关闭操作,可以关闭自动隐藏键盘:
const device = new HarmonyDevice('device-id', {
autoDismissKeyboard: false,
});
关闭后键盘不会自动隐藏,你可以使用 aiAct 指令手动隐藏键盘,例如 await agent.aiAct('隐藏键盘')。
如何使用自定义的 HDC 路径?
通过 HDC_HOME 环境变量指定 HDC 所在目录:
export HDC_HOME=/path/to/hdc/directory
也可以通过构造函数传入:
const device = new HarmonyDevice('0123456789ABCDEF', {
hdcPath: '/path/to/hdc',
});