appium-webdriveragent 10.5.3 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/Scripts/{build-webdriveragent.js → build-webdriveragent.mjs} +13 -7
  3. package/Scripts/{fetch-prebuilt-wda.js → fetch-prebuilt-wda.mjs} +17 -9
  4. package/Scripts/update-wda-version.mjs +42 -0
  5. package/WebDriverAgentLib/Info.plist +2 -2
  6. package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +6503 -6229
  7. package/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +4107 -3883
  8. package/build/index.d.ts +1 -1
  9. package/build/index.d.ts.map +1 -1
  10. package/build/index.js +1 -2
  11. package/build/index.js.map +1 -1
  12. package/build/lib/check-dependencies.d.ts +2 -7
  13. package/build/lib/check-dependencies.d.ts.map +1 -1
  14. package/build/lib/check-dependencies.js +0 -11
  15. package/build/lib/check-dependencies.js.map +1 -1
  16. package/build/lib/constants.d.ts +12 -12
  17. package/build/lib/constants.d.ts.map +1 -1
  18. package/build/lib/constants.js +14 -26
  19. package/build/lib/constants.js.map +1 -1
  20. package/build/lib/logger.d.ts +1 -2
  21. package/build/lib/logger.d.ts.map +1 -1
  22. package/build/lib/logger.js +2 -2
  23. package/build/lib/logger.js.map +1 -1
  24. package/build/lib/no-session-proxy.d.ts +5 -5
  25. package/build/lib/no-session-proxy.d.ts.map +1 -1
  26. package/build/lib/no-session-proxy.js +0 -1
  27. package/build/lib/no-session-proxy.js.map +1 -1
  28. package/build/lib/utils.d.ts +53 -96
  29. package/build/lib/utils.d.ts.map +1 -1
  30. package/build/lib/utils.js +124 -110
  31. package/build/lib/utils.js.map +1 -1
  32. package/build/lib/webdriveragent.d.ts +123 -170
  33. package/build/lib/webdriveragent.d.ts.map +1 -1
  34. package/build/lib/webdriveragent.js +319 -349
  35. package/build/lib/webdriveragent.js.map +1 -1
  36. package/build/lib/xcodebuild.d.ts +66 -77
  37. package/build/lib/xcodebuild.d.ts.map +1 -1
  38. package/build/lib/xcodebuild.js +123 -92
  39. package/build/lib/xcodebuild.js.map +1 -1
  40. package/build/test/functional/desired.d.ts +3 -0
  41. package/build/test/functional/desired.d.ts.map +1 -0
  42. package/build/test/functional/desired.js +9 -0
  43. package/build/test/functional/desired.js.map +1 -0
  44. package/build/test/functional/helpers/simulator.d.ts +5 -0
  45. package/build/test/functional/helpers/simulator.d.ts.map +1 -0
  46. package/build/test/functional/helpers/simulator.js +39 -0
  47. package/build/test/functional/helpers/simulator.js.map +1 -0
  48. package/build/test/functional/webdriveragent-e2e-specs.d.ts +2 -0
  49. package/build/test/functional/webdriveragent-e2e-specs.d.ts.map +1 -0
  50. package/build/test/functional/webdriveragent-e2e-specs.js +133 -0
  51. package/build/test/functional/webdriveragent-e2e-specs.js.map +1 -0
  52. package/build/test/unit/utils-specs.d.ts +2 -0
  53. package/build/test/unit/utils-specs.d.ts.map +1 -0
  54. package/build/test/unit/utils-specs.js +161 -0
  55. package/build/test/unit/utils-specs.js.map +1 -0
  56. package/build/test/unit/webdriveragent-specs.d.ts +2 -0
  57. package/build/test/unit/webdriveragent-specs.d.ts.map +1 -0
  58. package/build/test/unit/webdriveragent-specs.js +424 -0
  59. package/build/test/unit/webdriveragent-specs.js.map +1 -0
  60. package/build/tsconfig.tsbuildinfo +1 -0
  61. package/index.ts +1 -1
  62. package/lib/{check-dependencies.js → check-dependencies.ts} +4 -13
  63. package/lib/constants.ts +18 -0
  64. package/lib/logger.ts +3 -0
  65. package/lib/{no-session-proxy.js → no-session-proxy.ts} +4 -5
  66. package/lib/{utils.js → utils.ts} +101 -111
  67. package/lib/{webdriveragent.js → webdriveragent.ts} +353 -390
  68. package/lib/{xcodebuild.js → xcodebuild.ts} +145 -114
  69. package/package.json +7 -9
  70. package/lib/constants.js +0 -24
  71. package/lib/logger.js +0 -5
@@ -6,12 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WebDriverAgent = void 0;
7
7
  const asyncbox_1 = require("asyncbox");
8
8
  const lodash_1 = __importDefault(require("lodash"));
