@midscene/ios 0.30.10 → 1.0.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.
Files changed (65) hide show
  1. package/README.md +11 -11
  2. package/dist/es/bin.mjs +423 -327
  3. package/dist/es/index.mjs +150 -64
  4. package/dist/lib/bin.js +393 -283
  5. package/dist/lib/index.js +155 -69
  6. package/dist/types/index.d.ts +88 -14
  7. package/package.json +8 -7
  8. package/static/index.html +1 -1
  9. package/static/static/css/index.d32b7df9.css +2 -0
  10. package/static/static/css/index.d32b7df9.css.map +1 -0
  11. package/static/static/js/79.25af61dc.js +611 -0
  12. package/static/static/js/{931.dc961e99.js.LICENSE.txt → 79.25af61dc.js.LICENSE.txt} +0 -4
  13. package/static/static/js/79.25af61dc.js.map +1 -0
  14. package/static/static/js/async/195.0366f6e8.js +3 -0
  15. package/static/static/js/async/195.0366f6e8.js.map +1 -0
  16. package/static/static/js/async/{702.60261735.js → 199.f31e52e7.js} +20 -20
  17. package/static/static/js/async/199.f31e52e7.js.map +1 -0
  18. package/static/static/js/async/221.591b048e.js +21 -0
  19. package/static/static/js/async/221.591b048e.js.map +1 -0
  20. package/static/static/js/async/271.15d46ff8.js +30 -0
  21. package/static/static/js/async/271.15d46ff8.js.map +1 -0
  22. package/static/static/js/async/35.2b64fb0f.js +1 -0
  23. package/static/static/js/async/{644.6bdc4065.js → 467.710fa05a.js} +1 -1
  24. package/static/static/js/async/652.b5a7c7b4.js +3 -0
  25. package/static/static/js/async/652.b5a7c7b4.js.map +1 -0
  26. package/static/static/js/async/856.be9fd814.js +158 -0
  27. package/static/static/js/async/{212.e243c338.js.map → 856.be9fd814.js.map} +1 -1
  28. package/static/static/js/async/860.b56301d9.js +2 -0
  29. package/static/static/js/async/860.b56301d9.js.map +1 -0
  30. package/static/static/js/async/990.82a78a53.js +26 -0
  31. package/static/static/js/async/990.82a78a53.js.map +1 -0
  32. package/static/static/js/index.0930f837.js +10 -0
  33. package/static/static/js/index.0930f837.js.map +1 -0
  34. package/static/static/js/lib-react.7b1abe58.js +3 -0
  35. package/static/static/js/lib-react.7b1abe58.js.map +1 -0
  36. package/static/static/css/index.44466eb4.css +0 -2
  37. package/static/static/css/index.44466eb4.css.map +0 -1
  38. package/static/static/js/931.dc961e99.js +0 -620
  39. package/static/static/js/931.dc961e99.js.map +0 -1
  40. package/static/static/js/async/173.9cf6b074.js +0 -3
  41. package/static/static/js/async/173.9cf6b074.js.map +0 -1
  42. package/static/static/js/async/212.e243c338.js +0 -158
  43. package/static/static/js/async/329.f888b505.js +0 -26
  44. package/static/static/js/async/329.f888b505.js.map +0 -1
  45. package/static/static/js/async/364.1821e74b.js +0 -30
  46. package/static/static/js/async/364.1821e74b.js.map +0 -1
  47. package/static/static/js/async/544.b73fa603.js +0 -2
  48. package/static/static/js/async/544.b73fa603.js.map +0 -1
  49. package/static/static/js/async/582.5dccae2d.js +0 -21
  50. package/static/static/js/async/582.5dccae2d.js.map +0 -1
  51. package/static/static/js/async/624.45ee2b2c.js +0 -3
  52. package/static/static/js/async/624.45ee2b2c.js.map +0 -1
  53. package/static/static/js/async/659.9afd03db.js +0 -21
  54. package/static/static/js/async/659.9afd03db.js.map +0 -1
  55. package/static/static/js/async/702.60261735.js.map +0 -1
  56. package/static/static/js/async/920.7d9a9aa8.js +0 -2
  57. package/static/static/js/async/920.7d9a9aa8.js.map +0 -1
  58. package/static/static/js/async/983.8b91303f.js +0 -1
  59. package/static/static/js/index.2caaacaf.js +0 -10
  60. package/static/static/js/index.2caaacaf.js.map +0 -1
  61. package/static/static/js/lib-react.f566a9ed.js +0 -3
  62. package/static/static/js/lib-react.f566a9ed.js.map +0 -1
  63. /package/static/static/js/{index.2caaacaf.js.LICENSE.txt → index.0930f837.js.LICENSE.txt} +0 -0
  64. /package/static/static/js/{lib-react.f566a9ed.js.LICENSE.txt → lib-react.7b1abe58.js.LICENSE.txt} +0 -0
  65. /package/static/static/wasm/{9e906fbf55e08f98.module.wasm → 9e906fbf.module.wasm} +0 -0
