@midscene/harmony 1.10.11 → 1.10.12-beta-20260812055946.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/bin.mjs +152 -71
- package/dist/es/cli.mjs +153 -72
- package/dist/es/index.mjs +152 -71
- package/dist/lib/bin.js +152 -71
- package/dist/lib/cli.js +153 -72
- package/dist/lib/index.js +152 -71
- package/dist/types/index.d.ts +3 -0
- package/package.json +4 -4
- package/static/index.html +1 -1
- package/static/static/js/index.fbf1ef67.js +973 -0
- package/static/static/js/index.fbf1ef67.js.map +1 -0
- package/static/static/js/index.277018ac.js +0 -973
- package/static/static/js/index.277018ac.js.map +0 -1
- /package/static/static/js/{index.277018ac.js.LICENSE.txt → index.fbf1ef67.js.LICENSE.txt} +0 -0
package/dist/es/bin.mjs
CHANGED
|
@@ -60,6 +60,23 @@ function _define_property(obj, key, value) {
|
|
|
60
60
|
}
|
|
61
61
|
const execFileAsync = promisify(execFile);
|
|
62
62
|
const debugHdc = getDebug('harmony:hdc');
|
|
63
|
+
const supportedStringKeyEvents = new Set([
|
|
64
|
+
'Back',
|
|
65
|
+
'Home',
|
|
66
|
+
'Power'
|
|
67
|
+
]);
|
|
68
|
+
const numericKeyEventPattern = /^\d+$/;
|
|
69
|
+
const uiInputErrorPattern = /(?:(?:Invalid parameters|Missing parameter|Too many parameters)\.?|Please confirm that the coordinate values are correct\.?)/i;
|
|
70
|
+
const minUiInputSpeed = 200;
|
|
71
|
+
const maxUiInputSpeed = 40000;
|
|
72
|
+
function roundUiCoordinate(value, name) {
|
|
73
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`HDC ${name} coordinate must be a non-negative finite number`);
|
|
74
|
+
return Math.round(value);
|
|
75
|
+
}
|
|
76
|
+
function roundUiInputSpeed(value, operation) {
|
|
77
|
+
if (!Number.isFinite(value) || value < minUiInputSpeed || value > maxUiInputSpeed) throw new Error(`HDC ${operation} speed must be a finite number between ${minUiInputSpeed} and ${maxUiInputSpeed}`);
|
|
78
|
+
return Math.round(value);
|
|
79
|
+
}
|
|
63
80
|
function resolveHdcPath(hdcPath) {
|
|
64
81
|
if (hdcPath) return hdcPath;
|
|
65
82
|
if (process.env.HDC_HOME) {
|
|
@@ -121,6 +138,26 @@ class HdcClient {
|
|
|
121
138
|
async shell(command) {
|
|
122
139
|
return this.exec('shell', command);
|
|
123
140
|
}
|
|
141
|
+
assertUiInputSucceeded(operation, output, detail) {
|
|
142
|
+
const errorMatch = output.match(uiInputErrorPattern);
|
|
143
|
+
if (!errorMatch) return;
|
|
144
|
+
const detailText = detail ? ` for ${detail}` : '';
|
|
145
|
+
throw new Error(`HDC uiInput ${operation} failed${detailText}: ${errorMatch[0]}`);
|
|
146
|
+
}
|
|
147
|
+
async runUiInput(operation, args, detail) {
|
|
148
|
+
const output = await this.shell(`uitest uiInput ${operation} ${args}`);
|
|
149
|
+
this.assertUiInputSucceeded(operation, output, detail);
|
|
150
|
+
}
|
|
151
|
+
buildPointerMovementArgs(operation, fromX, fromY, toX, toY, speed) {
|
|
152
|
+
const args = [
|
|
153
|
+
roundUiCoordinate(fromX, 'fromX'),
|
|
154
|
+
roundUiCoordinate(fromY, 'fromY'),
|
|
155
|
+
roundUiCoordinate(toX, 'toX'),
|
|
156
|
+
roundUiCoordinate(toY, 'toY')
|
|
157
|
+
];
|
|
158
|
+
if (void 0 !== speed) args.push(roundUiInputSpeed(speed, operation));
|
|
159
|
+
return args.join(' ');
|
|
160
|
+
}
|
|
124
161
|
async fileSend(localPath, remotePath) {
|
|
125
162
|
await this.exec('file', 'send', localPath, remotePath);
|
|
126
163
|
}
|
|
@@ -138,63 +175,49 @@ class HdcClient {
|
|
|
138
175
|
return output.slice(jsonStart);
|
|
139
176
|
}
|
|
140
177
|
async click(x, y) {
|
|
141
|
-
await this.
|
|
178
|
+
await this.runUiInput('click', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
142
179
|
}
|
|
143
180
|
async doubleClick(x, y) {
|
|
144
|
-
await this.
|
|
181
|
+
await this.runUiInput('doubleClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
145
182
|
}
|
|
146
183
|
async longClick(x, y) {
|
|
147
|
-
await this.
|
|
184
|
+
await this.runUiInput('longClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
148
185
|
}
|
|
149
186
|
async swipe(fromX, fromY, toX, toY, speed) {
|
|
150
|
-
|
|
151
|
-
Math.round(fromX),
|
|
152
|
-
Math.round(fromY),
|
|
153
|
-
Math.round(toX),
|
|
154
|
-
Math.round(toY)
|
|
155
|
-
];
|
|
156
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
157
|
-
await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
|
|
187
|
+
await this.runUiInput('swipe', this.buildPointerMovementArgs('swipe', fromX, fromY, toX, toY, speed));
|
|
158
188
|
}
|
|
159
189
|
async fling(fromX, fromY, toX, toY, speed) {
|
|
160
|
-
|
|
161
|
-
Math.round(fromX),
|
|
162
|
-
Math.round(fromY),
|
|
163
|
-
Math.round(toX),
|
|
164
|
-
Math.round(toY)
|
|
165
|
-
];
|
|
166
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
167
|
-
await this.shell(`uitest uiInput fling ${args.join(' ')}`);
|
|
190
|
+
await this.runUiInput('fling', this.buildPointerMovementArgs('fling', fromX, fromY, toX, toY, speed));
|
|
168
191
|
}
|
|
169
192
|
async drag(fromX, fromY, toX, toY, speed) {
|
|
170
|
-
|
|
171
|
-
Math.round(fromX),
|
|
172
|
-
Math.round(fromY),
|
|
173
|
-
Math.round(toX),
|
|
174
|
-
Math.round(toY)
|
|
175
|
-
];
|
|
176
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
177
|
-
await this.shell(`uitest uiInput drag ${args.join(' ')}`);
|
|
193
|
+
await this.runUiInput('drag', this.buildPointerMovementArgs('drag', fromX, fromY, toX, toY, speed));
|
|
178
194
|
}
|
|
179
195
|
async inputText(x, y, text) {
|
|
180
196
|
const escapedText = text.replace(/'/g, "'\\''");
|
|
181
|
-
await this.
|
|
197
|
+
await this.runUiInput('inputText', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')} '${escapedText}'`);
|
|
182
198
|
}
|
|
183
199
|
async keyEvent(...keys) {
|
|
184
|
-
|
|
200
|
+
if (keys.length < 1 || keys.length > 3) throw new Error('HDC keyEvent requires between 1 and 3 keys');
|
|
201
|
+
if (keys.length > 1 && keys.some((key)=>supportedStringKeyEvents.has(key))) throw new Error('HDC system key events cannot be combined');
|
|
202
|
+
for (const key of keys)if (!supportedStringKeyEvents.has(key) && !numericKeyEventPattern.test(key)) throw new Error(`Invalid HDC key event: ${key}`);
|
|
203
|
+
const joinedKeys = keys.join(' ');
|
|
204
|
+
await this.runUiInput('keyEvent', joinedKeys, joinedKeys);
|
|
185
205
|
}
|
|
186
206
|
async clearTextField(length = 100) {
|
|
187
|
-
if (length
|
|
188
|
-
|
|
207
|
+
if (!Number.isSafeInteger(length) || length < 0) throw new Error('HDC clearTextField length must be a non-negative safe integer');
|
|
208
|
+
if (0 === length) return;
|
|
209
|
+
const maxKeysPerCall = 3;
|
|
210
|
+
const backspaceKeyCode = '2055';
|
|
189
211
|
const cmds = [];
|
|
190
212
|
let remaining = length;
|
|
191
213
|
while(remaining > 0){
|
|
192
|
-
const n = Math.min(
|
|
193
|
-
const codes = Array(n).fill(
|
|
214
|
+
const n = Math.min(maxKeysPerCall, remaining);
|
|
215
|
+
const codes = Array(n).fill(backspaceKeyCode).join(' ');
|
|
194
216
|
cmds.push(`uitest uiInput keyEvent ${codes}`);
|
|
195
217
|
remaining -= n;
|
|
196
218
|
}
|
|
197
|
-
await this.shell(cmds.join(';'));
|
|
219
|
+
const output = await this.shell(cmds.join(';'));
|
|
220
|
+
this.assertUiInputSucceeded('clearTextField', output);
|
|
198
221
|
}
|
|
199
222
|
async startAbility(bundleName, abilityName) {
|
|
200
223
|
const output = await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
|
|
@@ -258,6 +281,96 @@ class HdcClient {
|
|
|
258
281
|
this.timeout = options.timeout ?? 60000;
|
|
259
282
|
}
|
|
260
283
|
}
|
|
284
|
+
const namedHarmonyKeyCodeMap = {
|
|
285
|
+
Enter: '2054',
|
|
286
|
+
Backspace: '2055',
|
|
287
|
+
Tab: '2049',
|
|
288
|
+
Escape: '2070',
|
|
289
|
+
Home: 'Home',
|
|
290
|
+
ArrowUp: '2012',
|
|
291
|
+
ArrowDown: '2013',
|
|
292
|
+
ArrowLeft: '2014',
|
|
293
|
+
ArrowRight: '2015',
|
|
294
|
+
Space: '2050',
|
|
295
|
+
Delete: '2071',
|
|
296
|
+
Alt: '2045',
|
|
297
|
+
Shift: '2047',
|
|
298
|
+
Control: '2072',
|
|
299
|
+
Meta: '2076',
|
|
300
|
+
Back: 'Back',
|
|
301
|
+
Power: 'Power'
|
|
302
|
+
};
|
|
303
|
+
const keyNameAliasMap = new Map([
|
|
304
|
+
[
|
|
305
|
+
'return',
|
|
306
|
+
'Enter'
|
|
307
|
+
],
|
|
308
|
+
[
|
|
309
|
+
'esc',
|
|
310
|
+
'Escape'
|
|
311
|
+
],
|
|
312
|
+
[
|
|
313
|
+
'up',
|
|
314
|
+
'ArrowUp'
|
|
315
|
+
],
|
|
316
|
+
[
|
|
317
|
+
'down',
|
|
318
|
+
'ArrowDown'
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
'left',
|
|
322
|
+
'ArrowLeft'
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
'right',
|
|
326
|
+
'ArrowRight'
|
|
327
|
+
],
|
|
328
|
+
[
|
|
329
|
+
'ctrl',
|
|
330
|
+
'Control'
|
|
331
|
+
]
|
|
332
|
+
]);
|
|
333
|
+
const caseInsensitiveNamedKeyCodeMap = new Map(Object.entries(namedHarmonyKeyCodeMap).map(([key, code])=>[
|
|
334
|
+
key.toLowerCase(),
|
|
335
|
+
code
|
|
336
|
+
]));
|
|
337
|
+
function numericKeyCode(value) {
|
|
338
|
+
return String(value);
|
|
339
|
+
}
|
|
340
|
+
function resolveSingleHarmonyKeyCodes(keyName) {
|
|
341
|
+
const trimmedKeyName = keyName.trim();
|
|
342
|
+
if (!trimmedKeyName) {
|
|
343
|
+
if (0 === keyName.length) throw new Error('Unsupported HarmonyOS key: empty key name');
|
|
344
|
+
throw unsupportedKeyboardPressKey(keyName);
|
|
345
|
+
}
|
|
346
|
+
const alias = keyNameAliasMap.get(trimmedKeyName.toLowerCase());
|
|
347
|
+
const normalizedKeyName = alias ?? trimmedKeyName;
|
|
348
|
+
if (/^[0-9]$/.test(normalizedKeyName)) return [
|
|
349
|
+
numericKeyCode(2000 + Number(normalizedKeyName))
|
|
350
|
+
];
|
|
351
|
+
const letterKeyMatch = normalizedKeyName.match(/^[a-z]$/i);
|
|
352
|
+
if (letterKeyMatch) {
|
|
353
|
+
const alphabetIndex = letterKeyMatch[0].toUpperCase().charCodeAt(0) - 65;
|
|
354
|
+
return [
|
|
355
|
+
numericKeyCode(2017 + alphabetIndex)
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
const namedCode = caseInsensitiveNamedKeyCodeMap.get(normalizedKeyName.toLowerCase());
|
|
359
|
+
if (namedCode) return [
|
|
360
|
+
namedCode
|
|
361
|
+
];
|
|
362
|
+
throw unsupportedKeyboardPressKey(keyName);
|
|
363
|
+
}
|
|
364
|
+
function unsupportedKeyboardPressKey(keyName) {
|
|
365
|
+
return new Error(`Unsupported HarmonyOS keyboardPress key: ${JSON.stringify(keyName)}. Use typeText() for text input, or pass a supported key name such as "Enter", "A", or "1".`);
|
|
366
|
+
}
|
|
367
|
+
function resolveHarmonyKeyCodes(keyName) {
|
|
368
|
+
const trimmedKeyName = keyName.trim();
|
|
369
|
+
if (!trimmedKeyName && ' ' !== keyName) return resolveSingleHarmonyKeyCodes(keyName);
|
|
370
|
+
if ('+' === trimmedKeyName) return resolveSingleHarmonyKeyCodes(keyName);
|
|
371
|
+
if (trimmedKeyName.includes('+')) throw new Error(`HarmonyOS keyboardPress does not support key combinations: ${JSON.stringify(keyName)}`);
|
|
372
|
+
return resolveSingleHarmonyKeyCodes(keyName);
|
|
373
|
+
}
|
|
261
374
|
function device_define_property(obj, key, value) {
|
|
262
375
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
263
376
|
value: value,
|
|
@@ -316,37 +429,6 @@ function pickFieldByPoint(fields, point) {
|
|
|
316
429
|
function pickLongestField(fields) {
|
|
317
430
|
return fields.reduce((a, b)=>b.text.length > a.text.length ? b : a);
|
|
318
431
|
}
|
|
319
|
-
const harmonyKeyCodeMap = {
|
|
320
|
-
Enter: '2054',
|
|
321
|
-
Backspace: '2055',
|
|
322
|
-
Tab: '2049',
|
|
323
|
-
Escape: '2070',
|
|
324
|
-
Home: 'Home',
|
|
325
|
-
ArrowUp: '2012',
|
|
326
|
-
ArrowDown: '2013',
|
|
327
|
-
ArrowLeft: '2014',
|
|
328
|
-
ArrowRight: '2015',
|
|
329
|
-
Space: '2050',
|
|
330
|
-
Delete: '2071'
|
|
331
|
-
};
|
|
332
|
-
const keyNameAliasMap = {
|
|
333
|
-
enter: 'Enter',
|
|
334
|
-
backspace: 'Backspace',
|
|
335
|
-
tab: 'Tab',
|
|
336
|
-
escape: 'Escape',
|
|
337
|
-
esc: 'Escape',
|
|
338
|
-
home: 'Home',
|
|
339
|
-
space: 'Space',
|
|
340
|
-
delete: 'Delete',
|
|
341
|
-
arrowup: 'ArrowUp',
|
|
342
|
-
arrowdown: 'ArrowDown',
|
|
343
|
-
arrowleft: 'ArrowLeft',
|
|
344
|
-
arrowright: 'ArrowRight',
|
|
345
|
-
up: 'ArrowUp',
|
|
346
|
-
down: 'ArrowDown',
|
|
347
|
-
left: 'ArrowLeft',
|
|
348
|
-
right: 'ArrowRight'
|
|
349
|
-
};
|
|
350
432
|
class device_HarmonyDevice {
|
|
351
433
|
actionSpace() {
|
|
352
434
|
const mobileActionContext = {
|
|
@@ -590,10 +672,9 @@ class device_HarmonyDevice {
|
|
|
590
672
|
}
|
|
591
673
|
}
|
|
592
674
|
async pressKey(key) {
|
|
593
|
-
const
|
|
594
|
-
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
675
|
+
const keyCodes = resolveHarmonyKeyCodes(key);
|
|
595
676
|
const hdc = await this.getHdc();
|
|
596
|
-
await hdc.keyEvent(
|
|
677
|
+
await hdc.keyEvent(...keyCodes);
|
|
597
678
|
}
|
|
598
679
|
async scroll(deltaX, deltaY, speed) {
|
|
599
680
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
@@ -748,13 +829,13 @@ class device_HarmonyDevice {
|
|
|
748
829
|
}
|
|
749
830
|
async recentApps() {
|
|
750
831
|
const hdc = await this.getHdc();
|
|
751
|
-
await hdc.keyEvent('
|
|
832
|
+
await hdc.keyEvent('10011');
|
|
752
833
|
}
|
|
753
834
|
async hideKeyboard(options) {
|
|
754
835
|
const hdc = await this.getHdc();
|
|
755
836
|
const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
|
|
756
|
-
|
|
757
|
-
await hdc.keyEvent(
|
|
837
|
+
if ('back-first' === keyboardDismissStrategy) return void await hdc.keyEvent('Back');
|
|
838
|
+
await hdc.keyEvent(...resolveHarmonyKeyCodes('Escape'));
|
|
758
839
|
}
|
|
759
840
|
async getDeviceLocalTimeString(format = 'YYYY-MM-DD HH:mm:ss') {
|
|
760
841
|
const hdc = await this.getHdc();
|
package/dist/es/cli.mjs
CHANGED
|
@@ -58,6 +58,23 @@ function _define_property(obj, key, value) {
|
|
|
58
58
|
}
|
|
59
59
|
const execFileAsync = promisify(execFile);
|
|
60
60
|
const debugHdc = getDebug('harmony:hdc');
|
|
61
|
+
const supportedStringKeyEvents = new Set([
|
|
62
|
+
'Back',
|
|
63
|
+
'Home',
|
|
64
|
+
'Power'
|
|
65
|
+
]);
|
|
66
|
+
const numericKeyEventPattern = /^\d+$/;
|
|
67
|
+
const uiInputErrorPattern = /(?:(?:Invalid parameters|Missing parameter|Too many parameters)\.?|Please confirm that the coordinate values are correct\.?)/i;
|
|
68
|
+
const minUiInputSpeed = 200;
|
|
69
|
+
const maxUiInputSpeed = 40000;
|
|
70
|
+
function roundUiCoordinate(value, name) {
|
|
71
|
+
if (!Number.isFinite(value) || value < 0) throw new Error(`HDC ${name} coordinate must be a non-negative finite number`);
|
|
72
|
+
return Math.round(value);
|
|
73
|
+
}
|
|
74
|
+
function roundUiInputSpeed(value, operation) {
|
|
75
|
+
if (!Number.isFinite(value) || value < minUiInputSpeed || value > maxUiInputSpeed) throw new Error(`HDC ${operation} speed must be a finite number between ${minUiInputSpeed} and ${maxUiInputSpeed}`);
|
|
76
|
+
return Math.round(value);
|
|
77
|
+
}
|
|
61
78
|
function resolveHdcPath(hdcPath) {
|
|
62
79
|
if (hdcPath) return hdcPath;
|
|
63
80
|
if (process.env.HDC_HOME) {
|
|
@@ -119,6 +136,26 @@ class HdcClient {
|
|
|
119
136
|
async shell(command) {
|
|
120
137
|
return this.exec('shell', command);
|
|
121
138
|
}
|
|
139
|
+
assertUiInputSucceeded(operation, output, detail) {
|
|
140
|
+
const errorMatch = output.match(uiInputErrorPattern);
|
|
141
|
+
if (!errorMatch) return;
|
|
142
|
+
const detailText = detail ? ` for ${detail}` : '';
|
|
143
|
+
throw new Error(`HDC uiInput ${operation} failed${detailText}: ${errorMatch[0]}`);
|
|
144
|
+
}
|
|
145
|
+
async runUiInput(operation, args, detail) {
|
|
146
|
+
const output = await this.shell(`uitest uiInput ${operation} ${args}`);
|
|
147
|
+
this.assertUiInputSucceeded(operation, output, detail);
|
|
148
|
+
}
|
|
149
|
+
buildPointerMovementArgs(operation, fromX, fromY, toX, toY, speed) {
|
|
150
|
+
const args = [
|
|
151
|
+
roundUiCoordinate(fromX, 'fromX'),
|
|
152
|
+
roundUiCoordinate(fromY, 'fromY'),
|
|
153
|
+
roundUiCoordinate(toX, 'toX'),
|
|
154
|
+
roundUiCoordinate(toY, 'toY')
|
|
155
|
+
];
|
|
156
|
+
if (void 0 !== speed) args.push(roundUiInputSpeed(speed, operation));
|
|
157
|
+
return args.join(' ');
|
|
158
|
+
}
|
|
122
159
|
async fileSend(localPath, remotePath) {
|
|
123
160
|
await this.exec('file', 'send', localPath, remotePath);
|
|
124
161
|
}
|
|
@@ -136,63 +173,49 @@ class HdcClient {
|
|
|
136
173
|
return output.slice(jsonStart);
|
|
137
174
|
}
|
|
138
175
|
async click(x, y) {
|
|
139
|
-
await this.
|
|
176
|
+
await this.runUiInput('click', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
140
177
|
}
|
|
141
178
|
async doubleClick(x, y) {
|
|
142
|
-
await this.
|
|
179
|
+
await this.runUiInput('doubleClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
143
180
|
}
|
|
144
181
|
async longClick(x, y) {
|
|
145
|
-
await this.
|
|
182
|
+
await this.runUiInput('longClick', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')}`);
|
|
146
183
|
}
|
|
147
184
|
async swipe(fromX, fromY, toX, toY, speed) {
|
|
148
|
-
|
|
149
|
-
Math.round(fromX),
|
|
150
|
-
Math.round(fromY),
|
|
151
|
-
Math.round(toX),
|
|
152
|
-
Math.round(toY)
|
|
153
|
-
];
|
|
154
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
155
|
-
await this.shell(`uitest uiInput swipe ${args.join(' ')}`);
|
|
185
|
+
await this.runUiInput('swipe', this.buildPointerMovementArgs('swipe', fromX, fromY, toX, toY, speed));
|
|
156
186
|
}
|
|
157
187
|
async fling(fromX, fromY, toX, toY, speed) {
|
|
158
|
-
|
|
159
|
-
Math.round(fromX),
|
|
160
|
-
Math.round(fromY),
|
|
161
|
-
Math.round(toX),
|
|
162
|
-
Math.round(toY)
|
|
163
|
-
];
|
|
164
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
165
|
-
await this.shell(`uitest uiInput fling ${args.join(' ')}`);
|
|
188
|
+
await this.runUiInput('fling', this.buildPointerMovementArgs('fling', fromX, fromY, toX, toY, speed));
|
|
166
189
|
}
|
|
167
190
|
async drag(fromX, fromY, toX, toY, speed) {
|
|
168
|
-
|
|
169
|
-
Math.round(fromX),
|
|
170
|
-
Math.round(fromY),
|
|
171
|
-
Math.round(toX),
|
|
172
|
-
Math.round(toY)
|
|
173
|
-
];
|
|
174
|
-
if (void 0 !== speed) args.push(Math.round(speed));
|
|
175
|
-
await this.shell(`uitest uiInput drag ${args.join(' ')}`);
|
|
191
|
+
await this.runUiInput('drag', this.buildPointerMovementArgs('drag', fromX, fromY, toX, toY, speed));
|
|
176
192
|
}
|
|
177
193
|
async inputText(x, y, text) {
|
|
178
194
|
const escapedText = text.replace(/'/g, "'\\''");
|
|
179
|
-
await this.
|
|
195
|
+
await this.runUiInput('inputText', `${roundUiCoordinate(x, 'x')} ${roundUiCoordinate(y, 'y')} '${escapedText}'`);
|
|
180
196
|
}
|
|
181
197
|
async keyEvent(...keys) {
|
|
182
|
-
|
|
198
|
+
if (keys.length < 1 || keys.length > 3) throw new Error('HDC keyEvent requires between 1 and 3 keys');
|
|
199
|
+
if (keys.length > 1 && keys.some((key)=>supportedStringKeyEvents.has(key))) throw new Error('HDC system key events cannot be combined');
|
|
200
|
+
for (const key of keys)if (!supportedStringKeyEvents.has(key) && !numericKeyEventPattern.test(key)) throw new Error(`Invalid HDC key event: ${key}`);
|
|
201
|
+
const joinedKeys = keys.join(' ');
|
|
202
|
+
await this.runUiInput('keyEvent', joinedKeys, joinedKeys);
|
|
183
203
|
}
|
|
184
204
|
async clearTextField(length = 100) {
|
|
185
|
-
if (length
|
|
186
|
-
|
|
205
|
+
if (!Number.isSafeInteger(length) || length < 0) throw new Error('HDC clearTextField length must be a non-negative safe integer');
|
|
206
|
+
if (0 === length) return;
|
|
207
|
+
const maxKeysPerCall = 3;
|
|
208
|
+
const backspaceKeyCode = '2055';
|
|
187
209
|
const cmds = [];
|
|
188
210
|
let remaining = length;
|
|
189
211
|
while(remaining > 0){
|
|
190
|
-
const n = Math.min(
|
|
191
|
-
const codes = Array(n).fill(
|
|
212
|
+
const n = Math.min(maxKeysPerCall, remaining);
|
|
213
|
+
const codes = Array(n).fill(backspaceKeyCode).join(' ');
|
|
192
214
|
cmds.push(`uitest uiInput keyEvent ${codes}`);
|
|
193
215
|
remaining -= n;
|
|
194
216
|
}
|
|
195
|
-
await this.shell(cmds.join(';'));
|
|
217
|
+
const output = await this.shell(cmds.join(';'));
|
|
218
|
+
this.assertUiInputSucceeded('clearTextField', output);
|
|
196
219
|
}
|
|
197
220
|
async startAbility(bundleName, abilityName) {
|
|
198
221
|
const output = await this.shell(`aa start -a ${abilityName} -b ${bundleName}`);
|
|
@@ -256,6 +279,96 @@ class HdcClient {
|
|
|
256
279
|
this.timeout = options.timeout ?? 60000;
|
|
257
280
|
}
|
|
258
281
|
}
|
|
282
|
+
const namedHarmonyKeyCodeMap = {
|
|
283
|
+
Enter: '2054',
|
|
284
|
+
Backspace: '2055',
|
|
285
|
+
Tab: '2049',
|
|
286
|
+
Escape: '2070',
|
|
287
|
+
Home: 'Home',
|
|
288
|
+
ArrowUp: '2012',
|
|
289
|
+
ArrowDown: '2013',
|
|
290
|
+
ArrowLeft: '2014',
|
|
291
|
+
ArrowRight: '2015',
|
|
292
|
+
Space: '2050',
|
|
293
|
+
Delete: '2071',
|
|
294
|
+
Alt: '2045',
|
|
295
|
+
Shift: '2047',
|
|
296
|
+
Control: '2072',
|
|
297
|
+
Meta: '2076',
|
|
298
|
+
Back: 'Back',
|
|
299
|
+
Power: 'Power'
|
|
300
|
+
};
|
|
301
|
+
const keyNameAliasMap = new Map([
|
|
302
|
+
[
|
|
303
|
+
'return',
|
|
304
|
+
'Enter'
|
|
305
|
+
],
|
|
306
|
+
[
|
|
307
|
+
'esc',
|
|
308
|
+
'Escape'
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
'up',
|
|
312
|
+
'ArrowUp'
|
|
313
|
+
],
|
|
314
|
+
[
|
|
315
|
+
'down',
|
|
316
|
+
'ArrowDown'
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
'left',
|
|
320
|
+
'ArrowLeft'
|
|
321
|
+
],
|
|
322
|
+
[
|
|
323
|
+
'right',
|
|
324
|
+
'ArrowRight'
|
|
325
|
+
],
|
|
326
|
+
[
|
|
327
|
+
'ctrl',
|
|
328
|
+
'Control'
|
|
329
|
+
]
|
|
330
|
+
]);
|
|
331
|
+
const caseInsensitiveNamedKeyCodeMap = new Map(Object.entries(namedHarmonyKeyCodeMap).map(([key, code])=>[
|
|
332
|
+
key.toLowerCase(),
|
|
333
|
+
code
|
|
334
|
+
]));
|
|
335
|
+
function numericKeyCode(value) {
|
|
336
|
+
return String(value);
|
|
337
|
+
}
|
|
338
|
+
function resolveSingleHarmonyKeyCodes(keyName) {
|
|
339
|
+
const trimmedKeyName = keyName.trim();
|
|
340
|
+
if (!trimmedKeyName) {
|
|
341
|
+
if (0 === keyName.length) throw new Error('Unsupported HarmonyOS key: empty key name');
|
|
342
|
+
throw unsupportedKeyboardPressKey(keyName);
|
|
343
|
+
}
|
|
344
|
+
const alias = keyNameAliasMap.get(trimmedKeyName.toLowerCase());
|
|
345
|
+
const normalizedKeyName = alias ?? trimmedKeyName;
|
|
346
|
+
if (/^[0-9]$/.test(normalizedKeyName)) return [
|
|
347
|
+
numericKeyCode(2000 + Number(normalizedKeyName))
|
|
348
|
+
];
|
|
349
|
+
const letterKeyMatch = normalizedKeyName.match(/^[a-z]$/i);
|
|
350
|
+
if (letterKeyMatch) {
|
|
351
|
+
const alphabetIndex = letterKeyMatch[0].toUpperCase().charCodeAt(0) - 65;
|
|
352
|
+
return [
|
|
353
|
+
numericKeyCode(2017 + alphabetIndex)
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
const namedCode = caseInsensitiveNamedKeyCodeMap.get(normalizedKeyName.toLowerCase());
|
|
357
|
+
if (namedCode) return [
|
|
358
|
+
namedCode
|
|
359
|
+
];
|
|
360
|
+
throw unsupportedKeyboardPressKey(keyName);
|
|
361
|
+
}
|
|
362
|
+
function unsupportedKeyboardPressKey(keyName) {
|
|
363
|
+
return new Error(`Unsupported HarmonyOS keyboardPress key: ${JSON.stringify(keyName)}. Use typeText() for text input, or pass a supported key name such as "Enter", "A", or "1".`);
|
|
364
|
+
}
|
|
365
|
+
function resolveHarmonyKeyCodes(keyName) {
|
|
366
|
+
const trimmedKeyName = keyName.trim();
|
|
367
|
+
if (!trimmedKeyName && ' ' !== keyName) return resolveSingleHarmonyKeyCodes(keyName);
|
|
368
|
+
if ('+' === trimmedKeyName) return resolveSingleHarmonyKeyCodes(keyName);
|
|
369
|
+
if (trimmedKeyName.includes('+')) throw new Error(`HarmonyOS keyboardPress does not support key combinations: ${JSON.stringify(keyName)}`);
|
|
370
|
+
return resolveSingleHarmonyKeyCodes(keyName);
|
|
371
|
+
}
|
|
259
372
|
function device_define_property(obj, key, value) {
|
|
260
373
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
261
374
|
value: value,
|
|
@@ -314,37 +427,6 @@ function pickFieldByPoint(fields, point) {
|
|
|
314
427
|
function pickLongestField(fields) {
|
|
315
428
|
return fields.reduce((a, b)=>b.text.length > a.text.length ? b : a);
|
|
316
429
|
}
|
|
317
|
-
const harmonyKeyCodeMap = {
|
|
318
|
-
Enter: '2054',
|
|
319
|
-
Backspace: '2055',
|
|
320
|
-
Tab: '2049',
|
|
321
|
-
Escape: '2070',
|
|
322
|
-
Home: 'Home',
|
|
323
|
-
ArrowUp: '2012',
|
|
324
|
-
ArrowDown: '2013',
|
|
325
|
-
ArrowLeft: '2014',
|
|
326
|
-
ArrowRight: '2015',
|
|
327
|
-
Space: '2050',
|
|
328
|
-
Delete: '2071'
|
|
329
|
-
};
|
|
330
|
-
const keyNameAliasMap = {
|
|
331
|
-
enter: 'Enter',
|
|
332
|
-
backspace: 'Backspace',
|
|
333
|
-
tab: 'Tab',
|
|
334
|
-
escape: 'Escape',
|
|
335
|
-
esc: 'Escape',
|
|
336
|
-
home: 'Home',
|
|
337
|
-
space: 'Space',
|
|
338
|
-
delete: 'Delete',
|
|
339
|
-
arrowup: 'ArrowUp',
|
|
340
|
-
arrowdown: 'ArrowDown',
|
|
341
|
-
arrowleft: 'ArrowLeft',
|
|
342
|
-
arrowright: 'ArrowRight',
|
|
343
|
-
up: 'ArrowUp',
|
|
344
|
-
down: 'ArrowDown',
|
|
345
|
-
left: 'ArrowLeft',
|
|
346
|
-
right: 'ArrowRight'
|
|
347
|
-
};
|
|
348
430
|
class HarmonyDevice {
|
|
349
431
|
actionSpace() {
|
|
350
432
|
const mobileActionContext = {
|
|
@@ -588,10 +670,9 @@ class HarmonyDevice {
|
|
|
588
670
|
}
|
|
589
671
|
}
|
|
590
672
|
async pressKey(key) {
|
|
591
|
-
const
|
|
592
|
-
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
673
|
+
const keyCodes = resolveHarmonyKeyCodes(key);
|
|
593
674
|
const hdc = await this.getHdc();
|
|
594
|
-
await hdc.keyEvent(
|
|
675
|
+
await hdc.keyEvent(...keyCodes);
|
|
595
676
|
}
|
|
596
677
|
async scroll(deltaX, deltaY, speed) {
|
|
597
678
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
@@ -746,13 +827,13 @@ class HarmonyDevice {
|
|
|
746
827
|
}
|
|
747
828
|
async recentApps() {
|
|
748
829
|
const hdc = await this.getHdc();
|
|
749
|
-
await hdc.keyEvent('
|
|
830
|
+
await hdc.keyEvent('10011');
|
|
750
831
|
}
|
|
751
832
|
async hideKeyboard(options) {
|
|
752
833
|
const hdc = await this.getHdc();
|
|
753
834
|
const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
|
|
754
|
-
|
|
755
|
-
await hdc.keyEvent(
|
|
835
|
+
if ('back-first' === keyboardDismissStrategy) return void await hdc.keyEvent('Back');
|
|
836
|
+
await hdc.keyEvent(...resolveHarmonyKeyCodes('Escape'));
|
|
756
837
|
}
|
|
757
838
|
async getDeviceLocalTimeString(format = 'YYYY-MM-DD HH:mm:ss') {
|
|
758
839
|
const hdc = await this.getHdc();
|
|
@@ -1093,7 +1174,7 @@ class HarmonyMidsceneTools extends BaseMidsceneTools {
|
|
|
1093
1174
|
const tools = new HarmonyMidsceneTools();
|
|
1094
1175
|
runToolsCLI(tools, 'midscene-harmony', {
|
|
1095
1176
|
stripPrefix: 'harmony_',
|
|
1096
|
-
version: "1.10.
|
|
1177
|
+
version: "1.10.12-beta-20260812055946.0",
|
|
1097
1178
|
extraCommands: createReportCliCommands()
|
|
1098
1179
|
}).catch((e)=>{
|
|
1099
1180
|
process.exit(reportCLIError(e));
|