@midscene/ios 0.30.9 → 0.30.11-beta-20251218071621.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 +467 -339
  3. package/dist/es/index.mjs +194 -76
  4. package/dist/lib/bin.js +437 -295
  5. package/dist/lib/index.js +199 -81
  6. package/dist/types/index.d.ts +103 -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.0bbfffbf.js +10 -0
  33. package/static/static/js/index.0bbfffbf.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.c7ba8adb.js +0 -10
  60. package/static/static/js/index.c7ba8adb.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.c7ba8adb.js.LICENSE.txt → index.0bbfffbf.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);
@@ -188,6 +188,48 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
188
188
  debugIOS(`Warning: Key "${key}" is not supported on iOS platform`);
189
189
  throw new Error(`Key "${key}" is not supported on iOS platform`);
190
190
  }
191
+ async getActiveElement() {
192
+ this.ensureSession();
193
+ debugIOS('Getting active element');
194
+ try {
195
+ const response = await this.makeRequest('GET', `/session/${this.sessionId}/element/active`);
196
+ const elementId = response.value?.ELEMENT || response.value?.['element-6066-11e4-a52e-4f735466cecf'] || response.ELEMENT || response['element-6066-11e4-a52e-4f735466cecf'];
197
+ if (elementId) {
198
+ debugIOS(`Got active element ID: ${elementId}`);
199
+ return elementId;
200
+ }
201
+ debugIOS('No active element found');
202
+ return null;
203
+ } catch (error) {
204
+ debugIOS(`Failed to get active element: ${error}`);
205
+ return null;
206
+ }
207
+ }
208
+ async clearElement(elementId) {
209
+ this.ensureSession();
210
+ debugIOS(`Clearing element: ${elementId}`);
211
+ try {
212
+ await this.makeRequest('POST', `/session/${this.sessionId}/element/${elementId}/clear`);
213
+ debugIOS('Element cleared successfully');
214
+ } catch (error) {
215
+ debugIOS(`Failed to clear element: ${error}`);
216
+ throw new Error(`Failed to clear element: ${error}`);
217
+ }
218
+ }
219
+ async clearActiveElement() {
220
+ try {
221
+ const elementId = await this.getActiveElement();
222
+ if (!elementId) {
223
+ debugIOS('No active element to clear');
224
+ return false;
225
+ }
226
+ await this.clearElement(elementId);
227
+ return true;
228
+ } catch (error) {
229
+ debugIOS(`Failed to clear active element: ${error}`);
230
+ return false;
231
+ }
232
+ }
191
233
  normalizeKeyName(key) {
192
234
  return key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
193
235
  }
@@ -228,8 +270,17 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
228
270
  });
229
271
  debugIOS(`Tapped at coordinates (${x}, ${y})`);
230
272
  } catch (error) {
231
- debugIOS(`Failed to tap at (${x}, ${y}): ${error}`);
232
- 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
+ }
233
284
  }
234
285
  }
235
286
  async swipe(fromX, fromY, toX, toY, duration = 500) {
@@ -302,13 +353,34 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
302
353
  debugIOS(`Triple tapped at coordinates (${x}, ${y})`);
303
354
  }