package/dist/lib/index.js CHANGED
@@ -34,11 +34,11 @@ var __webpack_exports__ = {};
34
34
  __webpack_require__.r(__webpack_exports__);
35
35
  __webpack_require__.d(__webpack_exports__, {
36
36
  overrideAIConfig: ()=>env_namespaceObject.overrideAIConfig,
37
+ IOSAgent: ()=>IOSAgent,
38
+ checkIOSEnvironment: ()=>checkIOSEnvironment,
37
39
  IOSDevice: ()=>IOSDevice,
38
40
  IOSWebDriverClient: ()=>IOSWebDriverClient,
39
- agentFromWebDriverAgent: ()=>agentFromWebDriverAgent,
40
- IOSAgent: ()=>IOSAgent,
41
- checkIOSEnvironment: ()=>checkIOSEnvironment
41
+ agentFromWebDriverAgent: ()=>agentFromWebDriverAgent
42
42
  });
43
43
  const external_node_assert_namespaceObject = require("node:assert");
44
44
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
@@ -192,9 +192,8 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
192
192
  this.ensureSession();
193
193
  debugIOS('Getting active element');
194
194
  try {
195
- var _response_value, _response_value1;
196
195
  const response = await this.makeRequest('GET', `/session/${this.sessionId}/element/active`);
197
- const elementId = (null == (_response_value = response.value) ? void 0 : _response_value.ELEMENT) || (null == (_response_value1 = response.value) ? void 0 : _response_value1['element-6066-11e4-a52e-4f735466cecf']) || response.ELEMENT || response['element-6066-11e4-a52e-4f735466cecf'];
196
+ const elementId = response.value?.ELEMENT || response.value?.['element-6066-11e4-a52e-4f735466cecf'] || response.ELEMENT || response['element-6066-11e4-a52e-4f735466cecf'];
198
197
  if (elementId) {
199
198
  debugIOS(`Got active element ID: ${elementId}`);
200
199
  return elementId;
@@ -271,8 +270,17 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
271
270
  });
272
271
  debugIOS(`Tapped at coordinates (${x}, ${y})`);
273
272
  } catch (error) {
274
- debugIOS(`Failed to tap at (${x}, ${y}): ${error}`);
275
- throw new Error(`Failed to tap at coordinates: ${error}`);
273
+ debugIOS(`New tap endpoint failed, trying legacy endpoint: ${error}`);
274
+ try {
275
+ await this.makeRequest('POST', `/session/${this.sessionId}/wda/tap/0`, {
276
+ x,
277
+ y
278
+ });
279
+ debugIOS(`Tapped at coordinates (${x}, ${y}) using legacy endpoint`);
280
+ } catch (fallbackError) {
281
+ debugIOS(`Failed to tap at (${x}, ${y}): ${fallbackError}`);
282
+ throw new Error(`Failed to tap at coordinates: ${fallbackError}`);
283
+ }
276
284
  }
277
285
  }
278
286
  async swipe(fromX, fromY, toX, toY, duration = 500) {
@@ -345,13 +353,34 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
345
353
  debugIOS(`Triple tapped at coordinates (${x}, ${y})`);
346
354
  }
