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 version

出现版本号表示安装成功。

配置 HDC 路径

如果 hdc 不在系统 PATH 中,你可以设置 HDC_HOME 环境变量指向 HDC 所在目录:

export HDC_HOME=/path/to/hdc/directory

启用开发者模式并验证设备

在 HarmonyOS 设备的设置中进入 开发者选项,开启 USB 调试,然后用数据线连接设备。

验证连接:

hdc list targets

出现设备 ID 代表连接成功:

0123456789ABCDEF

试用 Playground(零代码)

Playground 是验证连接、观察 AI Agent 的最快方式,无需编写代码。它与 @midscene/harmony 共享相同的代码实现,因此在 Playground 上验证通过的流程,用脚本运行时也完全一致。

  1. 启动 Playground CLI:
npx --yes @midscene/harmony-playground
  1. 点击 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
yarn
pnpm
bun
deno
npm install @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 步:运行示例

npx tsx demo.ts

第 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 覆盖。