@midscene/ios 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/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import node_assert from "node:assert";
2
- import { getMidsceneLocationSchema, z } from "@midscene/core";
3
- import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionLongPress, defineActionPinch, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam, normalizePinchParam } from "@midscene/core/device";
2
+ import { z } from "@midscene/core";
3
+ import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
4
4
  import { sleep } from "@midscene/core/utils";
5
5
  import { DEFAULT_WDA_PORT, PLAYGROUND_SERVER_PORT } from "@midscene/shared/constants";
6
6
  import { createImgBase64ByFormat } from "@midscene/shared/img";
@@ -470,16 +470,6 @@ function _define_property(obj, key, value) {
470
470
  return obj;
471
471
  }
472
472
  const debugDevice = getDebug('ios:device');
473
- const iosInputParamSchema = z.object({
474
- 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.'),
475
- autoDismissKeyboard: z.boolean().optional().describe('Whether to dismiss the keyboard after input. Defaults to true if not specified. Set to false to keep the keyboard visible after input.'),
476
- mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
477
- 'replace',
478
- 'clear',
479
- 'typeOnly'
480
- ]).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" - clear the field without inputting new text.')),
481
- locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
482
- });
483
473
  const WDA_HTTP_METHODS = [
484
474
  'GET',
485
475
  'POST',
@@ -488,96 +478,39 @@ const WDA_HTTP_METHODS = [
488
478
  ];
489
479
  const DEFAULT_WDA_MJPEG_PORT = 9100;
490
480
  class IOSDevice {
481
+ async tapPoint(point) {
482
+ debugDevice(`tap at coordinates (${point.x}, ${point.y})`);
483
+ await this.wdaBackend.tap(Math.round(point.x), Math.round(point.y));
484
+ }
485
+ async doubleTapPoint(point) {
486
+ await this.wdaBackend.doubleTap(Math.round(point.x), Math.round(point.y));
487
+ }
488
+ async longPressPoint(point, duration = 1000) {
489
+ await this.wdaBackend.longPress(Math.round(point.x), Math.round(point.y), duration);
490
+ }
491
+ async swipePoint(start, end, duration = 500) {
492
+ await this.wdaBackend.swipe(Math.round(start.x), Math.round(start.y), Math.round(end.x), Math.round(end.y), duration);
493
+ }
494
+ async clearInputAt(point) {
495
+ if (point) {
496
+ await this.tapPoint(point);
497
+ await sleep(100);
498
+ }
499
+ debugDevice('Attempting to clear input with WebDriver Clear API');
500
+ const cleared = await this.wdaBackend.clearActiveElement();
501
+ cleared ? debugDevice('Successfully cleared input with WebDriver Clear API') : debugDevice('WebDriver Clear API returned false (no active element or clear failed)');
502
+ }
491
503
  actionSpace() {
504
+ const mobileActionContext = {
505
+ input: this.inputPrimitives,
506
+ size: ()=>this.size(),
507
+ sleep: async (timeMs)=>{
508
+ await sleep(timeMs);
509
+ },
510
+ getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard
511
+ };
492
512
  const defaultActions = [
493
- defineActionTap(async (param)=>{
494
- const element = param.locate;
495
- node_assert(element, 'Element not found, cannot tap');
496
- await this.mouseClick(element.center[0], element.center[1]);
497
- }),
498
- defineActionDoubleClick(async (param)=>{
499
- const element = param.locate;
500
- node_assert(element, 'Element not found, cannot double click');
501
- await this.doubleTap(element.center[0], element.center[1]);
502
- }),
503
- defineAction({
504
- name: 'Input',
505
- description: 'Input text into the input field',
506
- interfaceAlias: 'aiInput',
507
- paramSchema: iosInputParamSchema,
508
- sample: {
509
- value: 'test@example.com',
510
- locate: {
511
- prompt: 'the email input field'
512
- }
513
- },
514
- call: async (param)=>{
515
- const element = param.locate;
516
- if ('typeOnly' !== param.mode) await this.clearInput(element);
517
- if ('clear' === param.mode) return;
518
- if (!param || !param.value) return;
519
- const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
520
- await this.typeText(param.value, {
521
- autoDismissKeyboard
522
- });
523
- }
524
- }),
525
- defineActionScroll(async (param)=>{
526
- const element = param.locate;
527
- const startingPoint = element ? {
528
- left: element.center[0],
529
- top: element.center[1]
530
- } : void 0;
531
- const scrollToEventName = param?.scrollType;
532
- if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
533
- else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
534
- else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
535
- else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
536
- else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
537
- else {
538
- if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
539
- else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
540
- else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
541
- else throw new Error(`Unknown scroll direction: ${param.direction}`);
542
- else await this.scrollDown(param?.distance || void 0, startingPoint);
543
- await sleep(500);
544
- }
545
- }),
546
- defineActionDragAndDrop(async (param)=>{
547
- const from = param.from;
548
- const to = param.to;
549
- node_assert(from, 'missing "from" param for drag and drop');
550
- node_assert(to, 'missing "to" param for drag and drop');
551
- await this.swipe(from.center[0], from.center[1], to.center[0], to.center[1], 1000);
552
- }),
553
- defineActionSwipe(async (param)=>{
554
- const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
555
- for(let i = 0; i < repeatCount; i++)await this.swipe(startPoint.x, startPoint.y, endPoint.x, endPoint.y, duration);
556
- }),
557
- defineActionKeyboardPress(async (param)=>{
558
- await this.pressKey(param.keyName);
559
- }),
560
- defineActionCursorMove(async (param)=>{
561
- const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
562
- const times = param.times ?? 1;
563
- for(let i = 0; i < times; i++){
564
- await this.pressKey(arrowKey);
565
- await sleep(100);
566
- }
567
- }),
568
- defineActionLongPress(async (param)=>{
569
- const element = param.locate;
570
- node_assert(element, 'LongPress requires an element to be located');
571
- const [x, y] = element.center;
572
- await this.longPress(x, y, param?.duration);
573
- }),
574
- defineActionPinch(async (param)=>{
575
- const { centerX, centerY, startDistance, endDistance, duration } = normalizePinchParam(param, await this.size());
576
- await this.wdaBackend.pinch(centerX, centerY, startDistance, endDistance, duration);
577
- }),
578
- defineActionClearInput(async (param)=>{
579
- await this.clearInput(param.locate);
580
- })
513
+ ...createDefaultMobileActions(mobileActionContext)
581
514
  ];
582
515
  const platformSpecificActions = Object.values(createPlatformActions(this));
583
516
  const customActions = this.customActions || [];
@@ -587,6 +520,27 @@ class IOSDevice {
587
520
  ...customActions
588
521
  ];
589
522
  }
523
+ async performActionScroll(param) {
524
+ const element = param.locate;
525
+ const startingPoint = element ? {
526
+ left: element.center[0],
527
+ top: element.center[1]
528
+ } : void 0;
529
+ const scrollToEventName = param?.scrollType;
530
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
531
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
532
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
533
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
534
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
535
+ else {
536
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
537
+ else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
538
+ else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
539
+ else throw new Error(`Unknown scroll direction: ${param.direction}`);
540
+ else await this.scrollDown(param?.distance || void 0, startingPoint);
541
+ await sleep(500);
542
+ }
543
+ }
590
544
  describe() {
591
545
  return this.description || `Device ID: ${this.deviceId}`;
592
546
  }
@@ -698,35 +652,31 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
698
652
  }
699
653
  }