347
355
  async getScreenScale() {
348
- var _screenResponse_value;
349
- const screenResponse = await this.makeRequest('GET', '/wda/screen');
350
- if (null == screenResponse ? void 0 : null == (_screenResponse_value = screenResponse.value) ? void 0 : _screenResponse_value.scale) {
351
- debugIOS(`Got screen scale from WDA screen endpoint: ${screenResponse.value.scale}`);
352
- return screenResponse.value.scale;
356
+ this.ensureSession();
357
+ try {
358
+ const screenResponse = await this.makeRequest('GET', `/session/${this.sessionId}/wda/screen`);
359
+ if (screenResponse?.value?.scale) {
360
+ debugIOS(`Got screen scale from WDA screen endpoint: ${screenResponse.value.scale}`);
361
+ return screenResponse.value.scale;
362
+ }
363
+ } catch (error) {
364
+ debugIOS(`Failed to get screen scale from /wda/screen: ${error}`);
365
+ }
366
+ try {
367
+ debugIOS('Calculating screen scale from screenshot and window size');
368
+ const [screenshotBase64, windowSize] = await Promise.all([
369
+ this.takeScreenshot(),
370
+ this.getWindowSize()
371
+ ]);
372
+ const { jimpFromBase64 } = await import("@midscene/shared/img");
373
+ const screenshotImg = await jimpFromBase64(screenshotBase64);
374
+ const screenshotWidth = screenshotImg.bitmap.width;
375
+ const screenshotHeight = screenshotImg.bitmap.height;
376
+ const scale = Math.max(screenshotWidth, screenshotHeight) / Math.max(windowSize.width, windowSize.height);
377
+ const roundedScale = Math.round(scale);
378
+ debugIOS(`Calculated screen scale: ${roundedScale} (screenshot: ${screenshotWidth}x${screenshotHeight}, window: ${windowSize.width}x${windowSize.height})`);
379
+ return roundedScale;
380
+ } catch (error) {
381
+ debugIOS(`Failed to calculate screen scale: ${error}`);
353
382
  }
354
- debugIOS('No screen scale found in WDA screen response');
383
+ debugIOS('No screen scale found');
355
384
  return null;
356
385
  }
357
386
  async createSession(capabilities) {
@@ -378,6 +407,10 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
378
407
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
379
408
  }
380
409
  }
410
+ async executeRequest(method, endpoint, data) {
411
+ this.ensureSession();
412
+ return this.makeRequest(method, endpoint, data);
413
+ }
381
414
  }
