@midscene/harmony 1.8.0 → 1.8.1
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 +72 -103
- package/dist/es/cli.mjs +73 -104
- package/dist/es/index.mjs +72 -103
- package/dist/es/mcp-server.mjs +73 -104
- package/dist/lib/bin.js +70 -101
- package/dist/lib/cli.js +71 -102
- package/dist/lib/index.js +70 -101
- package/dist/lib/mcp-server.js +71 -102
- package/dist/types/index.d.ts +8 -6
- package/dist/types/mcp-server.d.ts +8 -6
- package/package.json +4 -4
- package/static/index.html +1 -1
- package/static/static/css/{index.63b028da.css → index.26c9c911.css} +2 -2
- package/static/static/css/{index.63b028da.css.map → index.26c9c911.css.map} +1 -1
- package/static/static/js/{889.c8e2e995.js → 596.5426be9e.js} +27 -27
- package/static/static/js/596.5426be9e.js.map +1 -0
- package/static/static/js/index.acaa5ec1.js +914 -0
- package/static/static/js/index.acaa5ec1.js.map +1 -0
- package/static/static/js/889.c8e2e995.js.map +0 -1
- package/static/static/js/index.7d3d953d.js +0 -914
- package/static/static/js/index.7d3d953d.js.map +0 -1
- /package/static/static/js/{889.c8e2e995.js.LICENSE.txt → 596.5426be9e.js.LICENSE.txt} +0 -0
- /package/static/static/js/{index.7d3d953d.js.LICENSE.txt → index.acaa5ec1.js.LICENSE.txt} +0 -0
package/dist/es/bin.mjs
CHANGED
|
@@ -8,8 +8,8 @@ import { getDebug } from "@midscene/shared/logger";
|
|
|
8
8
|
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
9
9
|
import node_assert from "node:assert";
|
|
10
10
|
import node_fs, { accessSync, constants } from "node:fs";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { z } from "@midscene/core";
|
|
12
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
13
13
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
14
14
|
import { createImgBase64ByFormat } from "@midscene/shared/img";
|
|
15
15
|
import { execFile } from "node:child_process";
|
|
@@ -253,15 +253,6 @@ function device_define_property(obj, key, value) {
|
|
|
253
253
|
else obj[key] = value;
|
|
254
254
|
return obj;
|
|
255
255
|
}
|
|
256
|
-
const harmonyInputParamSchema = z.object({
|
|
257
|
-
value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
258
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
259
|
-
'replace',
|
|
260
|
-
'clear',
|
|
261
|
-
'typeOnly'
|
|
262
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - attempt to clear the field (limited support on HarmonyOS).')),
|
|
263
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
264
|
-
});
|
|
265
256
|
const defaultScrollUntilTimes = 10;
|
|
266
257
|
const defaultFastSwipeSpeed = 2000;
|
|
267
258
|
const maxScrollDistance = 9999999;
|
|
@@ -302,89 +293,15 @@ const keyNameAliasMap = {
|
|
|
302
293
|
};
|
|
303
294
|
class device_HarmonyDevice {
|
|
304
295
|
actionSpace() {
|
|
296
|
+
const mobileActionContext = {
|
|
297
|
+
input: this.inputPrimitives,
|
|
298
|
+
size: ()=>this.size(),
|
|
299
|
+
sleep: async (timeMs)=>{
|
|
300
|
+
await sleep(timeMs);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
305
303
|
const defaultActions = [
|
|
306
|
-
|
|
307
|
-
const element = param.locate;
|
|
308
|
-
node_assert(element, 'Element not found, cannot tap');
|
|
309
|
-
await this.tap(element.center[0], element.center[1]);
|
|
310
|
-
}),
|
|
311
|
-
defineActionDoubleClick(async (param)=>{
|
|
312
|
-
const element = param.locate;
|
|
313
|
-
node_assert(element, 'Element not found, cannot double click');
|
|
314
|
-
await this.doubleTap(element.center[0], element.center[1]);
|
|
315
|
-
}),
|
|
316
|
-
defineAction({
|
|
317
|
-
name: 'Input',
|
|
318
|
-
description: 'Input text into the input field',
|
|
319
|
-
interfaceAlias: 'aiInput',
|
|
320
|
-
paramSchema: harmonyInputParamSchema,
|
|
321
|
-
sample: {
|
|
322
|
-
value: 'test@example.com',
|
|
323
|
-
locate: {
|
|
324
|
-
prompt: 'the email input field'
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
call: async (param)=>{
|
|
328
|
-
const element = param.locate;
|
|
329
|
-
if ('clear' === param.mode) return void await this.clearInput(element);
|
|
330
|
-
if (!param || !param.value) return;
|
|
331
|
-
const shouldReplace = 'typeOnly' !== param.mode;
|
|
332
|
-
await this.inputText(param.value, element, shouldReplace);
|
|
333
|
-
}
|
|
334
|
-
}),
|
|
335
|
-
defineActionScroll(async (param)=>{
|
|
336
|
-
const element = param.locate;
|
|
337
|
-
const startingPoint = element ? {
|
|
338
|
-
left: element.center[0],
|
|
339
|
-
top: element.center[1]
|
|
340
|
-
} : void 0;
|
|
341
|
-
const scrollToEventName = param?.scrollType;
|
|
342
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
343
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
344
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
345
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
346
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
347
|
-
else {
|
|
348
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
349
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
350
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
351
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
352
|
-
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
353
|
-
await sleep(500);
|
|
354
|
-
}
|
|
355
|
-
}),
|
|
356
|
-
defineActionDragAndDrop(async (param)=>{
|
|
357
|
-
const from = param.from;
|
|
358
|
-
const to = param.to;
|
|
359
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
360
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
361
|
-
const hdc = await this.getHdc();
|
|
362
|
-
await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
363
|
-
}),
|
|
364
|
-
defineActionSwipe(async (param)=>{
|
|
365
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
366
|
-
const hdc = await this.getHdc();
|
|
367
|
-
for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
|
|
368
|
-
}),
|
|
369
|
-
defineActionKeyboardPress(async (param)=>{
|
|
370
|
-
await this.keyboardPress(param.keyName);
|
|
371
|
-
}),
|
|
372
|
-
defineActionCursorMove(async (param)=>{
|
|
373
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
374
|
-
const times = param.times ?? 1;
|
|
375
|
-
for(let i = 0; i < times; i++){
|
|
376
|
-
await this.keyboardPress(arrowKey);
|
|
377
|
-
await sleep(100);
|
|
378
|
-
}
|
|
379
|
-
}),
|
|
380
|
-
defineActionLongPress(async (param)=>{
|
|
381
|
-
const element = param.locate;
|
|
382
|
-
if (!element) throw new Error('LongPress requires an element to be located');
|
|
383
|
-
await this.longPress(element.center[0], element.center[1]);
|
|
384
|
-
}),
|
|
385
|
-
defineActionClearInput(async (param)=>{
|
|
386
|
-
await this.clearInput(param.locate);
|
|
387
|
-
})
|
|
304
|
+
...createDefaultMobileActions(mobileActionContext)
|
|
388
305
|
];
|
|
389
306
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
390
307
|
const customActions = this.customActions ?? [];
|
|
@@ -394,6 +311,27 @@ class device_HarmonyDevice {
|
|
|
394
311
|
...customActions
|
|
395
312
|
];
|
|
396
313
|
}
|
|
314
|
+
async performActionScroll(param) {
|
|
315
|
+
const element = param.locate;
|
|
316
|
+
const startingPoint = element ? {
|
|
317
|
+
left: element.center[0],
|
|
318
|
+
top: element.center[1]
|
|
319
|
+
} : void 0;
|
|
320
|
+
const scrollToEventName = param?.scrollType;
|
|
321
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
322
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
323
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
324
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
325
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
326
|
+
else {
|
|
327
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
328
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
329
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
330
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
331
|
+
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
332
|
+
await sleep(500);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
397
335
|
describe() {
|
|
398
336
|
return this.descriptionText || `DeviceId: ${this.deviceId}`;
|
|
399
337
|
}
|
|
@@ -524,23 +462,23 @@ class device_HarmonyDevice {
|
|
|
524
462
|
}
|
|
525
463
|
throw new Error('Screenshot buffer is empty after retries');
|
|
526
464
|
}
|
|
527
|
-
async
|
|
465
|
+
async tapPoint(point) {
|
|
528
466
|
this.lastTapPosition = {
|
|
529
|
-
x,
|
|
530
|
-
y
|
|
467
|
+
x: point.x,
|
|
468
|
+
y: point.y
|
|
531
469
|
};
|
|
532
470
|
const hdc = await this.getHdc();
|
|
533
|
-
await hdc.click(x, y);
|
|
471
|
+
await hdc.click(point.x, point.y);
|
|
534
472
|
}
|
|
535
|
-
async
|
|
473
|
+
async doubleTapPoint(point) {
|
|
536
474
|
const hdc = await this.getHdc();
|
|
537
|
-
await hdc.doubleClick(x, y);
|
|
475
|
+
await hdc.doubleClick(point.x, point.y);
|
|
538
476
|
}
|
|
539
|
-
async
|
|
477
|
+
async longPressPoint(point) {
|
|
540
478
|
const hdc = await this.getHdc();
|
|
541
|
-
await hdc.longClick(x, y);
|
|
479
|
+
await hdc.longClick(point.x, point.y);
|
|
542
480
|
}
|
|
543
|
-
async
|
|
481
|
+
async typeText(text, element, shouldReplace) {
|
|
544
482
|
if (!text) return;
|
|
545
483
|
const hdc = await this.getHdc();
|
|
546
484
|
let x;
|
|
@@ -571,7 +509,7 @@ class device_HarmonyDevice {
|
|
|
571
509
|
}
|
|
572
510
|
await hdc.clearTextField(100);
|
|
573
511
|
}
|
|
574
|
-
async
|
|
512
|
+
async pressKey(key) {
|
|
575
513
|
const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
|
|
576
514
|
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
577
515
|
const hdc = await this.getHdc();
|
|
@@ -771,6 +709,37 @@ class device_HarmonyDevice {
|
|
|
771
709
|
device_define_property(this, "interfaceType", 'harmony');
|
|
772
710
|
device_define_property(this, "uri", void 0);
|
|
773
711
|
device_define_property(this, "options", void 0);
|
|
712
|
+
device_define_property(this, "inputPrimitives", {
|
|
713
|
+
pointer: {
|
|
714
|
+
tap: (point)=>this.tapPoint(point),
|
|
715
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
716
|
+
longPress: (point)=>this.longPressPoint(point),
|
|
717
|
+
dragAndDrop: async (from, to)=>{
|
|
718
|
+
const hdc = await this.getHdc();
|
|
719
|
+
await hdc.drag(from.x, from.y, to.x, to.y);
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
keyboard: {
|
|
723
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
724
|
+
typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
|
|
725
|
+
clearInput: (target)=>this.clearInput(target),
|
|
726
|
+
cursorMove: async (direction, times = 1)=>{
|
|
727
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
728
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
touch: {
|
|
732
|
+
swipe: async (start, end, opts)=>{
|
|
733
|
+
const duration = opts?.duration;
|
|
734
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
735
|
+
const hdc = await this.getHdc();
|
|
736
|
+
for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
scroll: {
|
|
740
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
741
|
+
}
|
|
742
|
+
});
|
|
774
743
|
device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
|
|
775
744
|
device_define_property(this, "localScreenshotPath", null);
|
|
776
745
|
node_assert(deviceId, 'deviceId is required for HarmonyDevice');
|
package/dist/es/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createReportCliCommands,
|
|
1
|
+
import { createReportCliCommands, z } from "@midscene/core";
|
|
2
2
|
import { reportCLIError, runToolsCLI } from "@midscene/shared/cli";
|
|
3
3
|
import { getDebug } from "@midscene/shared/logger";
|
|
4
4
|
import { BaseMidsceneTools } from "@midscene/shared/mcp/base-tools";
|
|
@@ -6,7 +6,7 @@ import { Agent } from "@midscene/core/agent";
|
|
|
6
6
|
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
7
7
|
import node_assert from "node:assert";
|
|
8
8
|
import node_fs, { accessSync, constants } from "node:fs";
|
|
9
|
-
import {
|
|
9
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
10
10
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
11
11
|
import { createImgBase64ByFormat } from "@midscene/shared/img";
|
|
12
12
|
import { execFile } from "node:child_process";
|
|
@@ -250,15 +250,6 @@ function device_define_property(obj, key, value) {
|
|
|
250
250
|
else obj[key] = value;
|
|
251
251
|
return obj;
|
|
252
252
|
}
|
|
253
|
-
const harmonyInputParamSchema = z.object({
|
|
254
|
-
value: z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
255
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
256
|
-
'replace',
|
|
257
|
-
'clear',
|
|
258
|
-
'typeOnly'
|
|
259
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - attempt to clear the field (limited support on HarmonyOS).')),
|
|
260
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
261
|
-
});
|
|
262
253
|
const defaultScrollUntilTimes = 10;
|
|
263
254
|
const defaultFastSwipeSpeed = 2000;
|
|
264
255
|
const maxScrollDistance = 9999999;
|
|
@@ -299,89 +290,15 @@ const keyNameAliasMap = {
|
|
|
299
290
|
};
|
|
300
291
|
class HarmonyDevice {
|
|
301
292
|
actionSpace() {
|
|
293
|
+
const mobileActionContext = {
|
|
294
|
+
input: this.inputPrimitives,
|
|
295
|
+
size: ()=>this.size(),
|
|
296
|
+
sleep: async (timeMs)=>{
|
|
297
|
+
await sleep(timeMs);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
302
300
|
const defaultActions = [
|
|
303
|
-
|
|
304
|
-
const element = param.locate;
|
|
305
|
-
node_assert(element, 'Element not found, cannot tap');
|
|
306
|
-
await this.tap(element.center[0], element.center[1]);
|
|
307
|
-
}),
|
|
308
|
-
defineActionDoubleClick(async (param)=>{
|
|
309
|
-
const element = param.locate;
|
|
310
|
-
node_assert(element, 'Element not found, cannot double click');
|
|
311
|
-
await this.doubleTap(element.center[0], element.center[1]);
|
|
312
|
-
}),
|
|
313
|
-
defineAction({
|
|
314
|
-
name: 'Input',
|
|
315
|
-
description: 'Input text into the input field',
|
|
316
|
-
interfaceAlias: 'aiInput',
|
|
317
|
-
paramSchema: harmonyInputParamSchema,
|
|
318
|
-
sample: {
|
|
319
|
-
value: 'test@example.com',
|
|
320
|
-
locate: {
|
|
321
|
-
prompt: 'the email input field'
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
call: async (param)=>{
|
|
325
|
-
const element = param.locate;
|
|
326
|
-
if ('clear' === param.mode) return void await this.clearInput(element);
|
|
327
|
-
if (!param || !param.value) return;
|
|
328
|
-
const shouldReplace = 'typeOnly' !== param.mode;
|
|
329
|
-
await this.inputText(param.value, element, shouldReplace);
|
|
330
|
-
}
|
|
331
|
-
}),
|
|
332
|
-
defineActionScroll(async (param)=>{
|
|
333
|
-
const element = param.locate;
|
|
334
|
-
const startingPoint = element ? {
|
|
335
|
-
left: element.center[0],
|
|
336
|
-
top: element.center[1]
|
|
337
|
-
} : void 0;
|
|
338
|
-
const scrollToEventName = param?.scrollType;
|
|
339
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
340
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
341
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
342
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
343
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
344
|
-
else {
|
|
345
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
346
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
347
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
348
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
349
|
-
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
350
|
-
await sleep(500);
|
|
351
|
-
}
|
|
352
|
-
}),
|
|
353
|
-
defineActionDragAndDrop(async (param)=>{
|
|
354
|
-
const from = param.from;
|
|
355
|
-
const to = param.to;
|
|
356
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
357
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
358
|
-
const hdc = await this.getHdc();
|
|
359
|
-
await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
360
|
-
}),
|
|
361
|
-
defineActionSwipe(async (param)=>{
|
|
362
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
363
|
-
const hdc = await this.getHdc();
|
|
364
|
-
for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
|
|
365
|
-
}),
|
|
366
|
-
defineActionKeyboardPress(async (param)=>{
|
|
367
|
-
await this.keyboardPress(param.keyName);
|
|
368
|
-
}),
|
|
369
|
-
defineActionCursorMove(async (param)=>{
|
|
370
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
371
|
-
const times = param.times ?? 1;
|
|
372
|
-
for(let i = 0; i < times; i++){
|
|
373
|
-
await this.keyboardPress(arrowKey);
|
|
374
|
-
await sleep(100);
|
|
375
|
-
}
|
|
376
|
-
}),
|
|
377
|
-
defineActionLongPress(async (param)=>{
|
|
378
|
-
const element = param.locate;
|
|
379
|
-
if (!element) throw new Error('LongPress requires an element to be located');
|
|
380
|
-
await this.longPress(element.center[0], element.center[1]);
|
|
381
|
-
}),
|
|
382
|
-
defineActionClearInput(async (param)=>{
|
|
383
|
-
await this.clearInput(param.locate);
|
|
384
|
-
})
|
|
301
|
+
...createDefaultMobileActions(mobileActionContext)
|
|
385
302
|
];
|
|
386
303
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
387
304
|
const customActions = this.customActions ?? [];
|
|
@@ -391,6 +308,27 @@ class HarmonyDevice {
|
|
|
391
308
|
...customActions
|
|
392
309
|
];
|
|
393
310
|
}
|
|
311
|
+
async performActionScroll(param) {
|
|
312
|
+
const element = param.locate;
|
|
313
|
+
const startingPoint = element ? {
|
|
314
|
+
left: element.center[0],
|
|
315
|
+
top: element.center[1]
|
|
316
|
+
} : void 0;
|
|
317
|
+
const scrollToEventName = param?.scrollType;
|
|
318
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
319
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
320
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
321
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
322
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
323
|
+
else {
|
|
324
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
|
|
325
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
|
|
326
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
|
|
327
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
328
|
+
else await this.scrollDown(param?.distance ?? void 0, startingPoint);
|
|
329
|
+
await sleep(500);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
394
332
|
describe() {
|
|
395
333
|
return this.descriptionText || `DeviceId: ${this.deviceId}`;
|
|
396
334
|
}
|
|
@@ -521,23 +459,23 @@ class HarmonyDevice {
|
|
|
521
459
|
}
|
|
522
460
|
throw new Error('Screenshot buffer is empty after retries');
|
|
523
461
|
}
|
|
524
|
-
async
|
|
462
|
+
async tapPoint(point) {
|
|
525
463
|
this.lastTapPosition = {
|
|
526
|
-
x,
|
|
527
|
-
y
|
|
464
|
+
x: point.x,
|
|
465
|
+
y: point.y
|
|
528
466
|
};
|
|
529
467
|
const hdc = await this.getHdc();
|
|
530
|
-
await hdc.click(x, y);
|
|
468
|
+
await hdc.click(point.x, point.y);
|
|
531
469
|
}
|
|
532
|
-
async
|
|
470
|
+
async doubleTapPoint(point) {
|
|
533
471
|
const hdc = await this.getHdc();
|
|
534
|
-
await hdc.doubleClick(x, y);
|
|
472
|
+
await hdc.doubleClick(point.x, point.y);
|
|
535
473
|
}
|
|
536
|
-
async
|
|
474
|
+
async longPressPoint(point) {
|
|
537
475
|
const hdc = await this.getHdc();
|
|
538
|
-
await hdc.longClick(x, y);
|
|
476
|
+
await hdc.longClick(point.x, point.y);
|
|
539
477
|
}
|
|
540
|
-
async
|
|
478
|
+
async typeText(text, element, shouldReplace) {
|
|
541
479
|
if (!text) return;
|
|
542
480
|
const hdc = await this.getHdc();
|
|
543
481
|
let x;
|
|
@@ -568,7 +506,7 @@ class HarmonyDevice {
|
|
|
568
506
|
}
|
|
569
507
|
await hdc.clearTextField(100);
|
|
570
508
|
}
|
|
571
|
-
async
|
|
509
|
+
async pressKey(key) {
|
|
572
510
|
const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
|
|
573
511
|
const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
|
|
574
512
|
const hdc = await this.getHdc();
|
|
@@ -768,6 +706,37 @@ class HarmonyDevice {
|
|
|
768
706
|
device_define_property(this, "interfaceType", 'harmony');
|
|
769
707
|
device_define_property(this, "uri", void 0);
|
|
770
708
|
device_define_property(this, "options", void 0);
|
|
709
|
+
device_define_property(this, "inputPrimitives", {
|
|
710
|
+
pointer: {
|
|
711
|
+
tap: (point)=>this.tapPoint(point),
|
|
712
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
713
|
+
longPress: (point)=>this.longPressPoint(point),
|
|
714
|
+
dragAndDrop: async (from, to)=>{
|
|
715
|
+
const hdc = await this.getHdc();
|
|
716
|
+
await hdc.drag(from.x, from.y, to.x, to.y);
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
keyboard: {
|
|
720
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
721
|
+
typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
|
|
722
|
+
clearInput: (target)=>this.clearInput(target),
|
|
723
|
+
cursorMove: async (direction, times = 1)=>{
|
|
724
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
725
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
touch: {
|
|
729
|
+
swipe: async (start, end, opts)=>{
|
|
730
|
+
const duration = opts?.duration;
|
|
731
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
732
|
+
const hdc = await this.getHdc();
|
|
733
|
+
for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
scroll: {
|
|
737
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
738
|
+
}
|
|
739
|
+
});
|
|
771
740
|
device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
|
|
772
741
|
device_define_property(this, "localScreenshotPath", null);
|
|
773
742
|
node_assert(deviceId, 'deviceId is required for HarmonyDevice');
|
|
@@ -1022,7 +991,7 @@ class HarmonyMidsceneTools extends BaseMidsceneTools {
|
|
|
1022
991
|
const tools = new HarmonyMidsceneTools();
|
|
1023
992
|
runToolsCLI(tools, 'midscene-harmony', {
|
|
1024
993
|
stripPrefix: 'harmony_',
|
|
1025
|
-
version: "1.8.
|
|
994
|
+
version: "1.8.1",
|
|
1026
995
|
extraCommands: createReportCliCommands()
|
|
1027
996
|
}).catch((e)=>{
|
|
1028
997
|
process.exit(reportCLIError(e));
|