@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/lib/bin.js CHANGED
@@ -279,15 +279,6 @@ function device_define_property(obj, key, value) {
279
279
  else obj[key] = value;
280
280
  return obj;
281
281
  }
282
- const harmonyInputParamSchema = core_namespaceObject.z.object({
283
- value: core_namespaceObject.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.'),
284
- mode: core_namespaceObject.z.preprocess((val)=>'append' === val ? 'typeOnly' : val, core_namespaceObject.z["enum"]([
285
- 'replace',
286
- 'clear',
287
- 'typeOnly'
288
- ]).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).')),
289
- locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
290
- });
291
282
  const defaultScrollUntilTimes = 10;
292
283
  const defaultFastSwipeSpeed = 2000;
293
284
  const maxScrollDistance = 9999999;
@@ -328,89 +319,15 @@ const keyNameAliasMap = {
328
319
  };
329
320
  class device_HarmonyDevice {
330
321
  actionSpace() {
322
+ const mobileActionContext = {
323
+ input: this.inputPrimitives,
324
+ size: ()=>this.size(),
325
+ sleep: async (timeMs)=>{
326
+ await (0, core_utils_namespaceObject.sleep)(timeMs);
327
+ }
328
+ };
331
329
  const defaultActions = [
332
- (0, device_namespaceObject.defineActionTap)(async (param)=>{
333
- const element = param.locate;
334
- external_node_assert_default()(element, 'Element not found, cannot tap');
335
- await this.tap(element.center[0], element.center[1]);
336
- }),
337
- (0, device_namespaceObject.defineActionDoubleClick)(async (param)=>{
338
- const element = param.locate;
339
- external_node_assert_default()(element, 'Element not found, cannot double click');
340
- await this.doubleTap(element.center[0], element.center[1]);
341
- }),
342
- (0, device_namespaceObject.defineAction)({
343
- name: 'Input',
344
- description: 'Input text into the input field',
345
- interfaceAlias: 'aiInput',
346
- paramSchema: harmonyInputParamSchema,
347
- sample: {
348
- value: 'test@example.com',
349
- locate: {
350
- prompt: 'the email input field'
351
- }
352
- },
353
- call: async (param)=>{
354
- const element = param.locate;
355
- if ('clear' === param.mode) return void await this.clearInput(element);
356
- if (!param || !param.value) return;
357
- const shouldReplace = 'typeOnly' !== param.mode;
358
- await this.inputText(param.value, element, shouldReplace);
359
- }
360
- }),
361
- (0, device_namespaceObject.defineActionScroll)(async (param)=>{
362
- const element = param.locate;
363
- const startingPoint = element ? {
364
- left: element.center[0],
365
- top: element.center[1]
366
- } : void 0;
367
- const scrollToEventName = param?.scrollType;
368
- if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
369
- else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
370
- else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
371
- else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
372
- else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
373
- else {
374
- if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
375
- else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
376
- else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
377
- else throw new Error(`Unknown scroll direction: ${param.direction}`);
378
- else await this.scrollDown(param?.distance ?? void 0, startingPoint);
379
- await (0, core_utils_namespaceObject.sleep)(500);
380
- }
381
- }),
382
- (0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
383
- const from = param.from;
384
- const to = param.to;
385
- external_node_assert_default()(from, 'missing "from" param for drag and drop');
386
- external_node_assert_default()(to, 'missing "to" param for drag and drop');
387
- const hdc = await this.getHdc();
388
- await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
389
- }),
390
- (0, device_namespaceObject.defineActionSwipe)(async (param)=>{
391
- const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
392
- const hdc = await this.getHdc();
393
- for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
394
- }),
395
- (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
396
- await this.keyboardPress(param.keyName);
397
- }),
398
- (0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
399
- const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
400
- const times = param.times ?? 1;
401
- for(let i = 0; i < times; i++){
402
- await this.keyboardPress(arrowKey);
403
- await (0, core_utils_namespaceObject.sleep)(100);
404
- }
405
- }),
406
- (0, device_namespaceObject.defineActionLongPress)(async (param)=>{
407
- const element = param.locate;
408
- if (!element) throw new Error('LongPress requires an element to be located');
409
- await this.longPress(element.center[0], element.center[1]);
410
- }),
411
- (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
412
- await this.clearInput(param.locate);
413
- })
330
+ ...(0, device_namespaceObject.createDefaultMobileActions)(mobileActionContext)
414
331
  ];
415
332
  const platformSpecificActions = Object.values(createPlatformActions(this));
416
333
  const customActions = this.customActions ?? [];
@@ -420,6 +337,27 @@ class device_HarmonyDevice {
420
337
  ...customActions
421
338
  ];
422
339
  }
