Skip to content

Commit f04e2f9

Browse files
committed
refactor(harmony): update app name mapping and improve device handling logic
1 parent a502c36 commit f04e2f9

6 files changed

Lines changed: 74 additions & 119 deletions

File tree

‎packages/harmony/src/appNameMapping.ts‎

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,10 @@ export const defaultAppNameMapping: Record<string, string> = {
3333
天气: 'com.huawei.hmos.weather',
3434
Weather: 'com.huawei.hmos.weather',
3535

36-
// Common third-party apps
37-
微信: 'com.tencent.mm',
38-
WeChat: 'com.tencent.mm',
39-
支付宝: 'com.eg.android.AlipayGphone',
40-
淘宝: 'com.taobao.taobao',
41-
京东: 'com.jingdong.app.mall',
36+
// Common third-party apps (HarmonyOS NEXT bundle names)
4237
抖音: 'com.ss.hm.ugc.aweme',
43-
小红书: 'com.xingin.xhs',
44-
bilibili: 'tv.danmaku.bili',
45-
QQ: 'com.tencent.mobileqq',
46-
微博: 'com.sina.weibo',
47-
高德地图: 'com.autonavi.minimap',
48-
百度地图: 'com.baidu.BaiduMap',
49-
美团: 'com.sankuai.meituan',
50-
饿了么: 'me.ele',
51-
飞书: 'com.ss.android.lark',
52-
豆包: 'com.larus.nova',
53-
网易云音乐: 'com.netease.cloudmusic',
54-
QQ音乐: 'com.tencent.qqmusic',
55-
知乎: 'com.zhihu.android',
56-
今日头条: 'com.ss.android.article.news',
57-
Keep: 'com.gotokeep.keep',
38+
支付宝: 'com.alipay.mobile.client',
39+
高德地图: 'com.amap.hmapp',
40+
百度: 'com.baidu.baiduapp',
41+
携程: 'com.ctrip.harmonynext',
5842
};

‎packages/harmony/src/device.ts‎

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export type { HarmonyDeviceOpt } from '@midscene/core/device';
3636
const defaultScrollUntilTimes = 10;
3737
const defaultSwipeSpeed = 600;
3838
const defaultFastSwipeSpeed = 2000;
39+
const maxScrollDistance = 9999999;
40+
const scrollQuadrantDivisions = 4;
3941

4042
const debugDevice = getDebug('harmony:device');
4143