304
355
  async getScreenScale() {
305
- var _screenResponse_value;
306
- const screenResponse = await this.makeRequest('GET', '/wda/screen');
307
- if (null == screenResponse ? void 0 : null == (_screenResponse_value = screenResponse.value) ? void 0 : _screenResponse_value.scale) {
308
- debugIOS(`Got screen scale from WDA screen endpoint: ${screenResponse.value.scale}`);
309
- 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}`);
310
382
  }
311
- debugIOS('No screen scale found in WDA screen response');
383
+ debugIOS('No screen scale found');
312
384
  return null;
313
385
  }
314
386
  async createSession(capabilities) {
@@ -335,6 +407,10 @@ class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
335
407
  debugIOS(`Failed to apply iOS session configuration: ${error}`);
336
408
  }
337
409
  }
410
+ async executeRequest(method, endpoint, data) {
411
+ this.ensureSession();
412
+ return this.makeRequest(method, endpoint, data);
413
+ }
338
414
  }
339
415
  function _define_property(obj, key, value) {
340
416
  if (key in obj) Object.defineProperty(obj, key, {
@@ -347,7 +423,12 @@ function _define_property(obj, key, value) {
347
423
  return obj;
348
424
  }
349
425
  const debugDevice = (0, logger_namespaceObject.getDebug)('ios:device');
350
- const BackspaceChar = '\u0008';
426
+ const WDA_HTTP_METHODS = [
427
+ 'GET',
428
+ 'POST',
429
+ 'DELETE',
430
+ 'PUT'
431
+ ];
351
432
  class IOSDevice {
352
433
  actionSpace() {
353
434
  const defaultActions = [
@@ -367,7 +448,7 @@ class IOSDevice {
367
448
  interfaceAlias: 'aiInput',
368
449
  paramSchema: core_namespaceObject.z.object({
369
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.'),
370
- 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.'),
371
452
  mode: core_namespaceObject.z["enum"]([
372
453
  'replace',
373
454
  'clear',
@@ -376,14 +457,13 @@ class IOSDevice {
376
457
  locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
377
458
  }),
378
459
  call: async (param)=>{
379
- var _this_options;
380
460
  const element = param.locate;
381
461
  if (element) {
382
462
  if ('append' !== param.mode) await this.clearInput(element);
383
463
  }
384
464
  if ('clear' === param.mode) return;
385
465
  if (!param || !param.value) return;
386
- const autoDismissKeyboard = param.autoDismissKeyboard ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard);
466
+ const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
387
467
  await this.typeText(param.value, {
388
468
  autoDismissKeyboard
389
469
  });
@@ -395,18 +475,18 @@ class IOSDevice {
395
475
  left: element.center[0],
396
476
  top: element.center[1]
397
477
  } : void 0;
398
- const scrollToEventName = null == param ? void 0 : param.scrollType;
399
- if ('untilTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
400
- else if ('untilBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
401
- else if ('untilRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
402
- else if ('untilLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
403
- 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)}`);
404
484
  else {
405
- 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);
406
486
  else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
407
487
  else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
408
488
  else throw new Error(`Unknown scroll direction: ${param.direction}`);
409
- else await this.scrollDown((null == param ? void 0 : param.distance) || void 0, startingPoint);
489
+ else await this.scrollDown(param?.distance || void 0, startingPoint);
410
490
  await (0, utils_namespaceObject.sleep)(500);
411
491
  }
412
492
  }),
@@ -420,22 +500,6 @@ class IOSDevice {
420
500
  (0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
421
501
  await this.pressKey(param.keyName);
422
502
  }),
423
- (0, device_namespaceObject.defineAction)({
424
- name: 'IOSHomeButton',
425
- description: 'Trigger the system "home" operation on iOS devices',
426
- paramSchema: core_namespaceObject.z.object({}),
427
- call: async ()=>{
428
- await this.home();
429
- }
430
- }),
431
- (0, device_namespaceObject.defineAction)({
432
- name: 'IOSAppSwitcher',
433
- description: 'Trigger the system "app switcher" operation on iOS devices',
434
- paramSchema: core_namespaceObject.z.object({}),
435
- call: async ()=>{
436
- await this.appSwitcher();
437
- }
438
- }),
439
503
  (0, device_namespaceObject.defineAction)({
440
504
  name: 'IOSLongPress',
441
505
  description: 'Trigger a long press on the screen at specified coordinates on iOS devices',
@@ -447,7 +511,7 @@ class IOSDevice {
447
511
  const element = param.locate;
448
512
  external_node_assert_default()(element, 'IOSLongPress requires an element to be located');
449
513
  const [x, y] = element.center;
450
- await this.longPress(x, y, null == param ? void 0 : param.duration);
514
+ await this.longPress(x, y, param?.duration);
451
515
  }
452
516
  }),
453
517
  (0, device_namespaceObject.defineActionClearInput)(async (param)=>{
@@ -456,9 +520,11 @@ class IOSDevice {
456
520
  await this.clearInput(element);
457
521
  })
458
522
  ];
523
+ const platformSpecificActions = Object.values(createPlatformActions(this));
459
524
  const customActions = this.customActions || [];
460
525
  return [
461
526
  ...defaultActions,
527
+ ...platformSpecificActions,
462
528
  ...customActions
463
529
  ];
464
530
  }