700
654
  async clearInput(element) {
701
- if (element) {
702
- await this.tap(element.center[0], element.center[1]);
703
- await sleep(100);
704
- }
705
- debugDevice('Attempting to clear input with WebDriver Clear API');
706
- const cleared = await this.wdaBackend.clearActiveElement();
707
- cleared ? debugDevice('Successfully cleared input with WebDriver Clear API') : debugDevice('WebDriver Clear API returned false (no active element or clear failed)');
655
+ await this.clearInputAt(element ? {
656
+ x: element.center[0],
657
+ y: element.center[1]
658
+ } : void 0);
708
659
  }
709
660
  async url() {
710
661
  return '';
711
662
  }
712
663
  async tap(x, y) {
713
- await this.wdaBackend.tap(Math.round(x), Math.round(y));
714
- }
715
- async mouseClick(x, y) {
716
- debugDevice(`mouseClick at coordinates (${x}, ${y})`);
717
- await this.tap(x, y);
718
- }
719
- async doubleTap(x, y) {
720
- await this.wdaBackend.doubleTap(Math.round(x), Math.round(y));
721
- }
722
- async tripleTap(x, y) {
723
- await this.wdaBackend.tripleTap(Math.round(x), Math.round(y));
724
- }
725
- async longPress(x, y, duration = 1000) {
726
- await this.wdaBackend.longPress(Math.round(x), Math.round(y), duration);
664
+ await this.tapPoint({
665
+ x,
666
+ y
667
+ });
727
668
  }
