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