@sqaitech/ios 0.30.10
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 +336 -0
- package/bin/sqai-ios-playground +3 -0
- package/dist/es/bin.mjs +12872 -0
- package/dist/es/bin.mjs.LICENSE.txt +256 -0
- package/dist/es/index.mjs +923 -0
- package/dist/lib/bin.js +13017 -0
- package/dist/lib/bin.js.LICENSE.txt +256 -0
- package/dist/lib/index.js +982 -0
- package/dist/types/bin.d.ts +1 -0
- package/dist/types/index.d.ts +132 -0
- package/package.json +65 -0
- package/static/favicon.ico +0 -0
- package/static/index.html +1 -0
- package/static/static/css/index.60c69390.css +2 -0
- package/static/static/css/index.60c69390.css.map +1 -0
- package/static/static/js/931.dc961e99.js +620 -0
- package/static/static/js/931.dc961e99.js.LICENSE.txt +146 -0
- package/static/static/js/931.dc961e99.js.map +1 -0
- package/static/static/js/async/173.9cf6b074.js +3 -0
- package/static/static/js/async/173.9cf6b074.js.map +1 -0
- package/static/static/js/async/212.e243c338.js +158 -0
- package/static/static/js/async/212.e243c338.js.map +1 -0
- package/static/static/js/async/329.f888b505.js +26 -0
- package/static/static/js/async/329.f888b505.js.map +1 -0
- package/static/static/js/async/364.1821e74b.js +30 -0
- package/static/static/js/async/364.1821e74b.js.map +1 -0
- package/static/static/js/async/544.b73fa603.js +2 -0
- package/static/static/js/async/544.b73fa603.js.map +1 -0
- package/static/static/js/async/582.5dccae2d.js +21 -0
- package/static/static/js/async/582.5dccae2d.js.map +1 -0
- package/static/static/js/async/624.45ee2b2c.js +3 -0
- package/static/static/js/async/624.45ee2b2c.js.map +1 -0
- package/static/static/js/async/644.6bdc4065.js +1 -0
- package/static/static/js/async/659.9afd03db.js +21 -0
- package/static/static/js/async/659.9afd03db.js.map +1 -0
- package/static/static/js/async/702.60261735.js +231 -0
- package/static/static/js/async/702.60261735.js.map +1 -0
- package/static/static/js/async/920.7d9a9aa8.js +2 -0
- package/static/static/js/async/920.7d9a9aa8.js.map +1 -0
- package/static/static/js/async/983.8b91303f.js +1 -0
- package/static/static/js/index.5cccbdaf.js +10 -0
- package/static/static/js/index.5cccbdaf.js.LICENSE.txt +7 -0
- package/static/static/js/index.5cccbdaf.js.map +1 -0
- package/static/static/js/index.8a10896b.js +10 -0
- package/static/static/js/index.8a10896b.js.LICENSE.txt +7 -0
- package/static/static/js/index.8a10896b.js.map +1 -0
- package/static/static/js/index.f21bb1df.js +10 -0
- package/static/static/js/index.f21bb1df.js.LICENSE.txt +7 -0
- package/static/static/js/index.f21bb1df.js.map +1 -0
- package/static/static/js/lib-react.f566a9ed.js +3 -0
- package/static/static/js/lib-react.f566a9ed.js.LICENSE.txt +39 -0
- package/static/static/js/lib-react.f566a9ed.js.map +1 -0
- package/static/static/svg/server-offline-foreground.3113df3c.svg +36 -0
- package/static/static/wasm/9e906fbf55e08f98.module.wasm +0 -0
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
12
|
+
(()=>{
|
|
13
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
14
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: definition[key]
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
})();
|
|
20
|
+
(()=>{
|
|
21
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
22
|
+
})();
|
|
23
|
+
(()=>{
|
|
24
|
+
__webpack_require__.r = (exports1)=>{
|
|
25
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
26
|
+
value: 'Module'
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
29
|
+
value: true
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
})();
|
|
33
|
+
var __webpack_exports__ = {};
|
|
34
|
+
__webpack_require__.r(__webpack_exports__);
|
|
35
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
36
|
+
overrideAIConfig: ()=>env_namespaceObject.overrideAIConfig,
|
|
37
|
+
IOSDevice: ()=>IOSDevice,
|
|
38
|
+
IOSWebDriverClient: ()=>IOSWebDriverClient,
|
|
39
|
+
agentFromWebDriverAgent: ()=>agentFromWebDriverAgent,
|
|
40
|
+
IOSAgent: ()=>IOSAgent,
|
|
41
|
+
checkIOSEnvironment: ()=>checkIOSEnvironment
|
|
42
|
+
});
|
|
43
|
+
const external_node_assert_namespaceObject = require("node:assert");
|
|
44
|
+
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
45
|
+
const core_namespaceObject = require("@sqaitech/core");
|
|
46
|
+
const device_namespaceObject = require("@sqaitech/core/device");
|
|
47
|
+
const utils_namespaceObject = require("@sqaitech/core/utils");
|
|
48
|
+
const constants_namespaceObject = require("@sqaitech/shared/constants");
|
|
49
|
+
const img_namespaceObject = require("@sqaitech/shared/img");
|
|
50
|
+
const logger_namespaceObject = require("@sqaitech/shared/logger");
|
|
51
|
+
const webdriver_namespaceObject = require("@sqaitech/webdriver");
|
|
52
|
+
const debugIOS = (0, logger_namespaceObject.getDebug)('webdriver:ios');
|
|
53
|
+
class IOSWebDriverClient extends webdriver_namespaceObject.WebDriverClient {
|
|
54
|
+
async launchApp(bundleId) {
|
|
55
|
+
this.ensureSession();
|
|
56
|
+
try {
|
|
57
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/apps/launch`, {
|
|
58
|
+
bundleId
|
|
59
|
+
});
|
|
60
|
+
debugIOS(`Launched app: ${bundleId}`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
debugIOS(`Failed to launch app ${bundleId}: ${error}`);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async activateApp(bundleId) {
|
|
67
|
+
this.ensureSession();
|
|
68
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/apps/activate`, {
|
|
69
|
+
bundleId
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async terminateApp(bundleId) {
|
|
73
|
+
this.ensureSession();
|
|
74
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/apps/terminate`, {
|
|
75
|
+
bundleId
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async openUrl(url) {
|
|
79
|
+
this.ensureSession();
|
|
80
|
+
try {
|
|
81
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/url`, {
|
|
82
|
+
url
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
debugIOS(`Direct URL opening failed, trying Safari fallback: ${error}`);
|
|
86
|
+
await this.launchApp('com.apple.mobilesafari');
|
|
87
|
+
await new Promise((resolve)=>setTimeout(resolve, 2000));
|
|
88
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/url`, {
|
|
89
|
+
url
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async pressHomeButton() {
|
|
94
|
+
this.ensureSession();
|
|
95
|
+
try {
|
|
96
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/pressButton`, {
|
|
97
|
+
name: 'home'
|
|
98
|
+
});
|
|
99
|
+
debugIOS('Home button pressed using hardware key');
|
|
100
|
+
} catch (error) {
|
|
101
|
+
debugIOS(`Failed to press home button: ${error}`);
|
|
102
|
+
throw new Error(`Failed to press home button: ${error}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async appSwitcher() {
|
|
106
|
+
this.ensureSession();
|
|
107
|
+
try {
|
|
108
|
+
const windowSize = await this.getWindowSize();
|
|
109
|
+
debugIOS('Triggering app switcher with slow swipe up gesture');
|
|
110
|
+
const centerX = windowSize.width / 2;
|
|
111
|
+
const startY = windowSize.height - 5;
|
|
112
|
+
const endY = 0.5 * windowSize.height;
|
|
113
|
+
await this.swipe(centerX, startY, centerX, endY, 1500);
|
|
114
|
+
await new Promise((resolve)=>setTimeout(resolve, 800));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
debugIOS(`App switcher failed: ${error}`);
|
|
117
|
+
throw new Error(`Failed to trigger app switcher: ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async pressKey(key) {
|
|
121
|
+
this.ensureSession();
|
|
122
|
+
debugIOS(`Attempting to press key: ${key}`);
|
|
123
|
+
if ('Enter' === key || 'Return' === key || 'return' === key) {
|
|
124
|
+
debugIOS('Handling Enter/Return key for iOS');
|
|
125
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
126
|
+
value: [
|
|
127
|
+
'\n'
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
debugIOS('Sent newline character for Enter key');
|
|
131
|
+
await new Promise((resolve)=>setTimeout(resolve, 100));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if ('Backspace' === key || 'Delete' === key) try {
|
|
135
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
136
|
+
value: [
|
|
137
|
+
'\b'
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
debugIOS('Sent backspace character');
|
|
141
|
+
return;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
debugIOS(`Backspace failed: ${error}`);
|
|
144
|
+
}
|
|
145
|
+
if ('Space' === key) try {
|
|
146
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
147
|
+
value: [
|
|
148
|
+
' '
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
debugIOS('Sent space character');
|
|
152
|
+
return;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
debugIOS(`Space key failed: ${error}`);
|
|
155
|
+
}
|
|
156
|
+
const normalizedKey = this.normalizeKeyName(key);
|
|
157
|
+
const iosKeyMap = {
|
|
158
|
+
Tab: '\t',
|
|
159
|
+
ArrowUp: '\uE013',
|
|
160
|
+
ArrowDown: '\uE015',
|
|
161
|
+
ArrowLeft: '\uE012',
|
|
162
|
+
ArrowRight: '\uE014',
|
|
163
|
+
Home: '\uE011',
|
|
164
|
+
End: '\uE010'
|
|
165
|
+
};
|
|
166
|
+
if (iosKeyMap[normalizedKey]) try {
|
|
167
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
168
|
+
value: [
|
|
169
|
+
iosKeyMap[normalizedKey]
|
|
170
|
+
]
|
|
171
|
+
});
|
|
172
|
+
debugIOS(`Sent WebDriver key code for: ${key}`);
|
|
173
|
+
return;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
debugIOS(`WebDriver key failed for "${key}": ${error}`);
|
|
176
|
+
}
|
|
177
|
+
if (1 === key.length) try {
|
|
178
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
179
|
+
value: [
|
|
180
|
+
key
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
debugIOS(`Sent single character: "${key}"`);
|
|
184
|
+
return;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
debugIOS(`Failed to send character "${key}": ${error}`);
|
|
187
|
+
}
|
|
188
|
+
debugIOS(`Warning: Key "${key}" is not supported on iOS platform`);
|
|
189
|
+
throw new Error(`Key "${key}" is not supported on iOS platform`);
|
|
190
|
+
}
|
|
191
|
+
normalizeKeyName(key) {
|
|
192
|
+
return key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
async dismissKeyboard(keyNames) {
|
|
195
|
+
this.ensureSession();
|
|
196
|
+
try {
|
|
197
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keyboard/dismiss`, {
|
|
198
|
+
keyNames: keyNames || [
|
|
199
|
+
'done'
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
debugIOS('Dismissed keyboard using WDA API');
|
|
203
|
+
return true;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
debugIOS(`Failed to dismiss keyboard: ${error}`);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async typeText(text) {
|
|
210
|
+
this.ensureSession();
|
|
211
|
+
try {
|
|
212
|
+
const cleanText = text.trim();
|
|
213
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/keys`, {
|
|
214
|
+
value: cleanText.split('')
|
|
215
|
+
});
|
|
216
|
+
debugIOS(`Typed text: "${text}"`);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
debugIOS(`Failed to type text "${text}": ${error}`);
|
|
219
|
+
throw new Error(`Failed to type text: ${error}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async tap(x, y) {
|
|
223
|
+
this.ensureSession();
|
|
224
|
+
try {
|
|
225
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/tap`, {
|
|
226
|
+
x,
|
|
227
|
+
y
|
|
228
|
+
});
|
|
229
|
+
debugIOS(`Tapped at coordinates (${x}, ${y})`);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
debugIOS(`Failed to tap at (${x}, ${y}): ${error}`);
|
|
232
|
+
throw new Error(`Failed to tap at coordinates: ${error}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async swipe(fromX, fromY, toX, toY, duration = 500) {
|
|
236
|
+
this.ensureSession();
|
|
237
|
+
const actions = {
|
|
238
|
+
actions: [
|
|
239
|
+
{
|
|
240
|
+
type: 'pointer',
|
|
241
|
+
id: 'finger1',
|
|
242
|
+
parameters: {
|
|
243
|
+
pointerType: 'touch'
|
|
244
|
+
},
|
|
245
|
+
actions: [
|
|
246
|
+
{
|
|
247
|
+
type: 'pointerMove',
|
|
248
|
+
duration: 0,
|
|
249
|
+
x: fromX,
|
|
250
|
+
y: fromY
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
type: 'pointerDown',
|
|
254
|
+
button: 0
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
type: 'pause',
|
|
258
|
+
duration: 100
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: 'pointerMove',
|
|
262
|
+
duration,
|
|
263
|
+
x: toX,
|
|
264
|
+
y: toY
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: 'pointerUp',
|
|
268
|
+
button: 0
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/actions`, actions);
|
|
275
|
+
debugIOS(`Swiped using W3C Actions from (${fromX}, ${fromY}) to (${toX}, ${toY}) in ${duration}ms`);
|
|
276
|
+
}
|
|
277
|
+
async longPress(x, y, duration = 1000) {
|
|
278
|
+
this.ensureSession();
|
|
279
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/touchAndHold`, {
|
|
280
|
+
x,
|
|
281
|
+
y,
|
|
282
|
+
duration: duration / 1000
|
|
283
|
+
});
|
|
284
|
+
debugIOS(`Long pressed at coordinates (${x}, ${y}) for ${duration}ms`);
|
|
285
|
+
}
|
|
286
|
+
async doubleTap(x, y) {
|
|
287
|
+
this.ensureSession();
|
|
288
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/doubleTap`, {
|
|
289
|
+
x,
|
|
290
|
+
y
|
|
291
|
+
});
|
|
292
|
+
debugIOS(`Double tapped at coordinates (${x}, ${y})`);
|
|
293
|
+
}
|
|
294
|
+
async tripleTap(x, y) {
|
|
295
|
+
this.ensureSession();
|
|
296
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/wda/tapWithNumberOfTaps`, {
|
|
297
|
+
x,
|
|
298
|
+
y,
|
|
299
|
+
numberOfTaps: 3,
|
|
300
|
+
numberOfTouches: 1
|
|
301
|
+
});
|
|
302
|
+
debugIOS(`Triple tapped at coordinates (${x}, ${y})`);
|
|
303
|
+
}
|
|
304
|
+
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;
|
|
310
|
+
}
|
|
311
|
+
debugIOS('No screen scale found in WDA screen response');
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
async createSession(capabilities) {
|
|
315
|
+
const defaultCapabilities = {
|
|
316
|
+
platformName: 'iOS',
|
|
317
|
+
automationName: 'XCUITest',
|
|
318
|
+
shouldUseSingletonTestManager: false,
|
|
319
|
+
shouldUseTestManagerForVisibilityDetection: false,
|
|
320
|
+
...capabilities
|
|
321
|
+
};
|
|
322
|
+
const session = await super.createSession(defaultCapabilities);
|
|
323
|
+
await this.setupIOSSession();
|
|
324
|
+
return session;
|
|
325
|
+
}
|
|
326
|
+
async setupIOSSession() {
|
|
327
|
+
if (!this.sessionId) return;
|
|
328
|
+
try {
|
|
329
|
+
await this.makeRequest('POST', `/session/${this.sessionId}/appium/settings`, {
|
|
330
|
+
snapshotMaxDepth: 50,
|
|
331
|
+
elementResponseAttributes: 'type,label,name,value,rect,enabled,visible'
|
|
332
|
+
});
|
|
333
|
+
debugIOS('iOS session configuration applied');
|
|
334
|
+
} catch (error) {
|
|
335
|
+
debugIOS(`Failed to apply iOS session configuration: ${error}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function _define_property(obj, key, value) {
|
|
340
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
341
|
+
value: value,
|
|
342
|
+
enumerable: true,
|
|
343
|
+
configurable: true,
|
|
344
|
+
writable: true
|
|
345
|
+
});
|
|
346
|
+
else obj[key] = value;
|
|
347
|
+
return obj;
|
|
348
|
+
}
|
|
349
|
+
const debugDevice = (0, logger_namespaceObject.getDebug)('ios:device');
|
|
350
|
+
const BackspaceChar = '\u0008';
|
|
351
|
+
class IOSDevice {
|
|
352
|
+
actionSpace() {
|
|
353
|
+
const defaultActions = [
|
|
354
|
+
(0, device_namespaceObject.defineActionTap)(async (param)=>{
|
|
355
|
+
const element = param.locate;
|
|
356
|
+
external_node_assert_default()(element, 'Element not found, cannot tap');
|
|
357
|
+
await this.mouseClick(element.center[0], element.center[1]);
|
|
358
|
+
}),
|
|
359
|
+
(0, device_namespaceObject.defineActionDoubleClick)(async (param)=>{
|
|
360
|
+
const element = param.locate;
|
|
361
|
+
external_node_assert_default()(element, 'Element not found, cannot double click');
|
|
362
|
+
await this.doubleTap(element.center[0], element.center[1]);
|
|
363
|
+
}),
|
|
364
|
+
(0, device_namespaceObject.defineAction)({
|
|
365
|
+
name: 'Input',
|
|
366
|
+
description: 'Input text into the input field',
|
|
367
|
+
interfaceAlias: 'aiInput',
|
|
368
|
+
paramSchema: core_namespaceObject.z.object({
|
|
369
|
+
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.'),
|
|
371
|
+
mode: core_namespaceObject.z["enum"]([
|
|
372
|
+
'replace',
|
|
373
|
+
'clear',
|
|
374
|
+
'append'
|
|
375
|
+
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "append" - append the value to existing content; "clear" - clear the field without inputting new text.'),
|
|
376
|
+
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
377
|
+
}),
|
|
378
|
+
call: async (param)=>{
|
|
379
|
+
var _this_options;
|
|
380
|
+
const element = param.locate;
|
|
381
|
+
if (element) {
|
|
382
|
+
if ('append' !== param.mode) await this.clearInput(element);
|
|
383
|
+
}
|
|
384
|
+
if ('clear' === param.mode) return;
|
|
385
|
+
if (!param || !param.value) return;
|
|
386
|
+
const autoDismissKeyboard = param.autoDismissKeyboard ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard);
|
|
387
|
+
await this.typeText(param.value, {
|
|
388
|
+
autoDismissKeyboard
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}),
|
|
392
|
+
(0, device_namespaceObject.defineActionScroll)(async (param)=>{
|
|
393
|
+
const element = param.locate;
|
|
394
|
+
const startingPoint = element ? {
|
|
395
|
+
left: element.center[0],
|
|
396
|
+
top: element.center[1]
|
|
397
|
+
} : 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)}`);
|
|
404
|
+
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);
|
|
406
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
407
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
408
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
409
|
+
else await this.scrollDown((null == param ? void 0 : param.distance) || void 0, startingPoint);
|
|
410
|
+
await (0, utils_namespaceObject.sleep)(500);
|
|
411
|
+
}
|
|
412
|
+
}),
|
|
413
|
+
(0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
|
|
414
|
+
const from = param.from;
|
|
415
|
+
const to = param.to;
|
|
416
|
+
external_node_assert_default()(from, 'missing "from" param for drag and drop');
|
|
417
|
+
external_node_assert_default()(to, 'missing "to" param for drag and drop');
|
|
418
|
+
await this.swipe(from.center[0], from.center[1], to.center[0], to.center[1]);
|
|
419
|
+
}),
|
|
420
|
+
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
421
|
+
await this.pressKey(param.keyName);
|
|
422
|
+
}),
|
|
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
|
+
(0, device_namespaceObject.defineAction)({
|
|
440
|
+
name: 'IOSLongPress',
|
|
441
|
+
description: 'Trigger a long press on the screen at specified coordinates on iOS devices',
|
|
442
|
+
paramSchema: core_namespaceObject.z.object({
|
|
443
|
+
duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
|
|
444
|
+
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
|
|
445
|
+
}),
|
|
446
|
+
call: async (param)=>{
|
|
447
|
+
const element = param.locate;
|
|
448
|
+
external_node_assert_default()(element, 'IOSLongPress requires an element to be located');
|
|
449
|
+
const [x, y] = element.center;
|
|
450
|
+
await this.longPress(x, y, null == param ? void 0 : param.duration);
|
|
451
|
+
}
|
|
452
|
+
}),
|
|
453
|
+
(0, device_namespaceObject.defineActionClearInput)(async (param)=>{
|
|
454
|
+
const element = param.locate;
|
|
455
|
+
external_node_assert_default()(element, 'Element not found, cannot clear input');
|
|
456
|
+
await this.clearInput(element);
|
|
457
|
+
})
|
|
458
|
+
];
|
|
459
|
+
const customActions = this.customActions || [];
|
|
460
|
+
return [
|
|
461
|
+
...defaultActions,
|
|
462
|
+
...customActions
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
describe() {
|
|
466
|
+
return this.description || `Device ID: ${this.deviceId}`;
|
|
467
|
+
}
|
|
468
|
+
async getConnectedDeviceInfo() {
|
|
469
|
+
return await this.wdaBackend.getDeviceInfo();
|
|
470
|
+
}
|
|
471
|
+
async connect() {
|
|
472
|
+
external_node_assert_default()(!this.destroyed, `IOSDevice ${this.deviceId} has been destroyed and cannot execute commands`);
|
|
473
|
+
debugDevice(`Connecting to iOS device: ${this.deviceId}`);
|
|
474
|
+
try {
|
|
475
|
+
await this.wdaManager.start();
|
|
476
|
+
await this.wdaBackend.createSession();
|
|
477
|
+
const deviceInfo = await this.wdaBackend.getDeviceInfo();
|
|
478
|
+
if (null == deviceInfo ? void 0 : deviceInfo.udid) {
|
|
479
|
+
this.deviceId = deviceInfo.udid;
|
|
480
|
+
debugDevice(`Updated device ID to real UDID: ${this.deviceId}`);
|
|
481
|
+
}
|
|
482
|
+
const size = await this.getScreenSize();
|
|
483
|
+
this.description = `
|
|
484
|
+
UDID: ${this.deviceId}${deviceInfo ? `
|
|
485
|
+
Name: ${deviceInfo.name}
|
|
486
|
+
Model: ${deviceInfo.model}` : ''}
|
|
487
|
+
Type: WebDriverAgent
|
|
488
|
+
ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
|
|
489
|
+
`;
|
|
490
|
+
debugDevice('iOS device connected successfully', this.description);
|
|
491
|
+
} catch (e) {
|
|
492
|
+
debugDevice(`Failed to connect to iOS device: ${e}`);
|
|
493
|
+
throw new Error(`Unable to connect to iOS device ${this.deviceId}: ${e}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async launch(uri) {
|
|
497
|
+
this.uri = uri;
|
|
498
|
+
try {
|
|
499
|
+
debugDevice(`Launching app: ${uri}`);
|
|
500
|
+
if (uri.startsWith('http://') || uri.startsWith('https://') || uri.includes('://')) await this.openUrl(uri);
|
|
501
|
+
else await this.wdaBackend.launchApp(uri);
|
|
502
|
+
debugDevice(`Successfully launched: ${uri}`);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
debugDevice(`Error launching ${uri}: ${error}`);
|
|
505
|
+
throw new Error(`Failed to launch ${uri}: ${error.message}`);
|
|
506
|
+
}
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
async getElementsInfo() {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
async getElementsNodeTree() {
|
|
513
|
+
return {
|
|
514
|
+
node: null,
|
|
515
|
+
children: []
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async initializeDevicePixelRatio() {
|
|
519
|
+
if (this.devicePixelRatioInitialized) return;
|
|
520
|
+
const apiScale = await this.wdaBackend.getScreenScale();
|
|
521
|
+
external_node_assert_default()(apiScale && apiScale > 0, 'Failed to get device pixel ratio from WebDriverAgent API');
|
|
522
|
+
debugDevice(`Got screen scale from WebDriverAgent API: ${apiScale}`);
|
|
523
|
+
this.devicePixelRatio = apiScale;
|
|
524
|
+
this.devicePixelRatioInitialized = true;
|
|
525
|
+
}
|
|
526
|
+
async getScreenSize() {
|
|
527
|
+
await this.initializeDevicePixelRatio();
|
|
528
|
+
const windowSize = await this.wdaBackend.getWindowSize();
|
|
529
|
+
return {
|
|
530
|
+
width: windowSize.width,
|
|
531
|
+
height: windowSize.height,
|
|
532
|
+
scale: this.devicePixelRatio
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async size() {
|
|
536
|
+
const screenSize = await this.getScreenSize();
|
|
537
|
+
return {
|
|
538
|
+
width: screenSize.width,
|
|
539
|
+
height: screenSize.height,
|
|
540
|
+
dpr: screenSize.scale
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
async screenshotBase64() {
|
|
544
|
+
debugDevice('Taking screenshot via WDA');
|
|
545
|
+
try {
|
|
546
|
+
const base64Data = await this.wdaBackend.takeScreenshot();
|
|
547
|
+
const result = (0, img_namespaceObject.createImgBase64ByFormat)('png', base64Data);
|
|
548
|
+
debugDevice('Screenshot taken successfully');
|
|
549
|
+
return result;
|
|
550
|
+
} catch (error) {
|
|
551
|
+
debugDevice(`Screenshot failed: ${error}`);
|
|
552
|
+
throw new Error(`Failed to take screenshot: ${error}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async clearInput(element) {
|
|
556
|
+
if (!element) return;
|
|
557
|
+
await this.tap(element.center[0], element.center[1]);
|
|
558
|
+
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
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async url() {
|
|
574
|
+
return '';
|
|
575
|
+
}
|
|
576
|
+
async tap(x, y) {
|
|
577
|
+
await this.wdaBackend.tap(Math.round(x), Math.round(y));
|
|
578
|
+
}
|
|
579
|
+
async mouseClick(x, y) {
|
|
580
|
+
debugDevice(`mouseClick at coordinates (${x}, ${y})`);
|
|
581
|
+
await this.tap(x, y);
|
|
582
|
+
}
|
|
583
|
+
async doubleTap(x, y) {
|
|
584
|
+
await this.wdaBackend.doubleTap(Math.round(x), Math.round(y));
|
|
585
|
+
}
|
|
586
|
+
async tripleTap(x, y) {
|
|
587
|
+
await this.wdaBackend.tripleTap(Math.round(x), Math.round(y));
|
|
588
|
+
}
|
|
589
|
+
async longPress(x, y, duration = 1000) {
|
|
590
|
+
await this.wdaBackend.longPress(Math.round(x), Math.round(y), duration);
|
|
591
|
+
}
|
|
592
|
+
async swipe(fromX, fromY, toX, toY, duration = 500) {
|
|
593
|
+
await this.wdaBackend.swipe(Math.round(fromX), Math.round(fromY), Math.round(toX), Math.round(toY), duration);
|
|
594
|
+
}
|
|
595
|
+
async typeText(text, options) {
|
|
596
|
+
var _this_options;
|
|
597
|
+
if (!text) return;
|
|
598
|
+
const shouldAutoDismissKeyboard = (null == options ? void 0 : options.autoDismissKeyboard) ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard) ?? true;
|
|
599
|
+
debugDevice(`Typing text: "${text}"`);
|
|
600
|
+
try {
|
|
601
|
+
await (0, utils_namespaceObject.sleep)(200);
|
|
602
|
+
await this.wdaBackend.typeText(text);
|
|
603
|
+
await (0, utils_namespaceObject.sleep)(300);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
debugDevice(`Failed to type text with WDA: ${error}`);
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
if (shouldAutoDismissKeyboard) await this.hideKeyboard();
|
|
609
|
+
}
|
|
610
|
+
async pressKey(key) {
|
|
611
|
+
await this.wdaBackend.pressKey(key);
|
|
612
|
+
}
|
|
613
|
+
async scrollUp(distance, startPoint) {
|
|
614
|
+
const { width, height } = await this.size();
|
|
615
|
+
const start = startPoint ? {
|
|
616
|
+
x: Math.round(startPoint.left),
|
|
617
|
+
y: Math.round(startPoint.top)
|
|
618
|
+
} : {
|
|
619
|
+
x: Math.round(width / 2),
|
|
620
|
+
y: Math.round(height / 2)
|
|
621
|
+
};
|
|
622
|
+
const scrollDistance = Math.round(distance || height / 3);
|
|
623
|
+
await this.swipe(start.x, start.y, start.x, start.y + scrollDistance);
|
|
624
|
+
}
|
|
625
|
+
async scrollDown(distance, startPoint) {
|
|
626
|
+
const { width, height } = await this.size();
|
|
627
|
+
const start = startPoint ? {
|
|
628
|
+
x: Math.round(startPoint.left),
|
|
629
|
+
y: Math.round(startPoint.top)
|
|
630
|
+
} : {
|
|
631
|
+
x: Math.round(width / 2),
|
|
632
|
+
y: Math.round(height / 2)
|
|
633
|
+
};
|
|
634
|
+
const scrollDistance = Math.round(distance || height / 3);
|
|
635
|
+
await this.swipe(start.x, start.y, start.x, start.y - scrollDistance);
|
|
636
|
+
}
|
|
637
|
+
async scrollLeft(distance, startPoint) {
|
|
638
|
+
const { width, height } = await this.size();
|
|
639
|
+
const start = startPoint ? {
|
|
640
|
+
x: Math.round(startPoint.left),
|
|
641
|
+
y: Math.round(startPoint.top)
|
|
642
|
+
} : {
|
|
643
|
+
x: Math.round(width / 2),
|
|
644
|
+
y: Math.round(height / 2)
|
|
645
|
+
};
|
|
646
|
+
const scrollDistance = Math.round(distance || 0.7 * width);
|
|
647
|
+
await this.swipe(start.x, start.y, start.x + scrollDistance, start.y);
|
|
648
|
+
}
|
|
649
|
+
async scrollRight(distance, startPoint) {
|
|
650
|
+
const { width, height } = await this.size();
|
|
651
|
+
const start = startPoint ? {
|
|
652
|
+
x: Math.round(startPoint.left),
|
|
653
|
+
y: Math.round(startPoint.top)
|
|
654
|
+
} : {
|
|
655
|
+
x: Math.round(width / 2),
|
|
656
|
+
y: Math.round(height / 2)
|
|
657
|
+
};
|
|
658
|
+
const scrollDistance = Math.round(distance || 0.7 * width);
|
|
659
|
+
await this.swipe(start.x, start.y, start.x - scrollDistance, start.y);
|
|
660
|
+
}
|
|
661
|
+
async scrollUntilTop(startPoint) {
|
|
662
|
+
debugDevice('Using screenshot-based scroll detection for better reliability');
|
|
663
|
+
await this.scrollUntilBoundary('up', startPoint, 1);
|
|
664
|
+
}
|
|
665
|
+
async scrollUntilBottom(startPoint) {
|
|
666
|
+
debugDevice('Using screenshot-based scroll detection for better reliability');
|
|
667
|
+
await this.scrollUntilBoundary('down', startPoint, 1);
|
|
668
|
+
}
|
|
669
|
+
compareScreenshots(screenshot1, screenshot2, tolerancePercent = 2) {
|
|
670
|
+
if (screenshot1 === screenshot2) {
|
|
671
|
+
debugDevice('Screenshots are identical');
|
|
672
|
+
return true;
|
|
673
|
+
}
|
|
674
|
+
const len1 = screenshot1.length;
|
|
675
|
+
const len2 = screenshot2.length;
|
|
676
|
+
debugDevice(`Screenshots differ: length1=${len1}, length2=${len2}`);
|
|
677
|
+
if (Math.abs(len1 - len2) > 0.1 * Math.min(len1, len2)) {
|
|
678
|
+
debugDevice('Screenshots have significant length difference');
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (len1 > 0 && len2 > 0) {
|
|
682
|
+
const minLength = Math.min(len1, len2);
|
|
683
|
+
const sampleSize = Math.min(2000, minLength);
|
|
684
|
+
let diffCount = 0;
|
|
685
|
+
for(let i = 0; i < sampleSize; i++)if (screenshot1[i] !== screenshot2[i]) diffCount++;
|
|
686
|
+
const diffPercent = diffCount / sampleSize * 100;
|
|
687
|
+
debugDevice(`Character differences: ${diffCount}/${sampleSize} (${diffPercent.toFixed(2)}%)`);
|
|
688
|
+
const isSimilar = diffPercent <= tolerancePercent;
|
|
689
|
+
if (isSimilar) debugDevice(`Screenshots are similar enough (${diffPercent.toFixed(2)}% <= ${tolerancePercent}%)`);
|
|
690
|
+
return isSimilar;
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
async scrollUntilBoundary(direction, startPoint, maxUnchangedCount = 1) {
|
|
695
|
+
const maxAttempts = 20;
|
|
696
|
+
const { width, height } = await this.size();
|
|
697
|
+
let start;
|
|
698
|
+
if (startPoint) start = {
|
|
699
|
+
x: Math.round(startPoint.left),
|
|
700
|
+
y: Math.round(startPoint.top)
|
|
701
|
+
};
|
|
702
|
+
else switch(direction){
|
|
703
|
+
case 'up':
|
|
704
|
+
start = {
|
|
705
|
+
x: Math.round(width / 2),
|
|
706
|
+
y: Math.round(0.2 * height)
|
|
707
|
+
};
|
|
708
|
+
break;
|
|
709
|
+
case 'down':
|
|
710
|
+
start = {
|
|
711
|
+
x: Math.round(width / 2),
|
|
712
|
+
y: Math.round(0.8 * height)
|
|
713
|
+
};
|
|
714
|
+
break;
|
|
715
|
+
case 'left':
|
|
716
|
+
start = {
|
|
717
|
+
x: Math.round(0.8 * width),
|
|
718
|
+
y: Math.round(height / 2)
|
|
719
|
+
};
|
|
720
|
+
break;
|
|
721
|
+
case 'right':
|
|
722
|
+
start = {
|
|
723
|
+
x: Math.round(0.2 * width),
|
|
724
|
+
y: Math.round(height / 2)
|
|
725
|
+
};
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
let lastScreenshot = null;
|
|
729
|
+
let unchangedCount = 0;
|
|
730
|
+
debugDevice(`Starting scroll to ${direction} with content detection`);
|
|
731
|
+
for(let i = 0; i < maxAttempts; i++)try {
|
|
732
|
+
debugDevice(`Scroll attempt ${i + 1}/${maxAttempts}`);
|
|
733
|
+
await (0, utils_namespaceObject.sleep)(500);
|
|
734
|
+
const currentScreenshot = await this.screenshotBase64();
|
|
735
|
+
if (lastScreenshot && this.compareScreenshots(lastScreenshot, currentScreenshot, 10)) {
|
|
736
|
+
unchangedCount++;
|
|
737
|
+
debugDevice(`Screen content unchanged (${unchangedCount}/${maxUnchangedCount})`);
|
|
738
|
+
if (unchangedCount >= maxUnchangedCount) {
|
|
739
|
+
debugDevice(`Reached ${direction}: screen content no longer changes`);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
if (lastScreenshot) debugDevice(`Content changed, resetting counter (was ${unchangedCount})`);
|
|
744
|
+
unchangedCount = 0;
|
|
745
|
+
}
|
|
746
|
+
if (i >= 15 && 0 === unchangedCount) {
|
|
747
|
+
debugDevice(`Too many attempts with dynamic content, stopping scroll to ${direction}`);
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
lastScreenshot = currentScreenshot;
|
|
751
|
+
const scrollDistance = Math.round('left' === direction || 'right' === direction ? 0.6 * width : 0.6 * height);
|
|
752
|
+
debugDevice(`Performing scroll: ${direction}, distance: ${scrollDistance}`);
|
|
753
|
+
switch(direction){
|
|
754
|
+
case 'up':
|
|
755
|
+
await this.swipe(start.x, start.y, start.x, start.y + scrollDistance, 300);
|
|
756
|
+
break;
|
|
757
|
+
case 'down':
|
|
758
|
+
await this.swipe(start.x, start.y, start.x, start.y - scrollDistance, 300);
|
|
759
|
+
break;
|
|
760
|
+
case 'left':
|
|
761
|
+
await this.swipe(start.x, start.y, start.x + scrollDistance, start.y, 300);
|
|
762
|
+
break;
|
|
763
|
+
case 'right':
|
|
764
|
+
await this.swipe(start.x, start.y, start.x - scrollDistance, start.y, 300);
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
debugDevice('Waiting for scroll and inertia to complete...');
|
|
768
|
+
await (0, utils_namespaceObject.sleep)(2000);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
debugDevice(`Error during scroll attempt ${i + 1}: ${error}`);
|
|
771
|
+
await (0, utils_namespaceObject.sleep)(300);
|
|
772
|
+
}
|
|
773
|
+
debugDevice(`Scroll to ${direction} completed after ${maxAttempts} attempts`);
|
|
774
|
+
}
|
|
775
|
+
async scrollUntilLeft(startPoint) {
|
|
776
|
+
await this.scrollUntilBoundary('left', startPoint, 1);
|
|
777
|
+
}
|
|
778
|
+
async scrollUntilRight(startPoint) {
|
|
779
|
+
await this.scrollUntilBoundary('right', startPoint, 3);
|
|
780
|
+
}
|
|
781
|
+
async home() {
|
|
782
|
+
await this.wdaBackend.pressHomeButton();
|
|
783
|
+
}
|
|
784
|
+
async appSwitcher() {
|
|
785
|
+
try {
|
|
786
|
+
debugDevice('Triggering app switcher with slow swipe up gesture');
|
|
787
|
+
const { width, height } = await this.size();
|
|
788
|
+
const centerX = Math.round(width / 2);
|
|
789
|
+
const startY = Math.round(height - 5);
|
|
790
|
+
const endY = Math.round(0.5 * height);
|
|
791
|
+
await this.wdaBackend.swipe(centerX, startY, centerX, endY, 1500);
|
|
792
|
+
await (0, utils_namespaceObject.sleep)(800);
|
|
793
|
+
} catch (error) {
|
|
794
|
+
debugDevice(`App switcher failed: ${error}`);
|
|
795
|
+
throw new Error(`Failed to trigger app switcher: ${error}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
async hideKeyboard(keyNames) {
|
|
799
|
+
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);
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
const windowSize = await this.wdaBackend.getWindowSize();
|
|
808
|
+
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);
|
|
814
|
+
return true;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
debugDevice(`Failed to hide keyboard: ${error}`);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async openUrl(url, options) {
|
|
821
|
+
const opts = {
|
|
822
|
+
useSafariAsBackup: true,
|
|
823
|
+
waitTime: 2000,
|
|
824
|
+
...options
|
|
825
|
+
};
|
|
826
|
+
try {
|
|
827
|
+
debugDevice(`Opening URL: ${url}`);
|
|
828
|
+
await this.wdaBackend.openUrl(url);
|
|
829
|
+
await (0, utils_namespaceObject.sleep)(opts.waitTime);
|
|
830
|
+
debugDevice(`Successfully opened URL: ${url}`);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
debugDevice(`Direct URL opening failed: ${error}`);
|
|
833
|
+
if (opts.useSafariAsBackup) {
|
|
834
|
+
debugDevice(`Attempting to open URL via Safari: ${url}`);
|
|
835
|
+
await this.openUrlViaSafari(url);
|
|
836
|
+
} else throw new Error(`Failed to open URL: ${error}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
async openUrlViaSafari(url) {
|
|
840
|
+
try {
|
|
841
|
+
debugDevice(`Opening URL via Safari: ${url}`);
|
|
842
|
+
await this.wdaBackend.launchApp('com.apple.mobilesafari');
|
|
843
|
+
await (0, utils_namespaceObject.sleep)(2000);
|
|
844
|
+
await this.typeText(url);
|
|
845
|
+
await (0, utils_namespaceObject.sleep)(500);
|
|
846
|
+
await this.pressKey('Return');
|
|
847
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
848
|
+
try {
|
|
849
|
+
await (0, utils_namespaceObject.sleep)(2000);
|
|
850
|
+
debugDevice(`URL opened via Safari: ${url}`);
|
|
851
|
+
} catch (dialogError) {
|
|
852
|
+
debugDevice(`No confirmation dialog or dialog handling failed: ${dialogError}`);
|
|
853
|
+
}
|
|
854
|
+
} catch (error) {
|
|
855
|
+
debugDevice(`Failed to open URL via Safari: ${error}`);
|
|
856
|
+
throw new Error(`Failed to open URL via Safari: ${error}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async destroy() {
|
|
860
|
+
if (this.destroyed) return;
|
|
861
|
+
try {
|
|
862
|
+
await this.wdaBackend.deleteSession();
|
|
863
|
+
await this.wdaManager.stop();
|
|
864
|
+
} catch (error) {
|
|
865
|
+
debugDevice(`Error during cleanup: ${error}`);
|
|
866
|
+
}
|
|
867
|
+
this.destroyed = true;
|
|
868
|
+
debugDevice(`iOS device ${this.deviceId} destroyed`);
|
|
869
|
+
}
|
|
870
|
+
constructor(options){
|
|
871
|
+
_define_property(this, "deviceId", void 0);
|
|
872
|
+
_define_property(this, "devicePixelRatio", 1);
|
|
873
|
+
_define_property(this, "devicePixelRatioInitialized", false);
|
|
874
|
+
_define_property(this, "destroyed", false);
|
|
875
|
+
_define_property(this, "description", void 0);
|
|
876
|
+
_define_property(this, "customActions", void 0);
|
|
877
|
+
_define_property(this, "wdaBackend", void 0);
|
|
878
|
+
_define_property(this, "wdaManager", void 0);
|
|
879
|
+
_define_property(this, "interfaceType", 'ios');
|
|
880
|
+
_define_property(this, "uri", void 0);
|
|
881
|
+
_define_property(this, "options", void 0);
|
|
882
|
+
this.deviceId = 'pending-connection';
|
|
883
|
+
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';
|
|
887
|
+
this.wdaBackend = new IOSWebDriverClient({
|
|
888
|
+
port: wdaPort,
|
|
889
|
+
host: wdaHost
|
|
890
|
+
});
|
|
891
|
+
this.wdaManager = webdriver_namespaceObject.WDAManager.getInstance(wdaPort, wdaHost);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const agent_namespaceObject = require("@sqaitech/core/agent");
|
|
895
|
+
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
896
|
+
const external_node_os_namespaceObject = require("node:os");
|
|
897
|
+
const external_node_util_namespaceObject = require("node:util");
|
|
898
|
+
const execAsync = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.exec);
|
|
899
|
+
const debugUtils = (0, logger_namespaceObject.getDebug)('ios:utils');
|
|
900
|
+
function checkMacOSPlatform() {
|
|
901
|
+
const currentPlatform = (0, external_node_os_namespaceObject.platform)();
|
|
902
|
+
return {
|
|
903
|
+
isMacOS: 'darwin' === currentPlatform,
|
|
904
|
+
platform: currentPlatform
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
async function checkIOSEnvironment() {
|
|
908
|
+
try {
|
|
909
|
+
const platformCheck = checkMacOSPlatform();
|
|
910
|
+
if (!platformCheck.isMacOS) return {
|
|
911
|
+
available: false,
|
|
912
|
+
error: `iOS development is only supported on macOS. Current platform: ${platformCheck.platform}`
|
|
913
|
+
};
|
|
914
|
+
const { stdout: xcrunPath } = await execAsync('which xcrun');
|
|
915
|
+
if (!xcrunPath.trim()) return {
|
|
916
|
+
available: false,
|
|
917
|
+
error: 'xcrun not found. Please install Xcode Command Line Tools: xcode-select --install'
|
|
918
|
+
};
|
|
919
|
+
try {
|
|
920
|
+
await execAsync('xcodebuild -version');
|
|
921
|
+
} catch (error) {
|
|
922
|
+
return {
|
|
923
|
+
available: false,
|
|
924
|
+
error: 'xcodebuild not found. Please install Xcode from the App Store'
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
debugUtils('iOS environment is available for WebDriverAgent');
|
|
928
|
+
return {
|
|
929
|
+
available: true
|
|
930
|
+
};
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
933
|
+
debugUtils(`iOS environment not available: ${errorMsg}`);
|
|
934
|
+
if (errorMsg.includes('xcrun')) return {
|
|
935
|
+
available: false,
|
|
936
|
+
error: 'Xcode Command Line Tools not properly configured. Please run: sudo xcode-select --reset'
|
|
937
|
+
};
|
|
938
|
+
return {
|
|
939
|
+
available: false,
|
|
940
|
+
error: `iOS development environment not available: ${errorMsg}`
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const debugAgent = (0, logger_namespaceObject.getDebug)('ios:agent');
|
|
945
|
+
class IOSAgent extends agent_namespaceObject.Agent {
|
|
946
|
+
async launch(uri) {
|
|
947
|
+
const device = this.page;
|
|
948
|
+
await device.launch(uri);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async function agentFromWebDriverAgent(opts) {
|
|
952
|
+
debugAgent('Creating iOS agent with WebDriverAgent auto-detection');
|
|
953
|
+
const envCheck = await checkIOSEnvironment();
|
|
954
|
+
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
|
+
});
|
|
962
|
+
await device.connect();
|
|
963
|
+
return new IOSAgent(device, opts);
|
|
964
|
+
}
|
|
965
|
+
const env_namespaceObject = require("@sqaitech/shared/env");
|
|
966
|
+
exports.IOSAgent = __webpack_exports__.IOSAgent;
|
|
967
|
+
exports.IOSDevice = __webpack_exports__.IOSDevice;
|
|
968
|
+
exports.IOSWebDriverClient = __webpack_exports__.IOSWebDriverClient;
|
|
969
|
+
exports.agentFromWebDriverAgent = __webpack_exports__.agentFromWebDriverAgent;
|
|
970
|
+
exports.checkIOSEnvironment = __webpack_exports__.checkIOSEnvironment;
|
|
971
|
+
exports.overrideAIConfig = __webpack_exports__.overrideAIConfig;
|
|
972
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
973
|
+
"IOSAgent",
|
|
974
|
+
"IOSDevice",
|
|
975
|
+
"IOSWebDriverClient",
|
|
976
|
+
"agentFromWebDriverAgent",
|
|
977
|
+
"checkIOSEnvironment",
|
|
978
|
+
"overrideAIConfig"
|
|
979
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
980
|
+
Object.defineProperty(exports, '__esModule', {
|
|
981
|
+
value: true
|
|
982
|
+
});
|