appium-webdriveragent 12.1.0 → 12.2.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.
@@ -1,8 +1,5 @@
1
1
  import {waitForCondition} from 'asyncbox';
2
- import _ from 'lodash';
3
2
  import path from 'node:path';
4
- import url from 'node:url';
5
- import B from 'bluebird';
6
3
  import {JWProxy} from '@appium/base-driver';
7
4
  import {fs, util, plist} from '@appium/support';
8
5
  import type {AppiumLogger, StringRecord} from '@appium/types';
@@ -34,41 +31,42 @@ const WDA_AGENT_PORT = 8100;
34
31
  const WDA_CF_BUNDLE_NAME = 'WebDriverAgentRunner-Runner';
35
32
  const SHARED_RESOURCES_GUARD = new AsyncLock();
36
33
  const RECENT_MODULE_VERSION_ITEM_NAME = 'recentWdaModuleVersion';
34
+ const URL_PROTOCOL_SEPARATOR = '://';
37
35
 
38
36
  export class WebDriverAgent {
39
37
  bootstrapPath: string;
40
38
  agentPath: string;
41
39
  readonly args: WebDriverAgentArgs;
42
- private readonly log: AppiumLogger;
43
40
  readonly device: AppleDevice;
44
41
  readonly platformVersion?: string;
45
42
  readonly platformName?: string;
46
43
  readonly iosSdkVersion?: string;
47
44
  readonly host?: string;
48
45
  readonly isRealDevice: boolean;
49
- private readonly wdaBundlePath?: string;
50
- private readonly wdaLocalPort?: number;
51
46
  readonly wdaRemotePort: number;
52
47
  readonly wdaBaseUrl: string;
53
48
  readonly wdaBindingIP?: string;
54
- private readonly prebuildWDA?: boolean;
55
49
  webDriverAgentUrl?: string;
56
50
  started: boolean;
51
+ updatedWDABundleId?: string;
52
+ noSessionProxy?: NoSessionProxy;
53
+ jwproxy?: JWProxy;
54
+ proxyReqRes?: any;
55
+ private readonly log: AppiumLogger;
56
+ private readonly wdaBundlePath?: string;
57
+ private readonly wdaLocalPort?: number;
58
+ private readonly prebuildWDA?: boolean;
57
59
  private readonly wdaConnectionTimeout?: number;
58
60
  private readonly useXctestrunFile?: boolean;
59
61
  private readonly usePrebuiltWDA?: boolean;
60
62
  private readonly derivedDataPath?: string;
61
63
  private readonly mjpegServerPort?: number;
62
- updatedWDABundleId?: string;
63
64
  private readonly wdaLaunchTimeout: number;
64
65
  private readonly usePreinstalledWDA?: boolean;
65
- private xctestApiClient?: Xctest | null;
66
66
  private readonly updatedWDABundleIdSuffix: string;
67
+ private xctestApiClient?: Xctest | null;
67
68
  private _xcodebuild?: XcodeBuild | null;
68
- noSessionProxy?: NoSessionProxy;
69
- jwproxy?: JWProxy;
70
- proxyReqRes?: any;
71
- private _url?: url.UrlWithStringQuery;
69
+ private _url?: URL;
72
70
 
73
71
  /**
74
72
  * Creates a new WebDriverAgent instance.
@@ -76,7 +74,7 @@ export class WebDriverAgent {
76
74
  * @param log - Optional logger instance
77
75
  */
78
76
  constructor(args: WebDriverAgentArgs, log: AppiumLogger | null = null) {
79
- this.args = _.clone(args);
77
+ this.args = {...args};
80
78
  this.log = log ?? defaultLogger;
81
79
 
82
80
  this.device = args.device;
@@ -185,6 +183,62 @@ export class WebDriverAgent {
185
183
  return `${this.updatedWDABundleId ? this.updatedWDABundleId : WDA_RUNNER_BUNDLE_ID}${this.updatedWDABundleIdSuffix}`;
186
184
  }
187
185
 
186
+ /**
187
+ * Gets the base path for the WebDriverAgent URL.
188
+ * @returns The base path (empty string if root path)
189
+ */
190
+ get basePath(): string {
191
+ if (this.url.pathname === '/') {
192
+ return '';
193
+ }
194
+ return this.url.pathname || '';
195
+ }
196
+
197
+ /**
198
+ * Gets the WebDriverAgent URL.
199
+ * Constructs the URL from webDriverAgentUrl if provided, otherwise
200
+ * builds it from wdaBaseUrl, wdaBindingIP, and wdaLocalPort.
201
+ * @returns The parsed URL object
202
+ */
203
+ get url(): URL {
204
+ if (!this._url) {
205
+ if (this.webDriverAgentUrl) {
206
+ this._url = this.toUrl(this.webDriverAgentUrl);
207
+ } else {
208
+ const port = this.wdaLocalPort || WDA_AGENT_PORT;
209
+ const parsedBaseUrl = this.toUrl(this.wdaBaseUrl || WDA_BASE_URL);
210
+ this._url = new URL(
211
+ `${parsedBaseUrl.protocol}//${this.wdaBindingIP || parsedBaseUrl.hostname}:${port}`,
212
+ );
213
+ }
214
+ }
215
+ return this._url;
216
+ }
217
+
218
+ /**
219
+ * Gets whether WebDriverAgent has fully started.
220
+ * @returns `true` if WDA has started, `false` otherwise
221
+ */
222
+ get fullyStarted(): boolean {
223
+ return this.started;
224
+ }
225
+
226
+ /**
227
+ * Sets whether WebDriverAgent has fully started.
228
+ * @param started - `true` if WDA has started, `false` otherwise
229
+ */
230
+ set fullyStarted(started: boolean) {
231
+ this.started = started ?? false;
232
+ }
233
+
234
+ /**
235
+ * Sets the WebDriverAgent URL.
236
+ * @param _url - The URL string to parse and set
237
+ */
238
+ set url(_url: string) {
239
+ this._url = this.toUrl(_url);
240
+ }
241
+
188
242
  /**
189
243
  * Cleans up obsolete cached processes from previous WDA sessions
190
244
  * that are listening on the same port but belong to different devices.
@@ -197,7 +251,7 @@ export class WebDriverAgent {
197
251
  !cmdLine.toLowerCase().includes(this.device.udid.toLowerCase()),
198
252
  );
199
253
 
200
- if (_.isEmpty(obsoletePids)) {
254
+ if (obsoletePids.length === 0) {
201
255
  this.log.debug(
202
256
  `No obsolete cached processes from previous WDA sessions ` +
203
257
  `listening on port ${this.url.port} have been found`,
@@ -220,14 +274,6 @@ export class WebDriverAgent {
220
274
  }
221
275
 
222
276
  /**
223
- * Gets the base path for the WebDriverAgent URL.
224
- * @returns The base path (empty string if root path)
225
- */
226
- get basePath(): string {
227
- if (this.url.path === '/') {
228
- return '';
229
- }
230
- return this.url.path || '';
231
277
  }
232
278
 
233
279
  /**
@@ -307,56 +353,10 @@ export class WebDriverAgent {
307
353
  * @returns `true` if source is fresh (all required files exist), `false` otherwise
308
354
  */
309
355
  async isSourceFresh(): Promise<boolean> {
310
- const existsPromises = ['Resources', `Resources${path.sep}WebDriverAgent.bundle`].map(
356
+ const existsPromises = ['Resources', path.join('Resources', 'WebDriverAgent.bundle')].map(
311
357
  (subPath) => fs.exists(path.resolve(this.bootstrapPath, subPath)),
312
358
  );
313
- return (await B.all(existsPromises)).some((v) => v === false);
314
- }
315
-
316
- private async parseBundleId(wdaBundlePath: string): Promise<string> {
317
- const infoPlistPath = path.join(wdaBundlePath, 'Info.plist');
318
- const infoPlist = (await plist.parsePlist(await fs.readFile(infoPlistPath))) as {
319
- CFBundleIdentifier?: string;
320
- };
321
- if (!infoPlist.CFBundleIdentifier) {
322
- throw new Error(`Could not find bundle id in '${infoPlistPath}'`);
323
- }
324
- return infoPlist.CFBundleIdentifier;
325
- }
326
-
327
- private async fetchWDABundle(): Promise<string> {
328
- if (!this.derivedDataPath) {
329
- return await bundleWDASim(this.xcodebuild);
330
- }
331
- const wdaBundlePaths = await fs.glob(`${this.derivedDataPath}/**/*${WDA_RUNNER_APP}/`, {
332
- absolute: true,
333
- });
334
- if (_.isEmpty(wdaBundlePaths)) {
335
- throw new Error(`Could not find the WDA bundle in '${this.derivedDataPath}'`);
336
- }
337
- return wdaBundlePaths[0];
338
- }
339
-
340
- private setupProxies(sessionId: string): void {
341
- const proxyOpts: any = {
342
- log: this.log,
343
- server: this.url.hostname ?? undefined,
344
- port: parseInt(this.url.port ?? '', 10) || undefined,
345
- base: this.basePath,
346
- timeout: this.wdaConnectionTimeout,
347
- keepAlive: true,
348
- scheme: this.url.protocol ? this.url.protocol.replace(':', '') : 'http',
349
- headers: this.args.extraRequestHeaders,
350
- };
351
- if (this.args.reqBasePath) {
352
- proxyOpts.reqBasePath = this.args.reqBasePath;
353
- }
354
-
355
- this.jwproxy = new JWProxy(proxyOpts);
356
- this.jwproxy.sessionId = sessionId;
357
- this.proxyReqRes = this.jwproxy.proxyReqRes.bind(this.jwproxy);
358
-
359
- this.noSessionProxy = new NoSessionProxy(proxyOpts);
359
+ return (await Promise.all(existsPromises)).every((v) => v === true);
360
360
  }
361
361
 
362
362
  /**
@@ -401,49 +401,6 @@ export class WebDriverAgent {
401
401
  }
402
402
  }
403
403
 
404
- /**
405
- * Gets the WebDriverAgent URL.
406
- * Constructs the URL from webDriverAgentUrl if provided, otherwise
407
- * builds it from wdaBaseUrl, wdaBindingIP, and wdaLocalPort.
408
- * @returns The parsed URL object
409
- */
410
- get url(): url.UrlWithStringQuery {
411
- if (!this._url) {
412
- if (this.webDriverAgentUrl) {
413
- this._url = url.parse(this.webDriverAgentUrl);
414
- } else {
415
- const port = this.wdaLocalPort || WDA_AGENT_PORT;
416
- const {protocol, hostname} = url.parse(this.wdaBaseUrl || WDA_BASE_URL);
417
- this._url = url.parse(`${protocol}//${this.wdaBindingIP || hostname}:${port}`);
418
- }
419
- }
420
- return this._url;
421
- }
422
-
423
- /**
424
- * Sets the WebDriverAgent URL.
425
- * @param _url - The URL string to parse and set
426
- */
427
- set url(_url: string) {
428
- this._url = url.parse(_url);
429
- }
430
-
431
- /**
432
- * Gets whether WebDriverAgent has fully started.
433
- * @returns `true` if WDA has started, `false` otherwise
434
- */
435
- get fullyStarted(): boolean {
436
- return this.started;
437
- }
438
-
439
- /**
440
- * Sets whether WebDriverAgent has fully started.
441
- * @param started - `true` if WDA has started, `false` otherwise
442
- */
443
- set fullyStarted(started: boolean) {
444
- this.started = started ?? false;
445
- }
446
-
447
404
  /**
448
405
  * Retrieves the Xcode derived data path for WebDriverAgent.
449
406
  * @returns The derived data path, or `undefined` if xcodebuild is skipped
@@ -497,7 +454,7 @@ export class WebDriverAgent {
497
454
  if (
498
455
  actualUpgradeTimestamp &&
499
456
  upgradedAt &&
500
- _.toLower(`${actualUpgradeTimestamp}`) !== _.toLower(`${upgradedAt}`)
457
+ `${actualUpgradeTimestamp}`.toLowerCase() !== `${upgradedAt}`.toLowerCase()
501
458
  ) {
502
459
  this.log.info(
503
460
  'Will uninstall running WDA since it has different version in comparison to the one ' +
@@ -523,6 +480,64 @@ export class WebDriverAgent {
523
480
  await this.uninstall();
524
481
  }
525
482
 
483
+ private async parseBundleId(wdaBundlePath: string): Promise<string> {
484
+ const infoPlistPath = path.join(wdaBundlePath, 'Info.plist');
485
+ const infoPlist = (await plist.parsePlist(await fs.readFile(infoPlistPath))) as {
486
+ CFBundleIdentifier?: string;
487
+ };
488
+ if (!infoPlist.CFBundleIdentifier) {
489
+ throw new Error(`Could not find bundle id in '${infoPlistPath}'`);
490
+ }
491
+ return infoPlist.CFBundleIdentifier;
492
+ }
493
+
494
+ private async fetchWDABundle(): Promise<string> {
495
+ if (!this.derivedDataPath) {
496
+ return await bundleWDASim(this.xcodebuild);
497
+ }
498
+ const wdaBundlePaths = await fs.glob(`${this.derivedDataPath}/**/*${WDA_RUNNER_APP}/`, {
499
+ absolute: true,
500
+ });
501
+ if (wdaBundlePaths.length === 0) {
502
+ throw new Error(`Could not find the WDA bundle in '${this.derivedDataPath}'`);
503
+ }
504
+ return wdaBundlePaths[0];
505
+ }
506
+
507
+ private setupProxies(sessionId: string): void {
508
+ const proxyOpts: any = {
509
+ log: this.log,
510
+ server: this.url.hostname ?? undefined,
511
+ port: parseInt(this.url.port ?? '', 10) || undefined,
512
+ base: this.basePath,
513
+ timeout: this.wdaConnectionTimeout,
514
+ keepAlive: true,
515
+ scheme: this.url.protocol ? this.url.protocol.replace(':', '') : 'http',
516
+ headers: this.args.extraRequestHeaders,
517
+ };
518
+ if (this.args.reqBasePath) {
519
+ proxyOpts.reqBasePath = this.args.reqBasePath;
520
+ }
521
+
522
+ this.jwproxy = new JWProxy(proxyOpts);
523
+ this.jwproxy.sessionId = sessionId;
524
+ this.proxyReqRes = this.jwproxy.proxyReqRes.bind(this.jwproxy);
525
+
526
+ this.noSessionProxy = new NoSessionProxy(proxyOpts);
527
+ }
528
+
529
+ private toUrl(value: string): URL {
530
+ // Treat values without `://` as host/path inputs and normalize to http.
531
+ if (!value.includes(URL_PROTOCOL_SEPARATOR)) {
532
+ return new URL(`http://${value}`);
533
+ }
534
+ try {
535
+ return new URL(value);
536
+ } catch {
537
+ throw new Error(`Invalid URL: ${value}`);
538
+ }
539
+ }
540
+
526
541
  private setWDAPaths(bootstrapPath?: string, agentPath?: string): void {
527
542
  // allow the user to specify a place for WDA. This is undocumented and
528
543
  // only here for the purposes of testing development of WDA
@@ -572,7 +587,7 @@ export class WebDriverAgent {
572
587
  const sendGetStatus = async () =>
573
588
  (await noSessionProxy.command('/status', 'GET')) as StringRecord;
574
589
 
575
- if (_.isNil(timeoutMs) || timeoutMs <= 0) {
590
+ if (timeoutMs == null || timeoutMs <= 0) {
576
591
  try {
577
592
  return await sendGetStatus();
578
593
  } catch (err: any) {
@@ -619,7 +634,7 @@ export class WebDriverAgent {
619
634
  private async uninstall(): Promise<void> {
620
635
  try {
621
636
  const bundleIds = await this.device.getUserInstalledBundleIdsByBundleName(WDA_CF_BUNDLE_NAME);
622
- if (_.isEmpty(bundleIds)) {
637
+ if (bundleIds.length === 0) {
623
638
  this.log.debug('No WDAs on the device.');
624
639
  return;
625
640
  }
package/lib/xcodebuild.ts CHANGED
@@ -3,15 +3,15 @@ import {SubProcess, exec} from 'teen_process';
3
3
  import {logger, timing} from '@appium/support';
4
4
  import type {AppiumLogger, StringRecord} from '@appium/types';
5
5
  import {log as defaultLogger} from './logger';
6
- import B from 'bluebird';
7
6
  import {
8
7
  setRealDeviceSecurity,
9
8
  setXctestrunFile,
10
9
  killProcess,
11
10
  getWDAUpgradeTimestamp,
12
11
  isTvOS,
12
+ escapeRegExp,
13
+ truncateString,
13
14
  } from './utils';
14
- import _ from 'lodash';
15
15
  import path from 'node:path';
16
16
  import {WDA_RUNNER_BUNDLE_ID} from './constants';
17
17
  import type {AppleDevice, XcodeBuildArgs} from './types';
@@ -30,7 +30,7 @@ const IGNORED_ERRORS = [
30
30
  'Failed to remove screenshot at path',
31
31
  ];
32
32
  const IGNORED_ERRORS_PATTERN = new RegExp(
33
- '(' + IGNORED_ERRORS.map((errStr) => _.escapeRegExp(errStr)).join('|') + ')',
33
+ '(' + IGNORED_ERRORS.map((errStr) => escapeRegExp(errStr)).join('|') + ')',
34
34
  );
35
35
 
36
36
  const RUNNER_SCHEME_TV = 'WebDriverAgentRunner_tvOS';
@@ -44,27 +44,28 @@ const xcodeLog = logger.getLogger('Xcode');
44
44
  export class XcodeBuild {
45
45
  xcodebuild?: SubProcess;
46
46
  readonly device: AppleDevice;
47
- private readonly log: AppiumLogger;
48
47
  readonly realDevice: boolean;
49
48
  readonly agentPath: string;
50
49
  readonly bootstrapPath: string;
51
50
  readonly platformVersion?: string;
52
51
  readonly platformName?: string;
53
52
  readonly iosSdkVersion?: string;
53
+ readonly xcodeSigningId: string;
54
+ usePrebuiltWDA?: boolean;
55
+ derivedDataPath?: string;
56
+ agentUrl?: string;
57
+ private readonly log: AppiumLogger;
54
58
  private readonly showXcodeLog?: boolean;
55
59
  private readonly xcodeConfigFile?: string;
56
60
  private readonly xcodeOrgId?: string;
57
- readonly xcodeSigningId: string;
58
61
  private readonly keychainPath?: string;
59
62
  private readonly keychainPassword?: string;
60
- usePrebuiltWDA?: boolean;
61
63
  private readonly useSimpleBuildTest?: boolean;
62
64
  private readonly useXctestrunFile?: boolean;
63
65
  private readonly launchTimeout?: number;
64
66
  private readonly wdaRemotePort?: number;
65
67
  private readonly wdaBindingIP?: string;
66
68
  private readonly updatedWDABundleId?: string;
67
- derivedDataPath?: string;
68
69
  private readonly mjpegServerPort?: number;
69
70
  private readonly prebuildDelay: number;
70
71
  private readonly allowProvisioningDeviceRegistration?: boolean;
@@ -75,7 +76,6 @@ export class XcodeBuild {
75
76
  private _derivedDataPathPromise?: Promise<string | undefined>;
76
77
  private noSessionProxy?: NoSessionProxy;
77
78
  private xctestrunFilePath?: string;
78
- agentUrl?: string;
79
79
 
80
80
  /**
81
81
  * Creates a new XcodeBuild instance.
@@ -119,7 +119,8 @@ export class XcodeBuild {
119
119
 
120
120
  this.mjpegServerPort = args.mjpegServerPort;
121
121
 
122
- this.prebuildDelay = _.isNumber(args.prebuildDelay) ? args.prebuildDelay : PREBUILD_DELAY;
122
+ this.prebuildDelay =
123
+ typeof args.prebuildDelay === 'number' ? args.prebuildDelay : PREBUILD_DELAY;
123
124
 
124
125
  this.allowProvisioningDeviceRegistration = args.allowProvisioningDeviceRegistration;
125
126
 
@@ -183,7 +184,7 @@ export class XcodeBuild {
183
184
  const pattern = /^\s*BUILD_DIR\s+=\s+(\/.*)/m;
184
185
  const match = pattern.exec(stdout);
185
186
  if (!match) {
186
- this.log.warn(`Cannot parse WDA build dir from ${_.truncate(stdout, {length: 300})}`);
187
+ this.log.warn(`Cannot parse WDA build dir from ${truncateString(stdout, 300)}`);
187
188
  return;
188
189
  }
189
190
  this.log.debug(`Parsed BUILD_DIR configuration value: '${match[1]}'`);
@@ -207,7 +208,7 @@ export class XcodeBuild {
207
208
 
208
209
  if (this.prebuildDelay > 0) {
209
210
  // pause a moment
210
- await B.delay(this.prebuildDelay);
211
+ await new Promise((resolve) => setTimeout(resolve, this.prebuildDelay));
211
212
  }
212
213
  }
213
214
 
@@ -242,7 +243,7 @@ export class XcodeBuild {
242
243
  throw new Error('xcodebuild subprocess was not created');
243
244
  }
244
245
  const xcodebuild = this.xcodebuild;
245
- return await new B((resolve, reject) => {
246
+ return await new Promise<StringRecord | void>((resolve, reject) => {
246
247
  xcodebuild.once('exit', (code, signal) => {
247
248
  xcodeLog.error(`xcodebuild exited with code '${code}' and signal '${signal}'`);
248
249
  xcodebuild.removeAllListeners();
@@ -415,9 +416,10 @@ export class XcodeBuild {
415
416
  });
416
417
 
417
418
  let logXcodeOutput = !!this.showXcodeLog;
418
- const logMsg = _.isBoolean(this.showXcodeLog)
419
- ? `Output from xcodebuild ${this.showXcodeLog ? 'will' : 'will not'} be logged`
420
- : 'Output from xcodebuild will only be logged if any errors are present there';
419
+ const logMsg =
420
+ typeof this.showXcodeLog === 'boolean'
421
+ ? `Output from xcodebuild ${this.showXcodeLog ? 'will' : 'will not'} be logged`
422
+ : 'Output from xcodebuild will only be logged if any errors are present there';
421
423
  this.log.debug(`${logMsg}. To change this, use 'showXcodeLog' desired capability`);
422
424
 
423
425
  const onStreamLine = (line: string) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "main": "./build/lib/index.js",
6
6
  "types": "./build/lib/index.d.ts",
@@ -54,16 +54,14 @@
54
54
  "@appium/types": "^1.0.0-rc.1",
55
55
  "@semantic-release/changelog": "^6.0.1",
56
56
  "@semantic-release/git": "^10.0.1",
57
- "@types/bluebird": "^3.5.38",
58
- "@types/lodash": "^4.14.196",
59
57
  "@types/mocha": "^10.0.1",
60
58
  "@types/node": "^25.0.0",
61
59
  "appium-xcode": "^6.0.0",
62
60
  "chai": "^6.0.0",
63
61
  "chai-as-promised": "^8.0.0",
64
62
  "conventional-changelog-conventionalcommits": "^9.0.0",
65
- "node-simctl": "^8.0.0",
66
63
  "mocha": "^11.0.1",
64
+ "node-simctl": "^8.0.0",
67
65
  "prettier": "^3.0.0",
68
66
  "semantic-release": "^25.0.2",
69
67
  "semver": "^7.3.7",
@@ -80,8 +78,6 @@
80
78
  "async-lock": "^1.0.0",
81
79
  "asyncbox": "^6.1.0",
82
80
  "axios": "^1.4.0",
83
- "bluebird": "^3.5.5",
84
- "lodash": "^4.17.11",
85
81
  "teen_process": "^4.0.7"
86
82
  },
87
83
  "files": [