728
669
  async swipe(fromX, fromY, toX, toY, duration = 500) {
729
- await this.wdaBackend.swipe(Math.round(fromX), Math.round(fromY), Math.round(toX), Math.round(toY), duration);
670
+ await this.swipeCoordinates(fromX, fromY, toX, toY, duration);
671
+ }
672
+ async swipeCoordinates(fromX, fromY, toX, toY, duration = 500) {
673
+ await this.swipePoint({
674
+ x: fromX,
675
+ y: fromY
676
+ }, {
677
+ x: toX,
678
+ y: toY
679
+ }, duration);
730
680
  }
731
681
  async typeText(text, options) {
732
682
  if (!text) return;
@@ -755,7 +705,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
755
705
  y: Math.round(height / 2)
756
706
  };
757
707
  const scrollDistance = Math.round(distance || height / 3);
758
- await this.swipe(start.x, start.y, start.x, start.y + scrollDistance);
708
+ await this.swipeCoordinates(start.x, start.y, start.x, start.y + scrollDistance);
759
709
  }
760
710
  async scrollDown(distance, startPoint) {
761
711
  const { width, height } = await this.size();
@@ -767,7 +717,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
767
717
  y: Math.round(height / 2)
768
718
  };
769
719
  const scrollDistance = Math.round(distance || height / 3);
770
- await this.swipe(start.x, start.y, start.x, start.y - scrollDistance);
720
+ await this.swipeCoordinates(start.x, start.y, start.x, start.y - scrollDistance);
771
721
  }
772
722
  async scrollLeft(distance, startPoint) {
773
723
  const { width, height } = await this.size();
@@ -779,7 +729,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
779
729
  y: Math.round(height / 2)
780
730
  };
781
731
  const scrollDistance = Math.round(distance || 0.7 * width);
782
- await this.swipe(start.x, start.y, start.x + scrollDistance, start.y);
732
+ await this.swipeCoordinates(start.x, start.y, start.x + scrollDistance, start.y);
783
733
  }
784
734
  async scrollRight(distance, startPoint) {
785
735
  const { width, height } = await this.size();
@@ -791,7 +741,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
791
741
  y: Math.round(height / 2)
792
742
  };
793
743
  const scrollDistance = Math.round(distance || 0.7 * width);
794
- await this.swipe(start.x, start.y, start.x - scrollDistance, start.y);
744
+ await this.swipeCoordinates(start.x, start.y, start.x - scrollDistance, start.y);
795
745
  }