@@ -475,7 +541,7 @@ class IOSDevice {
475
541
  await this.wdaManager.start();
476
542
  await this.wdaBackend.createSession();
477
543
  const deviceInfo = await this.wdaBackend.getDeviceInfo();
478
- if (null == deviceInfo ? void 0 : deviceInfo.udid) {
544
+ if (deviceInfo?.udid) {
479
545
  this.deviceId = deviceInfo.udid;
480
546
  debugDevice(`Updated device ID to real UDID: ${this.deviceId}`);
481
547
  }
@@ -556,19 +622,9 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
556
622
  if (!element) return;
557
623
  await this.tap(element.center[0], element.center[1]);
558
624
  await (0, utils_namespaceObject.sleep)(100);
559
- try {
560
- await this.tripleTap(element.center[0], element.center[1]);
561
- await (0, utils_namespaceObject.sleep)(200);
562
- await this.wdaBackend.typeText(BackspaceChar);
563
- } catch (error2) {
564
- debugDevice(`Method 1 failed, trying method 2: ${error2}`);
565
- try {
566
- const backspaces = Array(100).fill(BackspaceChar).join('');
567
- await this.wdaBackend.typeText(backspaces);
568
- } catch (error3) {
569
- debugDevice(`All clear methods failed: ${error3}`);
570
- }
571
- }
625
+ debugDevice('Attempting to clear input with WebDriver Clear API');
626
+ const cleared = await this.wdaBackend.clearActiveElement();
627
+ cleared ? debugDevice('Successfully cleared input with WebDriver Clear API') : debugDevice('WebDriver Clear API returned false (no active element or clear failed)');
572
628
  }
573
629
  async url() {
574
630
  return '';
@@ -593,9 +649,8 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
593
649
  await this.wdaBackend.swipe(Math.round(fromX), Math.round(fromY), Math.round(toX), Math.round(toY), duration);
594
650
  }
595
651
  async typeText(text, options) {
596
- var _this_options;
597
652
  if (!text) return;
598
- 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;
599
654
  debugDevice(`Typing text: "${text}"`);
600
655
  try {
601
656
  await (0, utils_namespaceObject.sleep)(200);
@@ -797,20 +852,30 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
797
852
  }
798
853
  async hideKeyboard(keyNames) {
799
854
  try {
800
- if (keyNames && keyNames.length > 0) {
801
- debugDevice(`Using keyNames to dismiss keyboard: ${keyNames.join(', ')}`);
802
- await this.wdaBackend.dismissKeyboard(keyNames);
803
- debugDevice('Dismissed keyboard using provided keyNames');
804
- 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);
805
868
  return true;
869
+ } catch (wdaError) {
870
+ debugDevice(`WDA dismissKeyboard failed, falling back to swipe gesture: ${wdaError}`);
806
871
  }
807
872
  const windowSize = await this.wdaBackend.getWindowSize();
808
873
  const centerX = Math.round(windowSize.width / 2);
809
- const startY = Math.round(0.33 * windowSize.height);
810
- const endY = Math.round(0.33 * windowSize.height + 10);
811
- await this.swipe(centerX, startY, centerX, endY, 50);
812
- debugDevice('Dismissed keyboard with swipe down gesture at screen one-third position');
813
- 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);
814
879
  return true;
815
880
  } catch (error) {
816
881
  debugDevice(`Failed to hide keyboard: ${error}`);
@@ -839,6 +904,7 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
839
904
  async openUrlViaSafari(url) {
840
905
  try {
841
906
  debugDevice(`Opening URL via Safari: ${url}`);
907
+ await this.wdaBackend.terminateApp('com.apple.mobilesafari');
842
908
  await this.wdaBackend.launchApp('com.apple.mobilesafari');
843
909
  await (0, utils_namespaceObject.sleep)(2000);
844
910
  await this.typeText(url);
@@ -856,6 +922,9 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
856
922
  throw new Error(`Failed to open URL via Safari: ${error}`);
857
923
  }
858
924
  }
925
+ async runWdaRequest(method, endpoint, data) {
926
+ return await this.wdaBackend.executeRequest(method, endpoint, data);
927
+ }
859
928
  async destroy() {
860
929
  if (this.destroyed) return;
861
930
  try {
@@ -881,9 +950,9 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
881
950
  _define_property(this, "options", void 0);
882
951
  this.deviceId = 'pending-connection';
883
952
  this.options = options;
884
- this.customActions = null == options ? void 0 : options.customActions;
885
- const wdaPort = (null == options ? void 0 : options.wdaPort) || constants_namespaceObject.DEFAULT_WDA_PORT;
886
- 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';
887
956
  this.wdaBackend = new IOSWebDriverClient({
888
957
  port: wdaPort,
889
958
  host: wdaHost
@@ -891,6 +960,44 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
891
960
  this.wdaManager = webdriver_namespaceObject.WDAManager.getInstance(wdaPort, wdaHost);
892
961
  }
893
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
+ });
894
1001
  const agent_namespaceObject = require("@midscene/core/agent");
895
1002
  const external_node_child_process_namespaceObject = require("node:child_process");
896
1003
  const external_node_os_namespaceObject = require("node:os");
@@ -941,24 +1048,35 @@ async function checkIOSEnvironment() {
941
1048
  };
942
1049
  }
943
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
+ }
944
1061
  const debugAgent = (0, logger_namespaceObject.getDebug)('ios:agent');
945
1062
  class IOSAgent extends agent_namespaceObject.Agent {
946
- async launch(uri) {
947
- const device = this.page;
948
- 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');
949
1073
  }
950
1074
  }
951
1075
  async function agentFromWebDriverAgent(opts) {
952
1076
  debugAgent('Creating iOS agent with WebDriverAgent auto-detection');
953
1077
  const envCheck = await checkIOSEnvironment();
954
1078
  if (!envCheck.available) throw new Error(`iOS environment not available: ${envCheck.error}`);
955
- const device = new IOSDevice({
956
- autoDismissKeyboard: null == opts ? void 0 : opts.autoDismissKeyboard,
957
- customActions: null == opts ? void 0 : opts.customActions,
958
- wdaPort: null == opts ? void 0 : opts.wdaPort,
959
- wdaHost: null == opts ? void 0 : opts.wdaHost,
960
- useWDA: null == opts ? void 0 : opts.useWDA
961
- });
1079
+ const device = new IOSDevice(opts || {});
962
1080
  await device.connect();
963
1081
  return new IOSAgent(device, opts);
964
1082
  }
@@ -969,14 +1087,14 @@ exports.IOSWebDriverClient = __webpack_exports__.IOSWebDriverClient;
969
1087
  exports.agentFromWebDriverAgent = __webpack_exports__.agentFromWebDriverAgent;
970
1088
  exports.checkIOSEnvironment = __webpack_exports__.checkIOSEnvironment;
971
1089
  exports.overrideAIConfig = __webpack_exports__.overrideAIConfig;
972
- for(var __webpack_i__ in __webpack_exports__)if (-1 === [
1090
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
973
1091
  "IOSAgent",
974
1092
  "IOSDevice",
975
1093
  "IOSWebDriverClient",
976
1094
  "agentFromWebDriverAgent",
977
1095
  "checkIOSEnvironment",
978
1096
  "overrideAIConfig"
979
- ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
1097
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
980
1098
  Object.defineProperty(exports, '__esModule', {
981
1099
  value: true
982
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>;
@@ -114,6 +145,21 @@ export declare class IOSWebDriverClient extends WebDriverClient {
114
145
  pressHomeButton(): Promise<void>;
115
146
  appSwitcher(): Promise<void>;
116
147
  pressKey(key: string): Promise<void>;
148
+ /**
149
+ * Get the currently focused element's WebDriver ID
150
+ * @returns WebDriver element ID or null if no element is focused
151
+ */
152
+ getActiveElement(): Promise<string | null>;
153
+ /**
154
+ * Clear an element using WebDriver's clear endpoint
155
+ * @param elementId WebDriver element ID
156
+ */
157
+ clearElement(elementId: string): Promise<void>;
158
+ /**
159
+ * Clear the currently focused input field using WebDriver Clear API
160
+ * @returns true if successful, false otherwise
161
+ */
162
+ clearActiveElement(): Promise<boolean>;
117
163
  private normalizeKeyName;
118
164
  dismissKeyboard(keyNames?: string[]): Promise<boolean>;
119
165
  typeText(text: string): Promise<void>;
@@ -125,8 +171,51 @@ export declare class IOSWebDriverClient extends WebDriverClient {
125
171
  getScreenScale(): Promise<number | null>;
126
172
  createSession(capabilities?: any): Promise<any>;
127
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>;
128
183
  }
129
184
 
185
+ declare type LaunchParam = z.infer<typeof launchParamSchema>;
186
+
187
+ declare const launchParamSchema: z.ZodString;
188
+
130
189
  export { overrideAIConfig }
131
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
+
132
221
  export { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/ios",
3
- "version": "0.30.9",
3
+ "version": "0.30.11-beta-20251218071621.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/core": "0.30.9",
42
- "@midscene/shared": "0.30.9",
43
- "@midscene/webdriver": "0.30.9"
41
+ "@midscene/shared": "0.30.11-beta-20251218071621.0",
42
+ "@midscene/webdriver": "0.30.11-beta-20251218071621.0",
43
+ "@midscene/core": "0.30.11-beta-20251218071621.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.9"
52
+ "zod": "3.24.3",
53
+ "@midscene/playground": "0.30.11-beta-20251218071621.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",