@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.
- package/README.md +11 -11
- package/dist/es/bin.mjs +467 -339
- package/dist/es/index.mjs +194 -76
- package/dist/lib/bin.js +437 -295
- package/dist/lib/index.js +199 -81
- package/dist/types/index.d.ts +103 -14
- package/package.json +8 -7
- package/static/index.html +1 -1
- package/static/static/css/index.d32b7df9.css +2 -0
- package/static/static/css/index.d32b7df9.css.map +1 -0
- package/static/static/js/79.25af61dc.js +611 -0
- package/static/static/js/{931.dc961e99.js.LICENSE.txt → 79.25af61dc.js.LICENSE.txt} +0 -4
- package/static/static/js/79.25af61dc.js.map +1 -0
- package/static/static/js/async/195.0366f6e8.js +3 -0
- package/static/static/js/async/195.0366f6e8.js.map +1 -0
- package/static/static/js/async/{702.60261735.js → 199.f31e52e7.js} +20 -20
- package/static/static/js/async/199.f31e52e7.js.map +1 -0
- package/static/static/js/async/221.591b048e.js +21 -0
- package/static/static/js/async/221.591b048e.js.map +1 -0
- package/static/static/js/async/271.15d46ff8.js +30 -0
- package/static/static/js/async/271.15d46ff8.js.map +1 -0
- package/static/static/js/async/35.2b64fb0f.js +1 -0
- package/static/static/js/async/{644.6bdc4065.js → 467.710fa05a.js} +1 -1
- package/static/static/js/async/652.b5a7c7b4.js +3 -0
- package/static/static/js/async/652.b5a7c7b4.js.map +1 -0
- package/static/static/js/async/856.be9fd814.js +158 -0
- package/static/static/js/async/{212.e243c338.js.map → 856.be9fd814.js.map} +1 -1
- package/static/static/js/async/860.b56301d9.js +2 -0
- package/static/static/js/async/860.b56301d9.js.map +1 -0
- package/static/static/js/async/990.82a78a53.js +26 -0
- package/static/static/js/async/990.82a78a53.js.map +1 -0
- package/static/static/js/index.0bbfffbf.js +10 -0
- package/static/static/js/index.0bbfffbf.js.map +1 -0
- package/static/static/js/lib-react.7b1abe58.js +3 -0
- package/static/static/js/lib-react.7b1abe58.js.map +1 -0
- package/static/static/css/index.44466eb4.css +0 -2
- package/static/static/css/index.44466eb4.css.map +0 -1
- package/static/static/js/931.dc961e99.js +0 -620
- package/static/static/js/931.dc961e99.js.map +0 -1
- package/static/static/js/async/173.9cf6b074.js +0 -3
- package/static/static/js/async/173.9cf6b074.js.map +0 -1
- package/static/static/js/async/212.e243c338.js +0 -158
- package/static/static/js/async/329.f888b505.js +0 -26
- package/static/static/js/async/329.f888b505.js.map +0 -1
- package/static/static/js/async/364.1821e74b.js +0 -30
- package/static/static/js/async/364.1821e74b.js.map +0 -1
- package/static/static/js/async/544.b73fa603.js +0 -2
- package/static/static/js/async/544.b73fa603.js.map +0 -1
- package/static/static/js/async/582.5dccae2d.js +0 -21
- package/static/static/js/async/582.5dccae2d.js.map +0 -1
- package/static/static/js/async/624.45ee2b2c.js +0 -3
- package/static/static/js/async/624.45ee2b2c.js.map +0 -1
- package/static/static/js/async/659.9afd03db.js +0 -21
- package/static/static/js/async/659.9afd03db.js.map +0 -1
- package/static/static/js/async/702.60261735.js.map +0 -1
- package/static/static/js/async/920.7d9a9aa8.js +0 -2
- package/static/static/js/async/920.7d9a9aa8.js.map +0 -1
- package/static/static/js/async/983.8b91303f.js +0 -1
- package/static/static/js/index.c7ba8adb.js +0 -10
- package/static/static/js/index.c7ba8adb.js.map +0 -1
- package/static/static/js/lib-react.f566a9ed.js +0 -3
- package/static/static/js/lib-react.f566a9ed.js.map +0 -1
- /package/static/static/js/{index.c7ba8adb.js.LICENSE.txt → index.0bbfffbf.js.LICENSE.txt} +0 -0
- /package/static/static/js/{lib-react.f566a9ed.js.LICENSE.txt → lib-react.7b1abe58.js.LICENSE.txt} +0 -0
- /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(`
|
|
232
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
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
|
|
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('
|
|
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 ??
|
|
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 =
|
|
399
|
-
if ('
|
|
400
|
-
else if ('
|
|
401
|
-
else if ('
|
|
402
|
-
else if ('
|
|
403
|
-
else if ('
|
|
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 (
|
|
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(
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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 =
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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.
|
|
810
|
-
const endY = Math.round(0.
|
|
811
|
-
await this.swipe(centerX, startY, centerX, endY,
|
|
812
|
-
debugDevice('Dismissed keyboard with swipe
|
|
813
|
-
await (0, utils_namespaceObject.sleep)(
|
|
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 =
|
|
885
|
-
const wdaPort =
|
|
886
|
-
const wdaHost =
|
|
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
|
-
|
|
947
|
-
const
|
|
948
|
-
|
|
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
|
|
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(
|
|
1097
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
980
1098
|
Object.defineProperty(exports, '__esModule', {
|
|
981
1099
|
value: true
|
|
982
1100
|
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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/
|
|
42
|
-
"@midscene/
|
|
43
|
-
"@midscene/
|
|
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.
|
|
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
|
-
"
|
|
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",
|