796
746
  async scrollUntilTop(startPoint) {
797
747
  debugDevice('Using screenshot-based scroll detection for better reliability');
@@ -887,16 +837,16 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
887
837
  debugDevice(`Performing scroll: ${direction}, distance: ${scrollDistance}`);
888
838
  switch(direction){
889
839
  case 'up':
890
- await this.swipe(start.x, start.y, start.x, start.y + scrollDistance, 300);
840
+ await this.swipeCoordinates(start.x, start.y, start.x, start.y + scrollDistance, 300);
891
841
  break;
892
842
  case 'down':
893
- await this.swipe(start.x, start.y, start.x, start.y - scrollDistance, 300);
843
+ await this.swipeCoordinates(start.x, start.y, start.x, start.y - scrollDistance, 300);
894
844
  break;
895
845
  case 'left':
896
- await this.swipe(start.x, start.y, start.x + scrollDistance, start.y, 300);
846
+ await this.swipeCoordinates(start.x, start.y, start.x + scrollDistance, start.y, 300);
897
847
  break;
898
848
  case 'right':
899
- await this.swipe(start.x, start.y, start.x - scrollDistance, start.y, 300);
849
+ await this.swipeCoordinates(start.x, start.y, start.x - scrollDistance, start.y, 300);
900
850
  break;
901
851
  }
902
852
  debugDevice('Waiting for scroll and inertia to complete...');
@@ -953,7 +903,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
953
903
  const centerX = Math.round(windowSize.width / 2);
954
904
  const startY = Math.round(0.9 * windowSize.height);
955
905
  const endY = Math.round(0.5 * windowSize.height);
956
- await this.swipe(centerX, startY, centerX, endY, 300);
906
+ await this.swipeCoordinates(centerX, startY, centerX, endY, 300);
957
907
  debugDevice('Dismissed keyboard with swipe up gesture from bottom of screen');
958
908
  await sleep(500);
959
909
  return true;
@@ -1030,6 +980,45 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
1030
980
  _define_property(this, "interfaceType", 'ios');
1031
981
  _define_property(this, "uri", void 0);
1032
982
  _define_property(this, "options", void 0);
983
+ _define_property(this, "inputPrimitives", {
984
+ pointer: {
985
+ tap: (point)=>this.tapPoint(point),
986
+ doubleClick: (point)=>this.doubleTapPoint(point),
987
+ longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
988
+ dragAndDrop: (from, to)=>this.swipePoint(from, to, 1000)
989
+ },
990
+ keyboard: {
991
+ keyboardPress: (keyName)=>this.pressKey(keyName),
992
+ typeText: async (value, opts)=>{
993
+ const target = opts?.target;
994
+ if (target && opts?.replace !== false) await this.clearInput(target);
995
+ else if (target) await this.tapPoint({
996
+ x: target.center[0],
997
+ y: target.center[1]
998
+ });
999
+ if (opts?.focusOnly) return;
1000
+ await this.typeText(value, opts);
1001
+ },
1002
+ clearInput: (target)=>this.clearInput(target),
1003
+ cursorMove: async (direction, times = 1)=>{
1004
+ const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
1005
+ for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
1006
+ }
1007
+ },
1008
+ touch: {
1009
+ swipe: async (start, end, opts)=>{
1010
+ const duration = opts?.duration ?? 300;
1011
+ const repeat = opts?.repeat ?? 1;
1012
+ for(let i = 0; i < repeat; i++)await this.swipePoint(start, end, duration);
1013
+ },
1014
+ pinch: async (center, opts)=>{
1015
+ await this.wdaBackend.pinch(Math.round(center.x), Math.round(center.y), opts.startDistance, opts.endDistance, opts.duration);
1016
+ }
1017
+ },
1018
+ scroll: {
1019
+ scroll: (param)=>this.performActionScroll(param)
1020
+ }
1021
+ });
1033
1022
  this.deviceId = 'pending-connection';
1034
1023
  this.options = options;
1035
1024
  this.customActions = options?.customActions;