9
- const path_1 = __importDefault(require("path"));
10
- const url_1 = __importDefault(require("url"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_url_1 = __importDefault(require("node:url"));
11
11
  const bluebird_1 = __importDefault(require("bluebird"));
12
12
  const base_driver_1 = require("@appium/base-driver");
13
13
  const support_1 = require("@appium/support");
14
- const logger_1 = __importDefault(require("./logger"));
14
+ const logger_1 = require("./logger");
15
15
  const no_session_proxy_1 = require("./no-session-proxy");
16
16
  const utils_1 = require("./utils");
17
17
  const xcodebuild_1 = require("./xcodebuild");
@@ -27,29 +27,53 @@ const WDA_CF_BUNDLE_NAME = 'WebDriverAgentRunner-Runner';
27
27
  const SHARED_RESOURCES_GUARD = new async_lock_1.default();
28
28
  const RECENT_MODULE_VERSION_ITEM_NAME = 'recentWdaModuleVersion';
29
29
  class WebDriverAgent {
30
- /** @type {string} */
31
30
  bootstrapPath;
32
- /** @type {string} */
33
31
  agentPath;
34
- /** @type {any} @deprecated for removal */
35
- idb;
32
+ args;
33
+ log;
34
+ device;
35
+ platformVersion;
36
+ platformName;
37
+ iosSdkVersion;
38
+ host;
39
+ isRealDevice;
40
+ wdaBundlePath;
41
+ wdaLocalPort;
42
+ wdaRemotePort;
43
+ wdaBaseUrl;
44
+ wdaBindingIP;
45
+ prebuildWDA;
46
+ webDriverAgentUrl;
47
+ started;
48
+ wdaConnectionTimeout;
49
+ useXctestrunFile;
50
+ usePrebuiltWDA;
51
+ derivedDataPath;
52
+ mjpegServerPort;
53
+ updatedWDABundleId;
54
+ wdaLaunchTimeout;
55
+ usePreinstalledWDA;
56
+ xctestApiClient;
57
+ updatedWDABundleIdSuffix;
58
+ _xcodebuild;
59
+ noSessionProxy;
60
+ jwproxy;
61
+ proxyReqRes;
62
+ _url;
36
63
  /**
37
- * @param {import('appium-xcode').XcodeVersion | undefined} xcodeVersion @deprecated Will be removed as no actual usage.
38
- * @param {import('./types').WebDriverAgentArgs} args
39
- * @param {import('@appium/types').AppiumLogger?} [log=null]
64
+ * Creates a new WebDriverAgent instance.
65
+ * @param args - Configuration arguments for WebDriverAgent
66
+ * @param log - Optional logger instance
40
67
  */
41
- constructor(xcodeVersion, args, log = null) {
42
- this.xcodeVersion = xcodeVersion;
68
+ constructor(args, log = null) {
43
69
  this.args = lodash_1.default.clone(args);
44
- this.log = log ?? logger_1.default;
70
+ this.log = log ?? logger_1.log;
45
71
  this.device = args.device;
46
72
  this.platformVersion = args.platformVersion;
47
73
  this.platformName = args.platformName;
48
74
  this.iosSdkVersion = args.iosSdkVersion;
49
75
  this.host = args.host;
50
76
  this.isRealDevice = !!args.realDevice;
51
- /** @deprecated We'll stop supporting idb */
52
- this.idb = args.device.idb;
53
77
  this.wdaBundlePath = args.wdaBundlePath;
54
78
  this.setWDAPaths(args.bootstrapPath, args.agentPath);
55
79
  this.wdaLocalPort = args.wdaLocalPort;
@@ -73,9 +97,9 @@ class WebDriverAgent {
73
97
  this.usePreinstalledWDA = args.usePreinstalledWDA;
74
98
  this.xctestApiClient = null;
75
99
  this.updatedWDABundleIdSuffix = args.updatedWDABundleIdSuffix ?? constants_1.DEFAULT_TEST_BUNDLE_SUFFIX;
76
- this.xcodebuild = this.canSkipXcodebuild
100
+ this._xcodebuild = this.canSkipXcodebuild
77
101
  ? null
78
- : new xcodebuild_1.XcodeBuild(this.xcodeVersion, this.device, {
102
+ : new xcodebuild_1.XcodeBuild(this.device, {
79
103
  platformVersion: this.platformVersion,
80
104
  platformName: this.platformName,
81
105
  iosSdkVersion: this.iosSdkVersion,
@@ -104,42 +128,41 @@ class WebDriverAgent {
104
128
  }
105
129
  /**
106
130
  * Return true if the session does not need xcodebuild.
107
- * @returns {boolean} Whether the session needs/has xcodebuild.
131
+ * @returns Whether the session needs/has xcodebuild.
108
132
  */
109
133
  get canSkipXcodebuild() {
110
134
  // Use this.args.webDriverAgentUrl to guarantee
111
135
  // the capabilities set gave the `appium:webDriverAgentUrl`.
112
136
  return this.usePreinstalledWDA || !!this.args.webDriverAgentUrl;
113
137
  }
138
+ /**
139
+ * Get the xcodebuild instance. Throws if not initialized.
140
+ * @returns The XcodeBuild instance
141
+ * @throws Error if xcodebuild is not available
142
+ */
143
+ get xcodebuild() {
144
+ if (!this._xcodebuild) {
145
+ throw new Error('xcodebuild is not available');
146
+ }
147
+ return this._xcodebuild;
148
+ }
114
149
  /**
115
150
  * Return bundle id for WebDriverAgent to launch the WDA.
116
151
  * The primary usage is with 'this.usePreinstalledWDA'.
117
152
  * It adds `.xctrunner` as suffix by default but 'this.updatedWDABundleIdSuffix'
118
153
  * lets skip it.
119
154
  *
120
- * @returns {string} Bundle ID for Xctest.
155
+ * @returns Bundle ID for Xctest.
121
156
  */
122
157
  get bundleIdForXctest() {
123
158
  return `${this.updatedWDABundleId ? this.updatedWDABundleId : constants_1.WDA_RUNNER_BUNDLE_ID}${this.updatedWDABundleIdSuffix}`;
124
159
  }
125
160
  /**
126
- * @param {string} [bootstrapPath]
127
- * @param {string} [agentPath]
128
- */
129
- setWDAPaths(bootstrapPath, agentPath) {
130
- // allow the user to specify a place for WDA. This is undocumented and
131
- // only here for the purposes of testing development of WDA
132
- this.bootstrapPath = bootstrapPath || utils_1.BOOTSTRAP_PATH;
133
- this.log.info(`Using WDA path: '${this.bootstrapPath}'`);
134
- // for backward compatibility we need to be able to specify agentPath too
135
- this.agentPath = agentPath || path_1.default.resolve(this.bootstrapPath, 'WebDriverAgent.xcodeproj');
136
- this.log.info(`Using WDA agent: '${this.agentPath}'`);
137
- }
138
- /**
139
- * @returns {Promise<void>}
161
+ * Cleans up obsolete cached processes from previous WDA sessions
162
+ * that are listening on the same port but belong to different devices.
140
163
  */
141
164
  async cleanupObsoleteProcesses() {
142
- const obsoletePids = await (0, utils_1.getPIDsListeningOnPort)(/** @type {string} */ (this.url.port), (cmdLine) => cmdLine.includes('/WebDriverAgentRunner') &&
165
+ const obsoletePids = await (0, utils_1.getPIDsListeningOnPort)(this.url.port, (cmdLine) => cmdLine.includes('/WebDriverAgentRunner') &&
143
166
  !cmdLine.toLowerCase().includes(this.device.udid.toLowerCase()));
144
167
  if (lodash_1.default.isEmpty(obsoletePids)) {
145
168
  this.log.debug(`No obsolete cached processes from previous WDA sessions ` +
@@ -157,15 +180,8 @@ class WebDriverAgent {
157
180
  }
158
181
  }
159
182
  /**
160
- * Return boolean if WDA is running or not
161
- * @return {Promise<boolean>} True if WDA is running
162
- * @throws {Error} If there was invalid response code or body
163
- */
164
- async isRunning() {
165
- return !!(await this.getStatus());
166
- }
167
- /**
168
- * @returns {string}
183
+ * Gets the base path for the WebDriverAgent URL.
184
+ * @returns The base path (empty string if root path)
169
185
  */
170
186
  get basePath() {
171
187
  if (this.url.path === '/') {
@@ -173,230 +189,6 @@ class WebDriverAgent {
173
189
  }
174
190
  return this.url.path || '';
175
191
  }
176
- /**
177
- * Return current running WDA's status like below
178
- * {
179
- * "state": "success",
180
- * "os": {
181
- * "name": "iOS",
182
- * "version": "11.4",
183
- * "sdkVersion": "11.3"
184
- * },
185
- * "ios": {
186
- * "simulatorVersion": "11.4",
187
- * "ip": "172.254.99.34"
188
- * },
189
- * "build": {
190
- * "time": "Jun 24 2018 17:08:21",
191
- * "productBundleIdentifier": "com.facebook.WebDriverAgentRunner"
192
- * }
193
- * }
194
- *
195
- * @param {number} [timeoutMs=0] If the given timeoutMs is zero or negative number,
196
- * this function will return the response of `/status` immediately. If the given timeoutMs,
197
- * this function will try to get the response of `/status` up to the timeoutMs.
198
- * @return {Promise<import('@appium/types').StringRecord|null>} State Object
199
- * @throws {Error} If there was an error within timeoutMs timeout.
200
- * No error is raised if zero or negative number for the timeoutMs.
201
- */
202
- async getStatus(timeoutMs = 0) {
203
- const noSessionProxy = new no_session_proxy_1.NoSessionProxy({
204
- server: this.url.hostname,
205
- port: this.url.port,
206
- base: this.basePath,
207
- timeout: 3000,
208
- });
209
- const sendGetStatus = async () => await /** @type import('@appium/types').StringRecord */ (noSessionProxy.command('/status', 'GET'));
210
- if (lodash_1.default.isNil(timeoutMs) || timeoutMs <= 0) {
211
- try {
212
- return await sendGetStatus();
213
- }
214
- catch (err) {
215
- this.log.debug(`WDA is not listening at '${this.url.href}'. Original error:: ${err.message}`);
216
- return null;
217
- }
218
- }
219
- let lastError = null;
220
- let status = null;
221
- try {
222
- await (0, asyncbox_1.waitForCondition)(async () => {
223
- try {
224
- status = await sendGetStatus();
225
- return true;
226
- }
227
- catch (err) {
228
- lastError = err;
229
- }
230
- return false;
231
- }, {
232
- waitMs: timeoutMs,
233
- intervalMs: 300,
234
- });
235
- }
236
- catch (err) {
237
- this.log.debug(`Failed to get the status endpoint in ${timeoutMs} ms. ` +
238
- `The last error while accessing ${this.url.href}: ${lastError}. Original error:: ${err.message}.`);
239
- throw new Error(`WDA was not ready in ${timeoutMs} ms.`);
240
- }
241
- return status;
242
- }
243
- /**
244
- * Uninstall WDAs from the test device.
245
- * Over Xcode 11, multiple WDA can be in the device since Xcode 11 generates different WDA.
246
- * Appium does not expect multiple WDAs are running on a device.
247
- *
248
- * @returns {Promise<void>}
249
- */
250
- async uninstall() {
251
- try {
252
- const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
253
- if (lodash_1.default.isEmpty(bundleIds)) {
254
- this.log.debug('No WDAs on the device.');
255
- return;
256
- }
257
- this.log.debug(`Uninstalling WDAs: '${bundleIds}'`);
258
- for (const bundleId of bundleIds) {
259
- await this.device.removeApp(bundleId);
260
- }
261
- }
262
- catch (e) {
263
- this.log.debug(e);
264
- this.log.warn(`WebDriverAgent uninstall failed. Perhaps, it is already uninstalled? ` +
265
- `Original error: ${e.message}`);
266
- }
267
- }
268
- async _cleanupProjectIfFresh() {
269
- if (this.canSkipXcodebuild) {
270
- return;
271
- }
272
- const packageInfo = JSON.parse(await support_1.fs.readFile(path_1.default.join(utils_1.BOOTSTRAP_PATH, 'package.json'), 'utf8'));
273
- const box = (0, strongbox_1.strongbox)(packageInfo.name);
274
- let boxItem = box.getItem(RECENT_MODULE_VERSION_ITEM_NAME);
275
- if (!boxItem) {
276
- const timestampPath = path_1.default.resolve(process.env.HOME ?? '', constants_1.WDA_UPGRADE_TIMESTAMP_PATH);
277
- if (await support_1.fs.exists(timestampPath)) {
278
- // TODO: It is probably a bit ugly to hardcode the recent version string,
279
- // TODO: hovewer it should do the job as a temporary transition trick
280
- // TODO: to switch from a hardcoded file path to the strongbox usage.
281
- try {
282
- boxItem = await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, '5.0.0');
283
- }
284
- catch (e) {
285
- this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
286
- return;
287
- }
288
- }
289
- else {
290
- this.log.info('There is no need to perform the project cleanup. A fresh install has been detected');
291
- try {
292
- await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, packageInfo.version);
293
- }
294
- catch (e) {
295
- this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
296
- }
297
- return;
298
- }
299
- }
300
- let recentModuleVersion = await boxItem.read();
301
- try {
302
- recentModuleVersion = support_1.util.coerceVersion(recentModuleVersion, true);
303
- }
304
- catch (e) {
305
- this.log.warn(`The persisted module version string has been damaged: ${e.message}`);
306
- this.log.info(`Updating it to '${packageInfo.version}' assuming the project clenup is not needed`);
307
- await boxItem.write(packageInfo.version);
308
- return;
309
- }
310
- if (support_1.util.compareVersions(recentModuleVersion, '>=', packageInfo.version)) {
311
- this.log.info(`WebDriverAgent does not need a cleanup. The project sources are up to date ` +
312
- `(${recentModuleVersion} >= ${packageInfo.version})`);
313
- return;
314
- }
315
- this.log.info(`Cleaning up the WebDriverAgent project after the module upgrade has happened ` +
316
- `(${recentModuleVersion} < ${packageInfo.version})`);
317
- try {
318
- // @ts-ignore xcodebuild should be set
319
- await this.xcodebuild.cleanProject();
320
- await boxItem.write(packageInfo.version);
321
- }
322
- catch (e) {
323
- this.log.warn(`Cannot perform WebDriverAgent project cleanup. Original error: ${e.message}`);
324
- }
325
- }
326
- /**
327
- * @typedef {Object} LaunchWdaViaDeviceCtlOptions
328
- * @property {Record<string, string|number>} [env] environment variables for the launching WDA process
329
- */
330
- /**
331
- * Launch WDA with preinstalled package with 'xcrun devicectl device process launch'.
332
- * The WDA package must be prepared properly like published via
333
- * https://github.com/appium/WebDriverAgent/releases
334
- * with proper sign for this case.
335
- *
336
- * When we implement launching XCTest service via appium-ios-device,
337
- * this implementation can be replaced with it.
338
- *
339
- * @param {LaunchWdaViaDeviceCtlOptions} [opts={}] launching WDA with devicectl command options.
340
- * @return {Promise<void>}
341
- */
342
- async _launchViaDevicectl(opts = {}) {
343
- const { env } = opts;
344
- await this.device.devicectl.launchApp(this.bundleIdForXctest, { env, terminateExisting: true });
345
- }
346
- /**
347
- * Launch WDA with preinstalled package without xcodebuild.
348
- * @param {string} sessionId Launch WDA and establish the session with this sessionId
349
- * @return {Promise<import('@appium/types').StringRecord|null>} State Object
350
- * @throws {Error} If there was an error within timeoutMs timeout.
351
- * No error is raised if zero or negative number for the timeoutMs.
352
- */
353
- async launchWithPreinstalledWDA(sessionId) {
354
- const xctestEnv = {
355
- USE_PORT: this.wdaLocalPort || WDA_AGENT_PORT,
356
- WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest
357
- };
358
- if (this.mjpegServerPort) {
359
- xctestEnv.MJPEG_SERVER_PORT = this.mjpegServerPort;
360
- }
361
- if (this.wdaBindingIP) {
362
- xctestEnv.USE_IP = this.wdaBindingIP;
363
- }
364
- this.log.info('Launching WebDriverAgent on the device without xcodebuild');
365
- if (this.isRealDevice) {
366
- // Current method to launch WDA process can be done via 'xcrun devicectl',
367
- // but it has limitation about the WDA preinstalled package.
368
- // https://github.com/appium/appium/issues/19206#issuecomment-2014182674
369
- if (this.platformVersion && support_1.util.compareVersions(this.platformVersion, '>=', '17.0')) {
370
- await this._launchViaDevicectl({ env: xctestEnv });
371
- }
372
- else {
373
- this.xctestApiClient = new appium_ios_device_1.Xctest(this.device.udid, this.bundleIdForXctest, null, { env: xctestEnv });
374
- await this.xctestApiClient.start();
375
- }
376
- }
377
- else {
378
- await this.device.simctl.exec('launch', {
379
- args: [
380
- '--terminate-running-process',
381
- this.device.udid,
382
- this.bundleIdForXctest,
383
- ],
384
- env: xctestEnv,
385
- });
386
- }
387
- this.setupProxies(sessionId);
388
- let status;
389
- try {
390
- status = await this.getStatus(this.wdaLaunchTimeout);
391
- }
392
- catch {
393
- throw new Error(`Failed to start the preinstalled WebDriverAgent in ${this.wdaLaunchTimeout} ms. ` +
394
- `The WebDriverAgent might not be properly built or the device might be locked. ` +
395
- `The 'appium:wdaLaunchTimeout' capability modifies the timeout.`);
396
- }
397
- this.started = true;
398
- return status;
399
- }
400
192
  /**
401
193
  * Return current running WDA's status like below after launching WDA
402
194
  * {
@@ -416,9 +208,7 @@ class WebDriverAgent {
416
208
  * }
417
209
  * }
418
210
  *
419
- * @param {string} sessionId Launch WDA and establish the session with this sessionId
420
- * @return {Promise<any?>} State Object
421
- * @throws {Error} If there was invalid response code or body
211
+ * @param sessionId Launch WDA and establish the session with this sessionId
422
212
  */
423
213
  async launch(sessionId) {
424
214
  if (this.webDriverAgentUrl) {
@@ -438,79 +228,48 @@ class WebDriverAgent {
438
228
  }
439
229
  // useXctestrunFile and usePrebuiltWDA use existing dependencies
440
230
  // It depends on user side
441
- if (this.idb || this.useXctestrunFile || this.usePrebuiltWDA) {
231
+ if (this.useXctestrunFile || this.usePrebuiltWDA) {
442
232
  this.log.info('Skipped WDA project cleanup according to the provided capabilities');
443
233
  }
444
234
  else {
445
- const synchronizationKey = path_1.default.normalize(this.bootstrapPath);
235
+ const synchronizationKey = node_path_1.default.normalize(this.bootstrapPath);
446
236
  await SHARED_RESOURCES_GUARD.acquire(synchronizationKey, async () => await this._cleanupProjectIfFresh());
447
237
  }
448
238
  // We need to provide WDA local port, because it might be occupied
449
239
  await (0, utils_1.resetTestProcesses)(this.device.udid, !this.isRealDevice);
450
- if (this.idb) {
451
- return await this.startWithIDB();
240
+ if (!this.noSessionProxy) {
241
+ throw new Error('noSessionProxy is not available');
452
242
  }
453
- // @ts-ignore xcodebuild should be set
454
243
  await this.xcodebuild.init(this.noSessionProxy);
455
244
  // Start the xcodebuild process
456
245
  if (this.prebuildWDA) {
457
- // @ts-ignore xcodebuild should be set
458
246
  await this.xcodebuild.prebuild();
459
247
  }
460
- // @ts-ignore xcodebuild should be set
461
248
  return await this.xcodebuild.start();
462
249
  }
463
250
  /**
464
- * @deprecated We'll stop supporting idb. Deprecated for removal.
465
- * @returns {Promise<void>}
251
+ * Checks if the WebDriverAgent source is fresh by verifying
252
+ * that required resource files exist.
253
+ * @returns `true` if source is fresh (all required files exist), `false` otherwise
466
254
  */
467
- async startWithIDB() {
468
- this.log.info('Will launch WDA with idb instead of xcodebuild since the corresponding flag is enabled');
469
- const { wdaBundleId, testBundleId } = await this.prepareWDA();
470
- const env = {
471
- USE_PORT: this.wdaRemotePort,
472
- WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest,
473
- };
474
- if (this.mjpegServerPort) {
475
- env.MJPEG_SERVER_PORT = this.mjpegServerPort;
476
- }
477
- if (this.wdaBindingIP) {
478
- env.USE_IP = this.wdaBindingIP;
479
- }
480
- return await this.idb.runXCUITest(wdaBundleId, wdaBundleId, testBundleId, { env });
255
+ async isSourceFresh() {
256
+ const existsPromises = [
257
+ 'Resources',
258
+ `Resources${node_path_1.default.sep}WebDriverAgent.bundle`,
259
+ ].map((subPath) => support_1.fs.exists(node_path_1.default.resolve(this.bootstrapPath, subPath)));
260
+ return (await bluebird_1.default.all(existsPromises)).some((v) => v === false);
481
261
  }
482
- /**
483
- *
484
- * @param {string} wdaBundlePath
485
- * @returns {Promise<string>}
486
- */
487
262
  async parseBundleId(wdaBundlePath) {
488
- const infoPlistPath = path_1.default.join(wdaBundlePath, 'Info.plist');
263
+ const infoPlistPath = node_path_1.default.join(wdaBundlePath, 'Info.plist');
489
264
  const infoPlist = await support_1.plist.parsePlist(await support_1.fs.readFile(infoPlistPath));
490
265
  if (!infoPlist.CFBundleIdentifier) {
491
266
  throw new Error(`Could not find bundle id in '${infoPlistPath}'`);
492
267
  }
493
268
  return infoPlist.CFBundleIdentifier;
494
269
  }
495
- /**
496
- * @deprecated We'll stop using idb
497
- * @returns {Promise<{wdaBundleId: string, testBundleId: string, wdaBundlePath: string}>}
498
- */
499
- async prepareWDA() {
500
- const wdaBundlePath = this.wdaBundlePath || await this.fetchWDABundle();
501
- const wdaBundleId = await this.parseBundleId(wdaBundlePath);
502
- if (!await this.device.isAppInstalled(wdaBundleId)) {
503
- await this.device.installApp(wdaBundlePath);
504
- }
505
- const testBundleId = await this.idb.installXCTestBundle(path_1.default.join(wdaBundlePath, 'PlugIns', 'WebDriverAgentRunner.xctest'));
506
- return { wdaBundleId, testBundleId, wdaBundlePath };
507
- }
508
- /**
509
- * @returns {Promise<string>}
510
- */
511
270
  async fetchWDABundle() {
512
271
  if (!this.derivedDataPath) {
513
- return await (0, check_dependencies_1.bundleWDASim)(/** @type {XcodeBuild} */ (this.xcodebuild));
272
+ return await (0, check_dependencies_1.bundleWDASim)(this.xcodebuild);
514
273
  }
515
274
  const wdaBundlePaths = await support_1.fs.glob(`${this.derivedDataPath}/**/*${constants_1.WDA_RUNNER_APP}/`, {
516
275
  absolute: true,
@@ -520,20 +279,6 @@ class WebDriverAgent {
520
279
  }
521
280
  return wdaBundlePaths[0];
522
281
  }
523
- /**
524
- * @returns {Promise<boolean>}
525
- */
526
- async isSourceFresh() {
527
- const existsPromises = [
528
- 'Resources',
529
- `Resources${path_1.default.sep}WebDriverAgent.bundle`,
530
- ].map((subPath) => support_1.fs.exists(path_1.default.resolve(/** @type {String} */ (this.bootstrapPath), subPath)));
531
- return (await bluebird_1.default.all(existsPromises)).some((v) => v === false);
532
- }
533
- /**
534
- * @param {string} sessionId
535
- * @returns {void}
536
- */
537
282
  setupProxies(sessionId) {
538
283
  const proxyOpts = {
539
284
  log: this.log,
@@ -553,7 +298,8 @@ class WebDriverAgent {
553
298
  this.noSessionProxy = new no_session_proxy_1.NoSessionProxy(proxyOpts);
554
299
  }
555
300
  /**
556
- * @returns {Promise<void>}
301
+ * Stops the WebDriverAgent session and cleans up resources.
302
+ * Handles both preinstalled WDA and xcodebuild-based sessions.
557
303
  */
558
304
  async quit() {
559
305
  if (this.usePreinstalledWDA) {
@@ -573,8 +319,10 @@ class WebDriverAgent {
573
319
  }
574
320
  else if (!this.args.webDriverAgentUrl) {
575
321
  this.log.info('Shutting down sub-processes');
576
- await this.xcodebuild?.quit();
577
- await this.xcodebuild?.reset();
322
+ if (this._xcodebuild) {
323
+ await this.xcodebuild.quit();
324
+ await this.xcodebuild.reset();
325
+ }
578
326
  }
579
327
  else {
580
328
  this.log.debug('Do not stop xcodebuild nor XCTest session ' +
@@ -591,58 +339,62 @@ class WebDriverAgent {
591
339
  }
592
340
  }
593
341
  /**
594
- * @returns {import('url').UrlWithStringQuery}
342
+ * Gets the WebDriverAgent URL.
343
+ * Constructs the URL from webDriverAgentUrl if provided, otherwise
344
+ * builds it from wdaBaseUrl, wdaBindingIP, and wdaLocalPort.
345
+ * @returns The parsed URL object
595
346
  */
596
347
  get url() {
597
348
  if (!this._url) {
598
349
  if (this.webDriverAgentUrl) {
599
- this._url = url_1.default.parse(this.webDriverAgentUrl);
350
+ this._url = node_url_1.default.parse(this.webDriverAgentUrl);
600
351
  }
601
352
  else {
602
353
  const port = this.wdaLocalPort || WDA_AGENT_PORT;
603
- const { protocol, hostname } = url_1.default.parse(this.wdaBaseUrl || constants_1.WDA_BASE_URL);
604
- this._url = url_1.default.parse(`${protocol}//${this.wdaBindingIP || hostname}:${port}`);
354
+ const { protocol, hostname } = node_url_1.default.parse(this.wdaBaseUrl || constants_1.WDA_BASE_URL);
355
+ this._url = node_url_1.default.parse(`${protocol}//${this.wdaBindingIP || hostname}:${port}`);
605
356
  }
606
357
  }
607
358
  return this._url;
608
359
  }
609
360
  /**
610
- * @param {string} _url
611
- * @returns {void}
361
+ * Sets the WebDriverAgent URL.
362
+ * @param _url - The URL string to parse and set
612
363
  */
613
364
  set url(_url) {
614
- this._url = url_1.default.parse(_url);
365
+ this._url = node_url_1.default.parse(_url);
615
366
  }
616
367
  /**
617
- * @returns {boolean}
368
+ * Gets whether WebDriverAgent has fully started.
369
+ * @returns `true` if WDA has started, `false` otherwise
618
370
  */
619
371
  get fullyStarted() {
620
372
  return this.started;
621
373
  }
622
374
  /**
623
- * @param {boolean} started
624
- * @returns {void}s
375
+ * Sets whether WebDriverAgent has fully started.
376
+ * @param started - `true` if WDA has started, `false` otherwise
625
377
  */
626
378
  set fullyStarted(started) {
627
379
  this.started = started ?? false;
628
380
  }
629
381
  /**
630
- * @returns {Promise<string|undefined>}
382
+ * Retrieves the Xcode derived data path for WebDriverAgent.
383
+ * @returns The derived data path, or `undefined` if xcodebuild is skipped
631
384
  */
632
385
  async retrieveDerivedDataPath() {
633
386
  if (this.canSkipXcodebuild) {
634
387
  return;
635
388
  }
636
- return await /** @type {XcodeBuild} */ (this.xcodebuild).retrieveDerivedDataPath();
389
+ return await this.xcodebuild.retrieveDerivedDataPath();
637
390
  }
638
391
  /**
639
392
  * Reuse running WDA if it has the same bundle id with updatedWDABundleId.
640
393
  * Or reuse it if it has the default id without updatedWDABundleId.
641
394
  * Uninstall it if the method faces an exception for the above situation.
642
- * @returns {Promise<void>}
643
395
  */
644
396
  async setupCaching() {
645
- const status = await this.getStatus();
397
+ const status = await this.getStatus(0);
646
398
  if (!status || !status.build) {
647
399
  this.log.debug('WDA is currently not running. There is nothing to cache');
648
400
  return;
@@ -674,13 +426,231 @@ class WebDriverAgent {
674
426
  }
675
427
  /**
676
428
  * Quit and uninstall running WDA.
677
- * @returns {Promise<void>}
678
429
  */
679
430
  async quitAndUninstall() {
680
431
  await this.quit();
681
432
  await this.uninstall();
682
433
  }
434
+ setWDAPaths(bootstrapPath, agentPath) {
435
+ // allow the user to specify a place for WDA. This is undocumented and
436
+ // only here for the purposes of testing development of WDA
437
+ this.bootstrapPath = bootstrapPath || utils_1.BOOTSTRAP_PATH;
438
+ this.log.info(`Using WDA path: '${this.bootstrapPath}'`);
439
+ // for backward compatibility we need to be able to specify agentPath too
440
+ this.agentPath = agentPath || node_path_1.default.resolve(this.bootstrapPath, 'WebDriverAgent.xcodeproj');
441
+ this.log.info(`Using WDA agent: '${this.agentPath}'`);
442
+ }
443
+ async isRunning() {
444
+ return !!(await this.getStatus());
445
+ }
446
+ /**
447
+ * Return current running WDA's status like below
448
+ * {
449
+ * "state": "success",
450
+ * "os": {
451
+ * "name": "iOS",
452
+ * "version": "11.4",
453
+ * "sdkVersion": "11.3"
454
+ * },
455
+ * "ios": {
456
+ * "simulatorVersion": "11.4",
457
+ * "ip": "172.254.99.34"
458
+ * },
459
+ * "build": {
460
+ * "time": "Jun 24 2018 17:08:21",
461
+ * "productBundleIdentifier": "com.facebook.WebDriverAgentRunner"
462
+ * }
463
+ * }
464
+ *
465
+ * @param timeoutMs If zero or negative, returns immediately. Otherwise, waits up to timeoutMs.
466
+ */
467
+ async getStatus(timeoutMs = 0) {
468
+ const noSessionProxy = new no_session_proxy_1.NoSessionProxy({
469
+ server: this.url.hostname ?? undefined,
470
+ port: parseInt(this.url.port ?? '', 10) || undefined,
471
+ base: this.basePath,
472
+ timeout: 3000,
473
+ });
474
+ const sendGetStatus = async () => await noSessionProxy.command('/status', 'GET');
475
+ if (lodash_1.default.isNil(timeoutMs) || timeoutMs <= 0) {
476
+ try {
477
+ return await sendGetStatus();
478
+ }
479
+ catch (err) {
480
+ this.log.debug(`WDA is not listening at '${this.url.href}'. Original error:: ${err.message}`);
481
+ return null;
482
+ }
483
+ }
484
+ let lastError = null;
485
+ let status = null;
486
+ try {
487
+ await (0, asyncbox_1.waitForCondition)(async () => {
488
+ try {
489
+ status = await sendGetStatus();
490
+ return true;
491
+ }
492
+ catch (err) {
493
+ lastError = err;
494
+ }
495
+ return false;
496
+ }, {
497
+ waitMs: timeoutMs,
498
+ intervalMs: 300,
499
+ });
500
+ }
501
+ catch (err) {
502
+ this.log.debug(`Failed to get the status endpoint in ${timeoutMs} ms. ` +
503
+ `The last error while accessing ${this.url.href}: ${lastError}. Original error:: ${err.message}.`);
504
+ throw new Error(`WDA was not ready in ${timeoutMs} ms.`);
505
+ }
506
+ return status;
507
+ }
508
+ /**
509
+ * Uninstall WDAs from the test device.
510
+ * Over Xcode 11, multiple WDA can be in the device since Xcode 11 generates different WDA.
511
+ * Appium does not expect multiple WDAs are running on a device.
512
+ */
513
+ async uninstall() {
514
+ try {
515
+ const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
516
+ if (lodash_1.default.isEmpty(bundleIds)) {
517
+ this.log.debug('No WDAs on the device.');
518
+ return;
519
+ }
520
+ this.log.debug(`Uninstalling WDAs: '${bundleIds}'`);
521
+ for (const bundleId of bundleIds) {
522
+ await this.device.removeApp(bundleId);
523
+ }
524
+ }
525
+ catch (e) {
526
+ this.log.debug(e);
527
+ this.log.warn(`WebDriverAgent uninstall failed. Perhaps, it is already uninstalled? ` +
528
+ `Original error: ${e.message}`);
529
+ }
530
+ }
531
+ async _cleanupProjectIfFresh() {
532
+ if (this.canSkipXcodebuild) {
533
+ return;
534
+ }
535
+ const packageInfo = JSON.parse(await support_1.fs.readFile(node_path_1.default.join(utils_1.BOOTSTRAP_PATH, 'package.json'), 'utf8'));
536
+ const box = (0, strongbox_1.strongbox)(packageInfo.name);
537
+ let boxItem = box.getItem(RECENT_MODULE_VERSION_ITEM_NAME);
538
+ if (!boxItem) {
539
+ const timestampPath = node_path_1.default.resolve(process.env.HOME ?? '', constants_1.WDA_UPGRADE_TIMESTAMP_PATH);
540
+ if (await support_1.fs.exists(timestampPath)) {
541
+ // TODO: It is probably a bit ugly to hardcode the recent version string,
542
+ // TODO: hovewer it should do the job as a temporary transition trick
543
+ // TODO: to switch from a hardcoded file path to the strongbox usage.
544
+ try {
545
+ boxItem = await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, '5.0.0');
546
+ }
547
+ catch (e) {
548
+ this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
549
+ return;
550
+ }
551
+ }
552
+ else {
553
+ this.log.info('There is no need to perform the project cleanup. A fresh install has been detected');
554
+ try {
555
+ await box.createItemWithValue(RECENT_MODULE_VERSION_ITEM_NAME, packageInfo.version);
556
+ }
557
+ catch (e) {
558
+ this.log.warn(`The actual module version cannot be persisted: ${e.message}`);
559
+ }
560
+ return;
561
+ }
562
+ }
563
+ let recentModuleVersion = await boxItem.read();
564
+ try {
565
+ recentModuleVersion = support_1.util.coerceVersion(recentModuleVersion, true);
566
+ }
567
+ catch (e) {
568
+ this.log.warn(`The persisted module version string has been damaged: ${e.message}`);
569
+ this.log.info(`Updating it to '${packageInfo.version}' assuming the project clenup is not needed`);
570
+ await boxItem.write(packageInfo.version);
571
+ return;
572
+ }
573
+ if (support_1.util.compareVersions(recentModuleVersion, '>=', packageInfo.version)) {
574
+ this.log.info(`WebDriverAgent does not need a cleanup. The project sources are up to date ` +
575
+ `(${recentModuleVersion} >= ${packageInfo.version})`);
576
+ return;
577
+ }
578
+ this.log.info(`Cleaning up the WebDriverAgent project after the module upgrade has happened ` +
579
+ `(${recentModuleVersion} < ${packageInfo.version})`);
580
+ try {
581
+ await this.xcodebuild.cleanProject();
582
+ await boxItem.write(packageInfo.version);
583
+ }
584
+ catch (e) {
585
+ this.log.warn(`Cannot perform WebDriverAgent project cleanup. Original error: ${e.message}`);
586
+ }
587
+ }
588
+ /**
589
+ * Launch WDA with preinstalled package with 'xcrun devicectl device process launch'.
590
+ * The WDA package must be prepared properly like published via
591
+ * https://github.com/appium/WebDriverAgent/releases
592
+ * with proper sign for this case.
593
+ *
594
+ * When we implement launching XCTest service via appium-ios-device,
595
+ * this implementation can be replaced with it.
596
+ *
597
+ * @param opts launching WDA with devicectl command options.
598
+ */
599
+ async _launchViaDevicectl(opts = {}) {
600
+ const { env } = opts;
601
+ await this.device.devicectl.launchApp(this.bundleIdForXctest, { env, terminateExisting: true });
602
+ }
603
+ /**
604
+ * Launch WDA with preinstalled package without xcodebuild.
605
+ * @param sessionId Launch WDA and establish the session with this sessionId
606
+ */
607
+ async launchWithPreinstalledWDA(sessionId) {
608
+ const xctestEnv = {
609
+ USE_PORT: this.wdaLocalPort || WDA_AGENT_PORT,
610
+ WDA_PRODUCT_BUNDLE_IDENTIFIER: this.bundleIdForXctest
611
+ };
612
+ if (this.mjpegServerPort) {
613
+ xctestEnv.MJPEG_SERVER_PORT = this.mjpegServerPort;
614
+ }
615
+ if (this.wdaBindingIP) {
616
+ xctestEnv.USE_IP = this.wdaBindingIP;
617
+ }
618
+ this.log.info('Launching WebDriverAgent on the device without xcodebuild');
619
+ if (this.isRealDevice) {
620
+ // Current method to launch WDA process can be done via 'xcrun devicectl',
621
+ // but it has limitation about the WDA preinstalled package.
622
+ // https://github.com/appium/appium/issues/19206#issuecomment-2014182674
623
+ if (this.platformVersion && support_1.util.compareVersions(this.platformVersion, '>=', '17.0')) {
624
+ await this._launchViaDevicectl({ env: xctestEnv });
625
+ }
626
+ else {
627
+ this.xctestApiClient = new appium_ios_device_1.Xctest(this.device.udid, this.bundleIdForXctest, null, { env: xctestEnv });
628
+ await this.xctestApiClient.start();
629
+ }
630
+ }
631
+ else {
632
+ await this.device.simctl.exec('launch', {
633
+ args: [
634
+ '--terminate-running-process',
635
+ this.device.udid,
636
+ this.bundleIdForXctest,
637
+ ],
638
+ env: xctestEnv,
639
+ });
640
+ }
641
+ this.setupProxies(sessionId);
642
+ let status;
643
+ try {
644
+ status = await this.getStatus(this.wdaLaunchTimeout);
645
+ }
646
+ catch {
647
+ throw new Error(`Failed to start the preinstalled WebDriverAgent in ${this.wdaLaunchTimeout} ms. ` +
648
+ `The WebDriverAgent might not be properly built or the device might be locked. ` +
649
+ `The 'appium:wdaLaunchTimeout' capability modifies the timeout.`);
650
+ }
651
+ this.started = true;
652
+ return status;
653
+ }
683
654
  }
684
655
  exports.WebDriverAgent = WebDriverAgent;
685
- exports.default = WebDriverAgent;
686
656
  //# sourceMappingURL=webdriveragent.js.map