@@ -128,7 +130,7 @@ export class HarmonyDevice implements AbstractInterface {
128130
await this.scrollUntilLeft(startingPoint);
129131
} else if (scrollToEventName === 'singleAction' || !scrollToEventName) {
130132
if (param?.direction === 'down' || !param || !param.direction) {
131-
await this.scrollDown(param?.distance || undefined, startingPoint);
133+
await this.scrollDown(param?.distance ?? undefined, startingPoint);
132134
} else if (param.direction === 'up') {
133135
await this.scrollUp(param.distance || undefined, startingPoint);
134136
} else if (param.direction === 'left') {
@@ -257,7 +259,6 @@ export class HarmonyDevice implements AbstractInterface {
257259
}
258260

259261
this.connecting = (async () => {
260-
let error: Error | null = null;
261262
debugDevice(`Initializing HDC with device ID: ${this.deviceId}`);
262263
try {
263264
this.hdc = new HdcClient({
@@ -273,16 +274,10 @@ export class HarmonyDevice implements AbstractInterface {
273274
return this.hdc;
274275
} catch (e) {
275276
debugDevice(`Failed to initialize HDC: ${e}`);
276-
error = new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
277+
throw new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
277278
} finally {
278279
this.connecting = null;
279280
}
280-
281-
if (error) {
282-
throw error;
283-
}
284-
285-
throw new Error('HDC initialization failed unexpectedly');
286281
})();
287282

288283
return this.connecting;
@@ -320,8 +315,9 @@ export class HarmonyDevice implements AbstractInterface {
320315
const bundleName = this.resolvePackageName(uri) ?? uri;
321316
try {
322317
await hdc.startAbility(bundleName, 'EntryAbility');
323-
} catch {
324-
// EntryAbility failed, auto-discover the main ability
318+
} catch (e: any) {
319+
if (!e.message?.includes('resolve ability')) throw e;
320+
// EntryAbility not found, auto-discover the main ability
325321
const mainAbility = await hdc.queryMainAbility(bundleName);
326322
if (!mainAbility) {
327323
throw new Error(
@@ -480,7 +476,7 @@ export class HarmonyDevice implements AbstractInterface {
480476
async keyboardPress(key: string): Promise<void> {
481477
// HarmonyOS uitest only accepts Back/Home/Power as string names.
482478
// All other keys must use numeric keycodes.
483-
const keyMap: Record<string, string> = {
479+
const harmonyKeyCodeMap: Record<string, string> = {
484480
Enter: '2054',
485481
Backspace: '2055',
486482
Tab: '2049',
@@ -495,14 +491,14 @@ export class HarmonyDevice implements AbstractInterface {
495491
};
496492

497493
const normalizedKey = this.normalizeKeyName(key);
498-
const harmonyKey = keyMap[normalizedKey] || key;
494+
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
499495

500496
const hdc = await this.getHdc();
501497
await hdc.keyEvent(harmonyKey);
502498
}
503499

504500
private normalizeKeyName(key: string): string {
505-
const keyMap: Record<string, string> = {
501+
const keyNameAliasMap: Record<string, string> = {
506502
enter: 'Enter',
507503
backspace: 'Backspace',
508504
tab: 'Tab',
@@ -522,7 +518,7 @@ export class HarmonyDevice implements AbstractInterface {
522518
};
523519

524520
const lowerKey = key.toLowerCase();
525-
return keyMap[lowerKey] || key;
521+
return keyNameAliasMap[lowerKey] ?? key;
526522
}
527523

528524
async scroll(deltaX: number, deltaY: number, speed?: number): Promise<void> {
@@ -531,7 +527,7 @@ export class HarmonyDevice implements AbstractInterface {
531527
}
532528

533529
const { width, height } = await this.size();
534-
const n = 4;
530+
const n = scrollQuadrantDivisions;
535531

536532
const startX = Math.round(deltaX < 0 ? (n - 1) * (width / n) : width / n);
537533
const startY = Math.round(deltaY < 0 ? (n - 1) * (height / n) : height / n);
@@ -553,7 +549,7 @@ export class HarmonyDevice implements AbstractInterface {
553549

554550
async scrollDown(distance?: number, startPoint?: Point): Promise<void> {
555551
const { height } = await this.size();
556-
const scrollDistance = Math.round(distance || height);
552+
const scrollDistance = Math.round(distance ?? height);
557553

558554
if (startPoint) {
559555
const hdc = await this.getHdc();
@@ -569,7 +565,7 @@ export class HarmonyDevice implements AbstractInterface {
569565

570566
async scrollUp(distance?: number, startPoint?: Point): Promise<void> {
571567
const { height } = await this.size();
572-
const scrollDistance = Math.round(distance || height);
568+
const scrollDistance = Math.round(distance ?? height);
573569

574570
if (startPoint) {
575571
const hdc = await this.getHdc();
@@ -585,7 +581,7 @@ export class HarmonyDevice implements AbstractInterface {
585581

586582
async scrollLeft(distance?: number, startPoint?: Point): Promise<void> {
587583
const { width } = await this.size();
588-
const scrollDistance = Math.round(distance || width);
584+
const scrollDistance = Math.round(distance ?? width);
589585

590586
if (startPoint) {
591587
const hdc = await this.getHdc();
@@ -601,7 +597,7 @@ export class HarmonyDevice implements AbstractInterface {
601597

602598
async scrollRight(distance?: number, startPoint?: Point): Promise<void> {
603599
const { width } = await this.size();
604-
const scrollDistance = Math.round(distance || width);
600+
const scrollDistance = Math.round(distance ?? width);
605601

606602
if (startPoint) {
607603
const hdc = await this.getHdc();
@@ -636,7 +632,7 @@ export class HarmonyDevice implements AbstractInterface {
636632
}
637633

638634
await repeat(defaultScrollUntilTimes, () =>
639-
this.scroll(0, -9999999, defaultFastSwipeSpeed),
635+
this.scroll(0, -maxScrollDistance, defaultFastSwipeSpeed),
640636
);
641637
await sleep(1000);
642638
}
@@ -655,7 +651,7 @@ export class HarmonyDevice implements AbstractInterface {
655651
}
656652

657653
await repeat(defaultScrollUntilTimes, () =>
658-
this.scroll(0, 9999999, defaultFastSwipeSpeed),
654+
this.scroll(0, maxScrollDistance, defaultFastSwipeSpeed),
659655
);
660656
await sleep(1000);
661657
}
@@ -681,7 +677,7 @@ export class HarmonyDevice implements AbstractInterface {
681677
}
682678

683679
await repeat(defaultScrollUntilTimes, () =>
684-
this.scroll(-9999999, 0, defaultFastSwipeSpeed),
680+
this.scroll(-maxScrollDistance, 0, defaultFastSwipeSpeed),
685681
);
686682
await sleep(1000);
687683
}
@@ -700,7 +696,7 @@ export class HarmonyDevice implements AbstractInterface {
700696
}
701697

702698
await repeat(defaultScrollUntilTimes, () =>
703-
this.scroll(9999999, 0, defaultFastSwipeSpeed),
699+
this.scroll(maxScrollDistance, 0, defaultFastSwipeSpeed),
704700
);
705701
await sleep(1000);
706702
}

‎packages/harmony/src/hdc.ts‎

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execFile } from 'node:child_process';
2+
import { accessSync, constants as fsConstants } from 'node:fs';
23
import { promisify } from 'node:util';
34
import { getDebug } from '@midscene/shared/logger';
45

@@ -27,7 +28,7 @@ function resolveHdcPath(hdcPath?: string): string {
2728
];
2829
for (const p of commonPaths) {
2930
try {
30-
require('node:fs').accessSync(p, require('node:fs').constants.X_OK);
31+
accessSync(p, fsConstants.X_OK);
3132
debugHdc(`Found HDC at: ${p}`);
3233
return p;
3334
} catch {}
@@ -43,8 +44,8 @@ export class HdcClient {
4344

4445
constructor(options: HdcOptions) {
4546
this.hdcPath = resolveHdcPath(options.hdcPath);
46-
this.deviceId = options.deviceId || '';
47-
this.timeout = options.timeout || 60000;
47+
this.deviceId = options.deviceId ?? '';
48+
this.timeout = options.timeout ?? 60000;
4849
}
4950

5051
private buildArgs(args: string[]): string[] {
@@ -191,33 +192,29 @@ export class HdcClient {
191192
}
192193

193194
async queryMainAbility(bundleName: string): Promise<string | undefined> {
194-
try {
195-
const output = await this.shell(`bm dump -n ${bundleName}`);
196-
const names: string[] = [];
197-
for (const match of output.matchAll(/"name"\s*:\s*"([^"]+)"/g)) {
198-
names.push(match[1]);
199-
}
200-
// Prefer: EntryAbility > MainAbility > {bundleName}.MainAbility > first *Ability
201-
for (const candidate of [
202-
'EntryAbility',
203-
'MainAbility',
204-
`${bundleName}.MainAbility`,
205-
]) {
206-
if (names.includes(candidate)) return candidate;
207-
}
208-
// Fallback: find first ability-like name that isn't the bundle itself
209-
return names.find(
210-
(n) =>
211-
n !== bundleName &&
212-
n.endsWith('Ability') &&
213-
!n.includes('Extension') &&
214-
!n.includes('Service') &&
215-
!n.includes('Form') &&
216-
!n.includes('Dialog'),
217-
);
218-
} catch {
219-
return undefined;
195+
const output = await this.shell(`bm dump -n ${bundleName}`);
196+
const names: string[] = [];
197+
for (const match of output.matchAll(/"name"\s*:\s*"([^"]+)"/g)) {
198+
names.push(match[1]);
199+
}
200+
// Prefer: EntryAbility > MainAbility > {bundleName}.MainAbility > first *Ability
201+
for (const candidate of [
202+
'EntryAbility',
203+
'MainAbility',
204+
`${bundleName}.MainAbility`,
205+
]) {
206+
if (names.includes(candidate)) return candidate;
220207
}
208+
// Fallback: find first ability-like name that isn't the bundle itself
209+
return names.find(
210+
(n) =>
211+
n !== bundleName &&
212+
n.endsWith('Ability') &&
213+
!n.includes('Extension') &&
214+
!n.includes('Service') &&
215+
!n.includes('Form') &&
216+
!n.includes('Dialog'),
217+
);
221218
}
222219

223220
async forceStop(bundleName: string): Promise<void> {

‎packages/harmony/src/mcp-server.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
type Tool,
55
createMCPServerLauncher,
66
} from '@midscene/shared/mcp';
7-
import { HarmonyAgent } from './agent';
7+
import type { HarmonyAgent } from './agent';
88
import { HarmonyMidsceneTools } from './mcp-tools.js';
99

1010
declare const __VERSION__: string;
@@ -42,9 +42,7 @@ export async function mcpKitForAgent(agent: Agent | HarmonyAgent): Promise<{
4242
}> {
4343
const toolsManager = new HarmonyMidsceneTools();
4444

45-
const harmonyAgent =
46-
agent instanceof HarmonyAgent ? agent : (agent as HarmonyAgent);
47-
toolsManager.setAgent(harmonyAgent);
45+
toolsManager.setAgent(agent as HarmonyAgent);
4846
await toolsManager.initTools();
4947

5048
return {

‎packages/harmony/src/utils.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { HdcClient } from './hdc';
33

44
const debugUtils = getDebug('harmony:utils');
55

6-
export interface HarmonyDevice {
6+
export interface HarmonyDeviceInfo {
77
deviceId: string;
88
}
99

1010
export async function getConnectedDevices(
1111
hdcPath?: string,
12-
): Promise<HarmonyDevice[]> {
12+
): Promise<HarmonyDeviceInfo[]> {
1313
try {
1414
const hdc = new HdcClient({ hdcPath });
1515
const targets = await hdc.listTargets();

0 commit comments

Comments
 (0)