• 简体中文
  • HarmonyOS

    Midscene 通过 HarmonyOS Device Connector(HDC)连接 HarmonyOS NEXT 设备,可自动化 App 和系统界面。

    本指南介绍设备连接、模型配置、Playground 体验,以及 @midscene/harmony 的 JavaScript SDK 集成。

    效果展示

    提示词: 打开设置,找到“关于手机”,查看设备信息。

    查看完整报告,或浏览更多 Midscene 案例

    快速开始

    准备 HarmonyOS 设备

    在编写脚本前,先确认 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 是验证连接的最快方式。无需编写代码,即可体验 aiActaiQueryaiAssert 等核心能力。它与 @midscene/harmony 共享相同的核心,因此在 Playground 中通过的流程,在脚本中运行会保持一致。

    1. 启动 Playground CLI:
    npx --yes @midscene/harmony-playground
    1. 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有 API Key,请回到 模型配置 获取。

    使用 JavaScript SDK

    当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。

    配置模型

    通过环境变量设置模型。选择模型时,请参考模型策略

    export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1"
    export MIDSCENE_MODEL_API_KEY="替换为你的 API Key"
    export MIDSCENE_MODEL_NAME="替换为你的模型名称"
    export MIDSCENE_MODEL_FAMILY="替换为你的模型系列"

    全部配置项请参考模型配置

    安装依赖

    npm
    yarn
    pnpm
    bun
    deno
    npm install @midscene/harmony dotenv --save-dev

    编写脚本

    下面的示例会在设备上打开设置应用,并执行滚动操作。

    ./demo.ts
    import 'dotenv/config'; // 通过 dotenv/config 自动加载 .env 文件中的环境变量
    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('页面中有设置项列表');
      })(),
    );

    运行脚本

    npx tsx demo.ts

    查看报告

    脚本成功后会输出 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(
          '切换蓝牙状态一次,如果蓝牙关闭则打开,否则关闭。',
        );
      });
    });
    Info

    合并报告默认存放在 midscene_run/report 目录。在 CI 中运行时可通过 MIDSCENE_RUN_DIR 覆盖。

    常见问题

    输入后键盘没有隐藏,或页面发生返回

    Midscene 在输入文本后会自动隐藏键盘。HarmonyOS 默认发送 ESC,以减少触发页面返回的概率。如果你的应用里 ESC 无法关闭键盘,可以切换为优先使用 Back:

    const device = new HarmonyDevice('device-id', {
      keyboardDismissStrategy: 'back-first',
    });

    如果你的输入框监听了 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',
    });