340
+ async performActionScroll(param) {
341
+ const element = param.locate;
342
+ const startingPoint = element ? {
343
+ left: element.center[0],
344
+ top: element.center[1]
345
+ } : void 0;
346
+ const scrollToEventName = param?.scrollType;
347
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
348
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
349
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
350
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
351
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
352
+ else {
353
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
354
+ else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
355
+ else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
356
+ else throw new Error(`Unknown scroll direction: ${param.direction}`);
357
+ else await this.scrollDown(param?.distance ?? void 0, startingPoint);
358
+ await (0, core_utils_namespaceObject.sleep)(500);
359
+ }
360
+ }
423
361
  describe() {
424
362
  return this.descriptionText || `DeviceId: ${this.deviceId}`;
425
363
  }
@@ -550,23 +488,23 @@ class device_HarmonyDevice {
550
488
  }
551
489
  throw new Error('Screenshot buffer is empty after retries');
552
490
  }
553
- async tap(x, y) {
491
+ async tapPoint(point) {
554
492
  this.lastTapPosition = {
555
- x,
556
- y
493
+ x: point.x,
494
+ y: point.y
557
495
  };
558
496
  const hdc = await this.getHdc();
559
- await hdc.click(x, y);
497
+ await hdc.click(point.x, point.y);
560
498
  }
561
- async doubleTap(x, y) {
499
+ async doubleTapPoint(point) {
562
500
  const hdc = await this.getHdc();
563
- await hdc.doubleClick(x, y);
501
+ await hdc.doubleClick(point.x, point.y);
564
502
  }
565
- async longPress(x, y) {
503
+ async longPressPoint(point) {
566
504
  const hdc = await this.getHdc();
567
- await hdc.longClick(x, y);
505
+ await hdc.longClick(point.x, point.y);
568
506
  }
569
- async inputText(text, element, shouldReplace) {
507
+ async typeText(text, element, shouldReplace) {
570
508
  if (!text) return;
571
509
  const hdc = await this.getHdc();
572
510
  let x;
@@ -597,7 +535,7 @@ class device_HarmonyDevice {
597
535
  }
598
536
  await hdc.clearTextField(100);
599
537
  }
600
- async keyboardPress(key) {
538
+ async pressKey(key) {
601
539
  const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
602
540
  const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
603
541
  const hdc = await this.getHdc();
@@ -797,6 +735,37 @@ class device_HarmonyDevice {
797
735
  device_define_property(this, "interfaceType", 'harmony');
798
736
  device_define_property(this, "uri", void 0);
799
737
  device_define_property(this, "options", void 0);
738
+ device_define_property(this, "inputPrimitives", {
739
+ pointer: {
740
+ tap: (point)=>this.tapPoint(point),
741
+ doubleClick: (point)=>this.doubleTapPoint(point),
742
+ longPress: (point)=>this.longPressPoint(point),
743
+ dragAndDrop: async (from, to)=>{
744
+ const hdc = await this.getHdc();
745
+ await hdc.drag(from.x, from.y, to.x, to.y);
746
+ }
747
+ },
748
+ keyboard: {
749
+ keyboardPress: (keyName)=>this.pressKey(keyName),
750
+ typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
751
+ clearInput: (target)=>this.clearInput(target),
752
+ cursorMove: async (direction, times = 1)=>{
753
+ const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
754
+ for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
755
+ }
756
+ },
757
+ touch: {
758
+ swipe: async (start, end, opts)=>{
759
+ const duration = opts?.duration;
760
+ const repeatCount = opts?.repeat ?? 1;
761
+ const hdc = await this.getHdc();
762
+ for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
763
+ }
764
+ },
765
+ scroll: {
766
+ scroll: (param)=>this.performActionScroll(param)
767
+ }
768
+ });
800
769
  device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
801
770
  device_define_property(this, "localScreenshotPath", null);
802
771
  external_node_assert_default()(deviceId, 'deviceId is required for HarmonyDevice');
package/dist/lib/cli.js CHANGED
@@ -275,15 +275,6 @@ function device_define_property(obj, key, value) {
275
275
  else obj[key] = value;
276
276
  return obj;
277
277
  }
278
- const harmonyInputParamSchema = core_namespaceObject.z.object({
279
- value: core_namespaceObject.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.'),
280
- mode: core_namespaceObject.z.preprocess((val)=>'append' === val ? 'typeOnly' : val, core_namespaceObject.z["enum"]([
281
- 'replace',
282
- 'clear',
283
- 'typeOnly'
284
- ]).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).')),
285
- locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
286
- });
287
278
  const defaultScrollUntilTimes = 10;
288
279
  const defaultFastSwipeSpeed = 2000;
289
280
  const maxScrollDistance = 9999999;
@@ -324,89 +315,15 @@ const keyNameAliasMap = {
324
315
  };
325
316
  class HarmonyDevice {
326
317
  actionSpace() {
318
+ const mobileActionContext = {
319
+ input: this.inputPrimitives,
320
+ size: ()=>this.size(),
321
+ sleep: async (timeMs)=>{
322
+ await (0, core_utils_namespaceObject.sleep)(timeMs);
323
+ }
324
+ };
327
325
  const defaultActions = [
328
- (0, device_namespaceObject.defineActionTap)(async (param)=>{
329
- const element = param.locate;
330
- external_node_assert_default()(element, 'Element not found, cannot tap');
331
- await this.tap(element.center[0], element.center[1]);
332
- }),
333
- (0, device_namespaceObject.defineActionDoubleClick)(async (param)=>{
334
- const element = param.locate;
335
- external_node_assert_default()(element, 'Element not found, cannot double click');
336
- await this.doubleTap(element.center[0], element.center[1]);
337
- }),
338
- (0, device_namespaceObject.defineAction)({
339
- name: 'Input',
340
- description: 'Input text into the input field',
341
- interfaceAlias: 'aiInput',
342
- paramSchema: harmonyInputParamSchema,
343
- sample: {
344
- value: 'test@example.com',
345
- locate: {
346
- prompt: 'the email input field'
347
- }
348
- },
349
- call: async (param)=>{
350
- const element = param.locate;
351
- if ('clear' === param.mode) return void await this.clearInput(element);
352
- if (!param || !param.value) return;
353
- const shouldReplace = 'typeOnly' !== param.mode;
354
- await this.inputText(param.value, element, shouldReplace);
355
- }
356
- }),
357
- (0, device_namespaceObject.defineActionScroll)(async (param)=>{
358
- const element = param.locate;
359
- const startingPoint = element ? {
360
- left: element.center[0],
361
- top: element.center[1]
362
- } : void 0;
363
- const scrollToEventName = param?.scrollType;
364
- if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
365
- else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
366
- else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
367
- else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
368
- else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
369
- else {
370
- if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
371
- else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
372
- else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
373
- else throw new Error(`Unknown scroll direction: ${param.direction}`);
374
- else await this.scrollDown(param?.distance ?? void 0, startingPoint);
375
- await (0, core_utils_namespaceObject.sleep)(500);
376
- }
377
- }),
378
- (0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
379
- const from = param.from;
380
- const to = param.to;
381
- external_node_assert_default()(from, 'missing "from" param for drag and drop');
382
- external_node_assert_default()(to, 'missing "to" param for drag and drop');
383
- const hdc = await this.getHdc();
384
- await hdc.drag(from.center[0], from.center[1], to.center[0], to.center[1]);
385
- }),
386
- (0, device_namespaceObject.defineActionSwipe)(async (param)=>{
387
- const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
388
- const hdc = await this.getHdc();
389
- for(let i = 0; i < repeatCount; i++)await hdc.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration ? Math.round(duration) : void 0);
390
- }),
391
- (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
392
- await this.keyboardPress(param.keyName);
393
- }),
394
- (0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
395
- const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
396
- const times = param.times ?? 1;
397
- for(let i = 0; i < times; i++){
398
- await this.keyboardPress(arrowKey);
399
- await (0, core_utils_namespaceObject.sleep)(100);
400
- }
401
- }),
402
- (0, device_namespaceObject.defineActionLongPress)(async (param)=>{
403
- const element = param.locate;
404
- if (!element) throw new Error('LongPress requires an element to be located');
405
- await this.longPress(element.center[0], element.center[1]);
406
- }),
407
- (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
408
- await this.clearInput(param.locate);
409
- })
326
+ ...(0, device_namespaceObject.createDefaultMobileActions)(mobileActionContext)
410
327
  ];
411
328
  const platformSpecificActions = Object.values(createPlatformActions(this));
412
329
  const customActions = this.customActions ?? [];
@@ -416,6 +333,27 @@ class HarmonyDevice {
416
333
  ...customActions
417
334
  ];
418
335
  }
336
+ async performActionScroll(param) {
337
+ const element = param.locate;
338
+ const startingPoint = element ? {
339
+ left: element.center[0],
340
+ top: element.center[1]
341
+ } : void 0;
342
+ const scrollToEventName = param?.scrollType;
343
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
344
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
345
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
346
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
347
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
348
+ else {
349
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance ?? void 0, startingPoint);
350
+ else if ('left' === param.direction) await this.scrollLeft(param.distance ?? void 0, startingPoint);
351
+ else if ('right' === param.direction) await this.scrollRight(param.distance ?? void 0, startingPoint);
352
+ else throw new Error(`Unknown scroll direction: ${param.direction}`);
353
+ else await this.scrollDown(param?.distance ?? void 0, startingPoint);
354
+ await (0, core_utils_namespaceObject.sleep)(500);
355
+ }
356
+ }
419
357
  describe() {
420
358
  return this.descriptionText || `DeviceId: ${this.deviceId}`;
421
359
  }
@@ -546,23 +484,23 @@ class HarmonyDevice {
546
484
  }
547
485
  throw new Error('Screenshot buffer is empty after retries');
548
486
  }
549
- async tap(x, y) {
487
+ async tapPoint(point) {
550
488
  this.lastTapPosition = {
551
- x,
552
- y
489
+ x: point.x,
490
+ y: point.y
553
491
  };
554
492
  const hdc = await this.getHdc();
555
- await hdc.click(x, y);
493
+ await hdc.click(point.x, point.y);
556
494
  }
557
- async doubleTap(x, y) {
495
+ async doubleTapPoint(point) {
558
496
  const hdc = await this.getHdc();
559
- await hdc.doubleClick(x, y);
497
+ await hdc.doubleClick(point.x, point.y);
560
498
  }
561
- async longPress(x, y) {
499
+ async longPressPoint(point) {
562
500
  const hdc = await this.getHdc();
563
- await hdc.longClick(x, y);
501
+ await hdc.longClick(point.x, point.y);
564
502
  }
565
- async inputText(text, element, shouldReplace) {
503
+ async typeText(text, element, shouldReplace) {
566
504
  if (!text) return;
567
505
  const hdc = await this.getHdc();
568
506
  let x;
@@ -593,7 +531,7 @@ class HarmonyDevice {
593
531
  }
594
532
  await hdc.clearTextField(100);
595
533
  }
596
- async keyboardPress(key) {
534
+ async pressKey(key) {
597
535
  const normalizedKey = keyNameAliasMap[key.toLowerCase()] ?? key;
598
536
  const harmonyKey = harmonyKeyCodeMap[normalizedKey] ?? key;
599
537
  const hdc = await this.getHdc();
@@ -793,6 +731,37 @@ class HarmonyDevice {
793
731
  device_define_property(this, "interfaceType", 'harmony');
794
732
  device_define_property(this, "uri", void 0);
795
733
  device_define_property(this, "options", void 0);
734
+ device_define_property(this, "inputPrimitives", {
735
+ pointer: {
736
+ tap: (point)=>this.tapPoint(point),
737
+ doubleClick: (point)=>this.doubleTapPoint(point),
738
+ longPress: (point)=>this.longPressPoint(point),
739
+ dragAndDrop: async (from, to)=>{
740
+ const hdc = await this.getHdc();
741
+ await hdc.drag(from.x, from.y, to.x, to.y);
742
+ }
743
+ },
744
+ keyboard: {
745
+ keyboardPress: (keyName)=>this.pressKey(keyName),
746
+ typeText: (value, opts)=>opts?.focusOnly ? Promise.resolve() : this.typeText(value, opts?.target, opts?.replace ?? true),
747
+ clearInput: (target)=>this.clearInput(target),
748
+ cursorMove: async (direction, times = 1)=>{
749
+ const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
750
+ for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
751
+ }
752
+ },
753
+ touch: {
754
+ swipe: async (start, end, opts)=>{
755
+ const duration = opts?.duration;
756
+ const repeatCount = opts?.repeat ?? 1;
757
+ const hdc = await this.getHdc();
758
+ for(let i = 0; i < repeatCount; i++)await hdc.swipe(start.x, start.y, end.x, end.y, duration ? Math.round(duration) : void 0);
759
+ }
760
+ },
761
+ scroll: {
762
+ scroll: (param)=>this.performActionScroll(param)
763
+ }
764
+ });
796
765
  device_define_property(this, "remoteScreenshotPath", '/data/local/tmp/ms_screen.jpeg');
797
766
  device_define_property(this, "localScreenshotPath", null);
798
767
  external_node_assert_default()(deviceId, 'deviceId is required for HarmonyDevice');
@@ -1047,7 +1016,7 @@ class HarmonyMidsceneTools extends base_tools_namespaceObject.BaseMidsceneTools
1047
1016
  const tools = new HarmonyMidsceneTools();
1048
1017
  (0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-harmony', {
1049
1018
  stripPrefix: 'harmony_',
1050
- version: "1.8.0",
1019
+ version: "1.8.1",
1051
1020
  extraCommands: (0, core_namespaceObject.createReportCliCommands)()
1052
1021
  }).catch((e)=>{
1053
1022
  process.exit((0, cli_namespaceObject.reportCLIError)(e));