382
415
  function _define_property(obj, key, value) {
383
416
  if (key in obj) Object.defineProperty(obj, key, {
@@ -390,6 +423,12 @@ function _define_property(obj, key, value) {
390
423
  return obj;
391
424
  }
392
425
  const debugDevice = (0, logger_namespaceObject.getDebug)('ios:device');
426
+ const WDA_HTTP_METHODS = [
427
+ 'GET',
428
+ 'POST',
429
+ 'DELETE',
430
+ 'PUT'
431
+ ];
393
432
  class IOSDevice {
394
433
  actionSpace() {
395
434
  const defaultActions = [
@@ -409,7 +448,7 @@ class IOSDevice {
409
448
  interfaceAlias: 'aiInput',
410
449
  paramSchema: core_namespaceObject.z.object({
411
450
  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.'),
412
- autoDismissKeyboard: core_namespaceObject.z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
451
+ autoDismissKeyboard: core_namespaceObject.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.'),
413
452
  mode: core_namespaceObject.z["enum"]([
414
453
  'replace',
415
454
  'clear',
@@ -418,14 +457,13 @@ class IOSDevice {
418
457
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
419
458
  }),
420
459
  call: async (param)=>{
421
- var _this_options;
422
460
  const element = param.locate;
423
461
  if (element) {
424
462
  if ('append' !== param.mode) await this.clearInput(element);
425
463
  }
426
464
  if ('clear' === param.mode) return;
427
465
  if (!param || !param.value) return;
428
- const autoDismissKeyboard = param.autoDismissKeyboard ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard);
466
+ const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
429
467
  await this.typeText(param.value, {
430
468
  autoDismissKeyboard
431
469
  });
@@ -437,18 +475,18 @@ class IOSDevice {
437
475
  left: element.center[0],
438
476
  top: element.center[1]
439
477
  } : void 0;
440
- const scrollToEventName = null == param ? void 0 : param.scrollType;
441
- if ('untilTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
442
- else if ('untilBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
443
- else if ('untilRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
444
- else if ('untilLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
445
- else if ('once' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
478
+ const scrollToEventName = param?.scrollType;
479
+ if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
480
+ else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
481
+ else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
482
+ else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
483
+ else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
446
484
  else {
447
- if ((null == param ? void 0 : param.direction) !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
485
+ if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
448
486
  else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
449
487
  else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
450
488
  else throw new Error(`Unknown scroll direction: ${param.direction}`);
451
- else await this.scrollDown((null == param ? void 0 : param.distance) || void 0, startingPoint);
489
+ else await this.scrollDown(param?.distance || void 0, startingPoint);
452
490
  await (0, utils_namespaceObject.sleep)(500);
453
491
  }
454
492
  }),
@@ -462,22 +500,6 @@ class IOSDevice {
462
500
  (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
463
501
  await this.pressKey(param.keyName);
464
502
  }),
465
- (0, device_namespaceObject.defineAction)({
466
- name: 'IOSHomeButton',
467
- description: 'Trigger the system "home" operation on iOS devices',
468
- paramSchema: core_namespaceObject.z.object({}),
469
- call: async ()=>{
470
- await this.home();
471
- }
472
- }),
473
- (0, device_namespaceObject.defineAction)({
474
- name: 'IOSAppSwitcher',
475
- description: 'Trigger the system "app switcher" operation on iOS devices',
476
- paramSchema: core_namespaceObject.z.object({}),
477
- call: async ()=>{
478
- await this.appSwitcher();
479
- }
480
- }),
481
503
  (0, device_namespaceObject.defineAction)({
482
504
  name: 'IOSLongPress',
483
505
  description: 'Trigger a long press on the screen at specified coordinates on iOS devices',
@@ -489,7 +511,7 @@ class IOSDevice {
489
511
  const element = param.locate;
490
512
  external_node_assert_default()(element, 'IOSLongPress requires an element to be located');
491
513
  const [x, y] = element.center;
492
- await this.longPress(x, y, null == param ? void 0 : param.duration);
514
+ await this.longPress(x, y, param?.duration);
493
515
  }
494
516
  }),
495
517
  (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
@@ -498,9 +520,11 @@ class IOSDevice {
498
520
  await this.clearInput(element);
499
521
  })
500
522
  ];
523
+ const platformSpecificActions = Object.values(createPlatformActions(this));
501
524
  const customActions = this.customActions || [];
502
525
  return [
503
526
  ...defaultActions,
527
+ ...platformSpecificActions,
504
528
  ...customActions
505
529
  ];
506
530
  }
@@ -517,7 +541,7 @@ class IOSDevice {
517
541
  await this.wdaManager.start();
518
542
  await this.wdaBackend.createSession();
519
543
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
520
- if (null == deviceInfo ? void 0 : deviceInfo.udid) {
544
+ if (deviceInfo?.udid) {
521
545
  this.deviceId = deviceInfo.udid;
522
546
  debugDevice(`Updated device ID to real UDID: ${this.deviceId}`);
523
547
  }
@@ -625,9 +649,8 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
625
649
  await this.wdaBackend.swipe(Math.round(fromX), Math.round(fromY), Math.round(toX), Math.round(toY), duration);
626
650
  }
627
651
  async typeText(text, options) {
628
- var _this_options;
629
652
  if (!text) return;
630
- const shouldAutoDismissKeyboard = (null == options ? void 0 : options.autoDismissKeyboard) ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard) ?? true;
653
+ const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
631
654
  debugDevice(`Typing text: "${text}"`);
632
655
  try {
633
656
  await (0, utils_namespaceObject.sleep)(200);
@@ -829,20 +852,30 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
829
852
  }
830
853
  async hideKeyboard(keyNames) {
831
854
  try {
832
- if (keyNames && keyNames.length > 0) {
833
- debugDevice(`Using keyNames to dismiss keyboard: ${keyNames.join(', ')}`);
834
- await this.wdaBackend.dismissKeyboard(keyNames);
835
- debugDevice('Dismissed keyboard using provided keyNames');
836
- await (0, utils_namespaceObject.sleep)(300);
855
+ const dismissKeys = keyNames && keyNames.length > 0 ? keyNames : [
856
+ 'return',
857
+ 'done',
858
+ 'go',
859
+ 'search',
860
+ 'next',
861
+ 'send'
862
+ ];
863
+ debugDevice(`Attempting to dismiss keyboard using WDA API with keys: ${dismissKeys.join(', ')}`);
864
+ try {
865
+ await this.wdaBackend.dismissKeyboard(dismissKeys);
866
+ debugDevice('Successfully dismissed keyboard using WDA API');
867
+ await (0, utils_namespaceObject.sleep)(500);
837
868
  return true;
869
+ } catch (wdaError) {
870
+ debugDevice(`WDA dismissKeyboard failed, falling back to swipe gesture: ${wdaError}`);
838
871
  }
839
872
  const windowSize = await this.wdaBackend.getWindowSize();
840
873
  const centerX = Math.round(windowSize.width / 2);
841
- const startY = Math.round(0.33 * windowSize.height);
842
- const endY = Math.round(0.33 * windowSize.height + 10);
843
- await this.swipe(centerX, startY, centerX, endY, 50);
844
- debugDevice('Dismissed keyboard with swipe down gesture at screen one-third position');
845
- await (0, utils_namespaceObject.sleep)(300);
874
+ const startY = Math.round(0.9 * windowSize.height);
875
+ const endY = Math.round(0.5 * windowSize.height);
876
+ await this.swipe(centerX, startY, centerX, endY, 300);
877
+ debugDevice('Dismissed keyboard with swipe up gesture from bottom of screen');
878
+ await (0, utils_namespaceObject.sleep)(500);
846
879
  return true;
847
880
  } catch (error) {
848
881
  debugDevice(`Failed to hide keyboard: ${error}`);
@@ -871,6 +904,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
871
904
  async openUrlViaSafari(url) {
872
905
  try {
873
906
  debugDevice(`Opening URL via Safari: ${url}`);
907
+ await this.wdaBackend.terminateApp('com.apple.mobilesafari');
874
908
  await this.wdaBackend.launchApp('com.apple.mobilesafari');
875
909
  await (0, utils_namespaceObject.sleep)(2000);
876
910
  await this.typeText(url);
@@ -888,6 +922,9 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
888
922
  throw new Error(`Failed to open URL via Safari: ${error}`);
889
923
  }
890
924
  }
925
+ async runWdaRequest(method, endpoint, data) {
926
+ return await this.wdaBackend.executeRequest(method, endpoint, data);
927
+ }
891
928
  async destroy() {
892
929
  if (this.destroyed) return;
893
930
  try {
@@ -913,9 +950,9 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
913
950
  _define_property(this, "options", void 0);
914
951
  this.deviceId = 'pending-connection';
915
952
  this.options = options;
916
- this.customActions = null == options ? void 0 : options.customActions;
917
- const wdaPort = (null == options ? void 0 : options.wdaPort) || constants_namespaceObject.DEFAULT_WDA_PORT;
918
- const wdaHost = (null == options ? void 0 : options.wdaHost) || 'localhost';
953
+ this.customActions = options?.customActions;
954
+ const wdaPort = options?.wdaPort || constants_namespaceObject.DEFAULT_WDA_PORT;
955
+ const wdaHost = options?.wdaHost || 'localhost';
919
956
  this.wdaBackend = new IOSWebDriverClient({
920
957
  port: wdaPort,
921
958
  host: wdaHost
@@ -923,6 +960,44 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
923
960
  this.wdaManager = webdriver_namespaceObject.WDAManager.getInstance(wdaPort, wdaHost);
924
961
  }
925
962
  }
963
+ const runWdaRequestParamSchema = core_namespaceObject.z.object({
964
+ method: core_namespaceObject.z["enum"](WDA_HTTP_METHODS).describe('HTTP method (GET, POST, DELETE, PUT)'),
965
+ endpoint: core_namespaceObject.z.string().describe('WebDriver API endpoint'),
966
+ data: core_namespaceObject.z.object({}).passthrough().optional().describe('Optional request body data as JSON object')
967
+ });
968
+ const launchParamSchema = core_namespaceObject.z.string().describe('App bundle ID or URL to launch');
969
+ const createPlatformActions = (device)=>({
970
+ RunWdaRequest: (0, device_namespaceObject.defineAction)({
971
+ name: 'RunWdaRequest',
972
+ description: 'Execute WebDriverAgent API request directly on iOS device',
973
+ interfaceAlias: 'runWdaRequest',
974
+ paramSchema: runWdaRequestParamSchema,
975
+ call: async (param)=>await device.runWdaRequest(param.method, param.endpoint, param.data)
976
+ }),
977
+ Launch: (0, device_namespaceObject.defineAction)({
978
+ name: 'Launch',
979
+ description: 'Launch an iOS app or URL',
980
+ interfaceAlias: 'launch',
981
+ paramSchema: launchParamSchema,
982
+ call: async (param)=>{
983
+ await device.launch(param);
984
+ }
985
+ }),
986
+ IOSHomeButton: (0, device_namespaceObject.defineAction)({
987
+ name: 'IOSHomeButton',
988
+ description: 'Trigger the system "home" operation on iOS devices',
989
+ call: async ()=>{
990
+ await device.home();
991
+ }
992
+ }),
993
+ IOSAppSwitcher: (0, device_namespaceObject.defineAction)({
994
+ name: 'IOSAppSwitcher',
995
+ description: 'Trigger the system "app switcher" operation on iOS devices',
996
+ call: async ()=>{
997
+ await device.appSwitcher();
998
+ }
999
+ })
1000
+ });
926
1001
  const agent_namespaceObject = require("@midscene/core/agent");
927
1002
  const external_node_child_process_namespaceObject = require("node:child_process");
928
1003
  const external_node_os_namespaceObject = require("node:os");
@@ -973,24 +1048,35 @@ async function checkIOSEnvironment() {
973
1048
  };
974
1049
  }
975
1050
  }
1051
+ function agent_define_property(obj, key, value) {
1052
+ if (key in obj) Object.defineProperty(obj, key, {
1053
+ value: value,
1054
+ enumerable: true,
1055
+ configurable: true,
1056
+ writable: true
1057
+ });
1058
+ else obj[key] = value;
1059
+ return obj;
1060
+ }
976
1061
  const debugAgent = (0, logger_namespaceObject.getDebug)('ios:agent');
977
1062
  class IOSAgent extends agent_namespaceObject.Agent {
978
- async launch(uri) {
979
- const device = this.page;
980
- await device.launch(uri);
1063
+ createActionWrapper(name) {
1064
+ const action = this.wrapActionInActionSpace(name);
1065
+ return (...args)=>action(args[0]);
1066
+ }
1067
+ constructor(device, opts){
1068
+ super(device, opts), agent_define_property(this, "launch", void 0), agent_define_property(this, "runWdaRequest", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "appSwitcher", void 0);
1069
+ this.launch = this.createActionWrapper('Launch');
1070
+ this.runWdaRequest = this.createActionWrapper('RunWdaRequest');
1071
+ this.home = this.createActionWrapper('IOSHomeButton');
1072
+ this.appSwitcher = this.createActionWrapper('IOSAppSwitcher');
981
1073
  }
982
1074
  }
983
1075
  async function agentFromWebDriverAgent(opts) {
984
1076
  debugAgent('Creating iOS agent with WebDriverAgent auto-detection');
985
1077
  const envCheck = await checkIOSEnvironment();
986
1078
  if (!envCheck.available) throw new Error(`iOS environment not available: ${envCheck.error}`);
987
- const device = new IOSDevice({
988
- autoDismissKeyboard: null == opts ? void 0 : opts.autoDismissKeyboard,
989
- customActions: null == opts ? void 0 : opts.customActions,
990
- wdaPort: null == opts ? void 0 : opts.wdaPort,
991
- wdaHost: null == opts ? void 0 : opts.wdaHost,
992
- useWDA: null == opts ? void 0 : opts.useWDA
993
- });
1079
+ const device = new IOSDevice(opts || {});
994
1080
  await device.connect();
995
1081
  return new IOSAgent(device, opts);
996
1082
  }
@@ -1001,14 +1087,14 @@ exports.IOSWebDriverClient = __webpack_exports__.IOSWebDriverClient;
1001
1087
  exports.agentFromWebDriverAgent = __webpack_exports__.agentFromWebDriverAgent;
1002
1088
  exports.checkIOSEnvironment = __webpack_exports__.checkIOSEnvironment;
1003
1089
  exports.overrideAIConfig = __webpack_exports__.overrideAIConfig;
1004
- for(var __webpack_i__ in __webpack_exports__)if (-1 === [
1090
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
1005
1091
  "IOSAgent",
1006
1092
  "IOSDevice",
1007
1093
  "IOSWebDriverClient",
1008
1094
  "agentFromWebDriverAgent",
1009
1095
  "checkIOSEnvironment",
1010
1096
  "overrideAIConfig"
1011
- ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
1097
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
1012
1098
  Object.defineProperty(exports, '__esModule', {
1013
1099
  value: true
1014
1100
  });
@@ -1,13 +1,20 @@
1
1
  import { AbstractInterface } from '@midscene/core/device';
2
+ import type { ActionParam } from '@midscene/core';
3
+ import type { ActionReturn } from '@midscene/core';
2
4
  import { Agent } from '@midscene/core/agent';
3
5
  import { AgentOpt } from '@midscene/core/agent';
4
6
  import { DeviceAction } from '@midscene/core';
5
7
  import type { ElementInfo } from '@midscene/shared/extractor';
6
8
  import { InterfaceType } from '@midscene/core';
9
+ import { IOSDeviceInputOpt } from '@midscene/core/device';
10
+ import { IOSDeviceOpt } from '@midscene/core/device';
7
11
  import { overrideAIConfig } from '@midscene/shared/env';
8
12
  import { Point } from '@midscene/core';
9
13
  import { Size } from '@midscene/core';
10
14
  import { WebDriverClient } from '@midscene/webdriver';
15
+ import { z } from '@midscene/core';
16
+
17
+ declare type ActionArgs<T extends DeviceAction> = [ActionParam<T>] extends [undefined] ? [] : [ActionParam<T>];
11
18
 
12
19
  export declare function agentFromWebDriverAgent(opts?: IOSAgentOpt & IOSDeviceOpt): Promise<IOSAgent>;
13
20
 
@@ -16,11 +23,38 @@ export declare function checkIOSEnvironment(): Promise<{
16
23
  error?: string;
17
24
  }>;
18
25
 
26
+ declare type DeviceActionIOSAppSwitcher = DeviceAction<undefined, void>;
27
+
28
+ declare type DeviceActionIOSHomeButton = DeviceAction<undefined, void>;
29
+
30
+ declare type DeviceActionLaunch = DeviceAction<LaunchParam, void>;
31
+
32
+ declare type DeviceActionRunWdaRequest = DeviceAction<RunWdaRequestParam, RunWdaRequestReturn>;
33
+
19
34
  export declare class IOSAgent extends Agent<IOSDevice> {
20
- launch(uri: string): Promise<void>;
35
+ /**
36
+ * Launch an iOS app or URL
37
+ * Type-safe wrapper around the Launch action from actionSpace
38
+ */
39
+ launch: WrappedAction<DeviceActionLaunch>;
40
+ /**
41
+ * Execute WebDriverAgent API request directly
42
+ * Type-safe wrapper around the RunWdaRequest action from actionSpace
43
+ */
44
+ runWdaRequest: WrappedAction<DeviceActionRunWdaRequest>;
45
+ /**
46
+ * Trigger the system home operation on iOS devices
47
+ */
48
+ home: WrappedAction<DeviceActionIOSHomeButton>;
49
+ /**
50
+ * Trigger the system app switcher operation on iOS devices
51
+ */
52
+ appSwitcher: WrappedAction<DeviceActionIOSAppSwitcher>;
53
+ constructor(device: IOSDevice, opts?: IOSAgentOpt);
54
+ private createActionWrapper;
21
55
  }
22
56
 
23
- declare type IOSAgentOpt = AgentOpt;
57
+ export declare type IOSAgentOpt = AgentOpt;
24
58
 
25
59
  export declare class IOSDevice implements AbstractInterface {
26
60
  private deviceId;
@@ -91,21 +125,18 @@ export declare class IOSDevice implements AbstractInterface {
91
125
  * @param url The URL to open
92
126
  */
93
127
  openUrlViaSafari(url: string): Promise<void>;
128
+ /**
129
+ * Execute a WebDriverAgent API request directly
130
+ * This is the iOS equivalent of Android's runAdbShell
131
+ * @param method HTTP method (GET, POST, DELETE, PUT)
132
+ * @param endpoint WebDriver API endpoint
133
+ * @param data Optional request body data
134
+ * @returns Response from the WebDriver API
135
+ */
136
+ runWdaRequest<TResult = any>(method: WDAHttpMethod, endpoint: string, data?: any): Promise<TResult>;
94
137
  destroy(): Promise<void>;
95
138
  }
96
139
 
97
- declare type IOSDeviceInputOpt = {
98
- autoDismissKeyboard?: boolean;
99
- };
100
-
101
- declare type IOSDeviceOpt = {
102
- deviceId?: string;
103
- customActions?: DeviceAction<any>[];
104
- wdaPort?: number;
105
- wdaHost?: string;
106
- useWDA?: boolean;
107
- } & IOSDeviceInputOpt;
108
-
109
140
  export declare class IOSWebDriverClient extends WebDriverClient {
110
141
  launchApp(bundleId: string): Promise<void>;
111
142
  activateApp(bundleId: string): Promise<void>;
@@ -140,8 +171,51 @@ export declare class IOSWebDriverClient extends WebDriverClient {
140
171
  getScreenScale(): Promise<number | null>;
141
172
  createSession(capabilities?: any): Promise<any>;
142
173
  private setupIOSSession;
174
+ /**
175
+ * Execute a WebDriverAgent API request directly
176
+ * This is the iOS equivalent of Android's runAdbShell
177
+ * @param method HTTP method (GET, POST, DELETE, etc.)
178
+ * @param endpoint WebDriver API endpoint
179
+ * @param data Optional request body data
180
+ * @returns Response from the WebDriver API
181
+ */
182
+ executeRequest<TResult = any>(method: string, endpoint: string, data?: any): Promise<TResult>;
143
183
  }
144
184
 
185
+ declare type LaunchParam = z.infer<typeof launchParamSchema>;
186
+
187
+ declare const launchParamSchema: z.ZodString;
188
+
145
189
  export { overrideAIConfig }
146
190
 
191
+ declare type RunWdaRequestParam = z.infer<typeof runWdaRequestParamSchema>;
192
+
193
+ declare const runWdaRequestParamSchema: z.ZodObject<{
194
+ method: z.ZodEnum<["GET", "POST", "DELETE", "PUT"]>;
195
+ endpoint: z.ZodString;
196
+ data: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
197
+ }, "strip", z.ZodTypeAny, {
198
+ method: "POST" | "GET" | "DELETE" | "PUT";
199
+ endpoint: string;
200
+ data?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
201
+ }, {
202
+ method: "POST" | "GET" | "DELETE" | "PUT";
203
+ endpoint: string;
204
+ data?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
205
+ }>;
206
+
207
+ declare type RunWdaRequestReturn = Awaited<ReturnType<IOSDevice['runWdaRequest']>>;
208
+
209
+ /**
210
+ * HTTP methods supported by WebDriverAgent API
211
+ */
212
+ declare const WDA_HTTP_METHODS: readonly ["GET", "POST", "DELETE", "PUT"];
213
+
214
+ declare type WDAHttpMethod = (typeof WDA_HTTP_METHODS)[number];
215
+
216
+ /**
217
+ * Helper type to convert DeviceAction to wrapped method signature
218
+ */
219
+ declare type WrappedAction<T extends DeviceAction> = (...args: ActionArgs<T>) => Promise<ActionReturn<T>>;
220
+
147
221
  export { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/ios",
3
- "version": "0.30.10",
3
+ "version": "1.0.0",
4
4
  "description": "iOS automation library for Midscene",
5
5
  "keywords": [
6
6
  "iOS UI automation",
@@ -38,24 +38,25 @@
38
38
  "dependencies": {
39
39
  "@inquirer/prompts": "^7.8.6",
40
40
  "open": "10.1.0",
41
- "@midscene/shared": "0.30.10",
42
- "@midscene/core": "0.30.10",
43
- "@midscene/webdriver": "0.30.10"
41
+ "@midscene/core": "1.0.0",
42
+ "@midscene/shared": "1.0.0",
43
+ "@midscene/webdriver": "1.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@rslib/core": "^0.11.2",
46
+ "@rslib/core": "^0.18.3",
47
47
  "@types/node": "^18.0.0",
48
48
  "dotenv": "^16.4.5",
49
49
  "typescript": "^5.8.3",
50
50
  "tsx": "^4.19.2",
51
51
  "vitest": "3.0.5",
52
- "@midscene/playground": "0.30.10"
52
+ "zod": "3.24.3",
53
+ "@midscene/playground": "1.0.0"
53
54
  },
54
55
  "license": "MIT",
55
56
  "scripts": {
56
57
  "dev": "npm run build:watch",
57
58
  "build": "rslib build",
58
- "build:watch": "rslib build --watch",
59
+ "build:watch": "rslib build --watch --no-clean",
59
60
  "playground": "DEBUG=midscene:* tsx demo/playground.ts",
60
61
  "test": "vitest --run",
61
62
  "test:u": "vitest --run -u",
package/static/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html><head><link rel="icon" href="/favicon.ico"><title>Midscene Playground</title><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script defer src="/static/js/lib-react.f566a9ed.js"></script><script defer src="/static/js/931.dc961e99.js"></script><script defer src="/static/js/index.2caaacaf.js"></script><link href="/static/css/index.44466eb4.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
1
+ <!doctype html><html><head><link rel="icon" href="/favicon.ico"><title>Midscene Playground</title><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script defer src="/static/js/lib-react.7b1abe58.js"></script><script defer src="/static/js/79.25af61dc.js"></script><script defer src="/static/js/index.0930f837.js"></script><link href="/static/css/index.d32b7df9.css" rel="stylesheet"></head><body><div id="root"></div></body></html>