appium-chromedriver 5.2.16 → 5.3.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 (39) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/build/index.d.ts +11 -0
  3. package/build/index.d.ts.map +1 -0
  4. package/build/index.js +18 -18
  5. package/build/index.js.map +1 -0
  6. package/build/lib/chromedriver.d.ts +108 -0
  7. package/build/lib/chromedriver.d.ts.map +1 -0
  8. package/build/lib/chromedriver.js +685 -559
  9. package/build/lib/chromedriver.js.map +1 -1
  10. package/build/lib/install.d.ts +3 -0
  11. package/build/lib/install.d.ts.map +1 -0
  12. package/build/lib/install.js +39 -32
  13. package/build/lib/install.js.map +1 -1
  14. package/build/lib/protocol-helpers.d.ts +15 -0
  15. package/build/lib/protocol-helpers.d.ts.map +1 -0
  16. package/build/lib/protocol-helpers.js +38 -21
  17. package/build/lib/protocol-helpers.js.map +1 -1
  18. package/build/lib/storage-client.d.ts +119 -0
  19. package/build/lib/storage-client.d.ts.map +1 -0
  20. package/build/lib/storage-client.js +393 -274
  21. package/build/lib/storage-client.js.map +1 -1
  22. package/build/lib/types.d.ts +101 -0
  23. package/build/lib/types.d.ts.map +1 -0
  24. package/build/lib/types.js +3 -0
  25. package/build/lib/types.js.map +1 -0
  26. package/build/lib/utils.d.ts +52 -0
  27. package/build/lib/utils.d.ts.map +1 -0
  28. package/build/lib/utils.js +117 -78
  29. package/build/lib/utils.js.map +1 -1
  30. package/config/mapping.json +1 -0
  31. package/index.js +9 -0
  32. package/lib/chromedriver.js +300 -162
  33. package/lib/install.js +8 -1
  34. package/lib/protocol-helpers.js +17 -1
  35. package/lib/storage-client.js +165 -129
  36. package/lib/types.ts +101 -0
  37. package/lib/utils.js +86 -42
  38. package/package.json +34 -30
  39. package/tsconfig.json +11 -0
@@ -1,22 +1,24 @@
1
- // transpile:main
2
-
3
1
  import events from 'events';
4
- import { JWProxy, PROTOCOLS } from '@appium/base-driver';
2
+ import {JWProxy, PROTOCOLS} from '@appium/base-driver';
5
3
  import cp from 'child_process';
6
- import { system, fs, logger, util } from '@appium/support';
7
- import { retryInterval, asyncmap } from 'asyncbox';
8
- import { SubProcess, exec } from 'teen_process';
4
+ import {system, fs, logger, util} from '@appium/support';
5
+ import {retryInterval, asyncmap} from 'asyncbox';
6
+ import {SubProcess, exec} from 'teen_process';
9
7
  import B from 'bluebird';
10
8
  import {
11
- getChromeVersion, getChromedriverDir, CHROMEDRIVER_CHROME_MAPPING,
12
- getChromedriverBinaryPath, CD_CDN, generateLogPrefix
9
+ getChromeVersion,
10
+ getChromedriverDir,
11
+ CHROMEDRIVER_CHROME_MAPPING,
12
+ getChromedriverBinaryPath,
13
+ CD_CDN,
14
+ generateLogPrefix,
13
15
  } from './utils';
14
16
  import semver from 'semver';
15
17
  import _ from 'lodash';
16
18
  import path from 'path';
17
- import { compareVersions } from 'compare-versions';
19
+ import {compareVersions} from 'compare-versions';
18
20
  import ChromedriverStorageClient from './storage-client';
19
- import { toW3cCapNames, getCapValue } from './protocol-helpers';
21
+ import {toW3cCapNames, getCapValue} from './protocol-helpers';
20
22
 
21
23
  const NEW_CD_VERSION_FORMAT_MAJOR_VERSION = 73;
22
24
  const DEFAULT_HOST = '127.0.0.1';
@@ -24,16 +26,17 @@ const MIN_CD_VERSION_WITH_W3C_SUPPORT = 75;
24
26
  const DEFAULT_PORT = 9515;
25
27
  const CHROME_BUNDLE_ID = 'com.android.chrome';
26
28
  const WEBVIEW_SHELL_BUNDLE_ID = 'org.chromium.webview_shell';
27
- const WEBVIEW_BUNDLE_IDS = [
28
- 'com.google.android.webview',
29
- 'com.android.webview',
30
- ];
29
+ const WEBVIEW_BUNDLE_IDS = ['com.google.android.webview', 'com.android.webview'];
31
30
  const VERSION_PATTERN = /([\d.]+)/;
32
31
 
33
32
  const CD_VERSION_TIMEOUT = 5000;
34
33
 
35
34
  class Chromedriver extends events.EventEmitter {
36
- constructor (args = {}) {
35
+ /**
36
+ *
37
+ * @param {import('./types').ChromedriverOpts} args
38
+ */
39
+ constructor(args = {}) {
37
40
  super();
38
41
 
39
42
  const {
@@ -75,28 +78,30 @@ class Chromedriver extends events.EventEmitter {
75
78
  this.logPath = logPath;
76
79
  this.disableBuildCheck = !!disableBuildCheck;
77
80
  this.storageClient = isAutodownloadEnabled
78
- ? new ChromedriverStorageClient({ chromedriverDir: this.executableDir })
81
+ ? new ChromedriverStorageClient({chromedriverDir: this.executableDir})
79
82
  : null;
80
83
  this.details = details;
84
+ /** @type {any} */
81
85
  this.capabilities = {};
82
86
  this.desiredProtocol = PROTOCOLS.MJSONWP;
83
87
  }
84
88
 
85
- get log () {
89
+ get log() {
86
90
  return this._log;
87
91
  }
88
92
 
89
- async getDriversMapping () {
93
+ async getDriversMapping() {
90
94
  let mapping = _.cloneDeep(CHROMEDRIVER_CHROME_MAPPING);
91
95
  if (this.mappingPath) {
92
96
  this.log.debug(`Attempting to use Chromedriver->Chrome mapping from '${this.mappingPath}'`);
93
- if (!await fs.exists(this.mappingPath)) {
97
+ if (!(await fs.exists(this.mappingPath))) {
94
98
  this.log.warn(`No file found at '${this.mappingPath}'`);
95
99
  this.log.info('Defaulting to the static Chromedriver->Chrome mapping');
96
100
  } else {
97
101
  try {
98
102
  mapping = JSON.parse(await fs.readFile(this.mappingPath, 'utf8'));
99
- } catch (err) {
103
+ } catch (e) {
104
+ const err = /** @type {Error} */ (e);
100
105
  this.log.warn(`Error parsing mapping from '${this.mappingPath}': ${err.message}`);
101
106
  this.log.info('Defaulting to the static Chromedriver->Chrome mapping');
102
107
  }
@@ -117,7 +122,10 @@ class Chromedriver extends events.EventEmitter {
117
122
  return mapping;
118
123
  }
119
124
 
120
- async getChromedrivers (mapping) {
125
+ /**
126
+ * @param {ChromedriverVersionMapping} mapping
127
+ */
128
+ async getChromedrivers(mapping) {
121
129
  // go through the versions available
122
130
  const executables = await fs.glob('*', {
123
131
  cwd: this.executableDir,
@@ -125,62 +133,78 @@ class Chromedriver extends events.EventEmitter {
125
133
  nodir: true,
126
134
  absolute: true,
127
135
  });
128
- this.log.debug(`Found ${util.pluralize('executable', executables.length, true)} ` +
129
- `in '${this.executableDir}'`);
130
- const cds = (await asyncmap(executables, async (executable) => {
131
- const logError = ({message, stdout = null, stderr = null}) => {
132
- let errMsg = `Cannot retrieve version number from '${path.basename(executable)}' Chromedriver binary. ` +
133
- `Make sure it returns a valid version string in response to '--version' command line argument. ${message}`;
134
- if (stdout) {
135
- errMsg += `\nStdout: ${stdout}`;
136
- }
137
- if (stderr) {
138
- errMsg += `\nStderr: ${stderr}`;
139
- }
140
- this.log.warn(errMsg);
141
- return null;
142
- };
136
+ this.log.debug(
137
+ `Found ${util.pluralize('executable', executables.length, true)} ` +
138
+ `in '${this.executableDir}'`
139
+ );
140
+ const cds = (
141
+ await asyncmap(executables, async (executable) => {
142
+ /**
143
+ * @param {{message: string, stdout?: string, stderr?: string}} opts
144
+ */
145
+ const logError = ({message, stdout, stderr}) => {
146
+ let errMsg =
147
+ `Cannot retrieve version number from '${path.basename(
148
+ executable
149
+ )}' Chromedriver binary. ` +
150
+ `Make sure it returns a valid version string in response to '--version' command line argument. ${message}`;
151
+ if (stdout) {
152
+ errMsg += `\nStdout: ${stdout}`;
153
+ }
154
+ if (stderr) {
155
+ errMsg += `\nStderr: ${stderr}`;
156
+ }
157
+ this.log.warn(errMsg);
158
+ return null;
159
+ };
143
160
 
144
- let stdout;
145
- let stderr;
146
- try {
147
- ({stdout, stderr} = await exec(executable, ['--version'], {
148
- timeout: CD_VERSION_TIMEOUT,
149
- }));
150
- } catch (err) {
151
- if (!(err.message || '').includes('timed out') && !(err.stdout || '').includes('Starting ChromeDriver')) {
152
- return logError(err);
153
- }
161
+ let stdout;
162
+ let stderr;
163
+ try {
164
+ ({stdout, stderr} = await exec(executable, ['--version'], {
165
+ timeout: CD_VERSION_TIMEOUT,
166
+ }));
167
+ } catch (e) {
168
+ const err = /** @type {import('teen_process').ExecError} */ (e);
169
+ if (
170
+ !(err.message || '').includes('timed out') &&
171
+ !(err.stdout || '').includes('Starting ChromeDriver')
172
+ ) {
173
+ return logError(err);
174
+ }
154
175
 
155
- // if this has timed out, it has actually started Chromedriver,
156
- // in which case there will also be the version string in the output
157
- stdout = err.stdout;
158
- }
176
+ // if this has timed out, it has actually started Chromedriver,
177
+ // in which case there will also be the version string in the output
178
+ stdout = err.stdout;
179
+ }
159
180
 
160
- const match = /ChromeDriver\s+\(?v?([\d.]+)\)?/i.exec(stdout); // https://regex101.com/r/zpj5wA/1
161
- if (!match) {
162
- return logError({message: 'Cannot parse the version string', stdout, stderr});
163
- }
164
- let version = match[1];
165
- let minChromeVersion = mapping[version];
166
- const coercedVersion = semver.coerce(version);
167
- if (coercedVersion) {
168
- // before 2019-03-06 versions were of the form major.minor
169
- if (coercedVersion.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
170
- version = `${coercedVersion.major}.${coercedVersion.minor}`;
171
- minChromeVersion = mapping[version];
181
+ const match = /ChromeDriver\s+\(?v?([\d.]+)\)?/i.exec(stdout); // https://regex101.com/r/zpj5wA/1
182
+ if (!match) {
183
+ return logError({message: 'Cannot parse the version string', stdout, stderr});
172
184
  }
173
- if (!minChromeVersion && coercedVersion.major >= NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
174
- // Assume the major Chrome version is the same as the corresponding driver major version
175
- minChromeVersion = `${coercedVersion.major}`;
185
+ let version = match[1];
186
+ let minChromeVersion = mapping[version];
187
+ const coercedVersion = semver.coerce(version);
188
+ if (coercedVersion) {
189
+ // before 2019-03-06 versions were of the form major.minor
190
+ if (coercedVersion.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
191
+ version = /** @type {keyof typeof mapping} */ (
192
+ `${coercedVersion.major}.${coercedVersion.minor}`
193
+ );
194
+ minChromeVersion = mapping[version];
195
+ }
196
+ if (!minChromeVersion && coercedVersion.major >= NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
197
+ // Assume the major Chrome version is the same as the corresponding driver major version
198
+ minChromeVersion = `${coercedVersion.major}`;
199
+ }
176
200
  }
177
- }
178
- return {
179
- executable,
180
- version,
181
- minChromeVersion,
182
- };
183
- }))
201
+ return {
202
+ executable,
203
+ version,
204
+ minChromeVersion,
205
+ };
206
+ })
207
+ )
184
208
  .filter((cd) => !!cd)
185
209
  .sort((a, b) => compareVersions(b.version, a.version));
186
210
  if (_.isEmpty(cds)) {
@@ -189,19 +213,23 @@ class Chromedriver extends events.EventEmitter {
189
213
  }
190
214
  this.log.debug(`The following Chromedriver executables were found:`);
191
215
  for (const cd of cds) {
192
- this.log.debug(` '${cd.executable}' (version '${cd.version}', minimum Chrome version '${cd.minChromeVersion ? cd.minChromeVersion : 'Unknown'}')`);
216
+ this.log.debug(
217
+ ` '${cd.executable}' (version '${cd.version}', minimum Chrome version '${
218
+ cd.minChromeVersion ? cd.minChromeVersion : 'Unknown'
219
+ }')`
220
+ );
193
221
  }
194
222
  return cds;
195
223
  }
196
224
 
197
- async getChromeVersion () {
225
+ async getChromeVersion() {
198
226
  // Try to retrieve the version from `details` property if it is set
199
227
  // The `info` item must contain the output of /json/version CDP command
200
228
  // where `Browser` field looks like `Chrome/72.0.3601.0``
201
229
  if (this.details?.info) {
202
230
  this.log.debug(`Browser version in the supplied details: ${this.details?.info?.Browser}`);
203
231
  }
204
- const versionMatch = VERSION_PATTERN.exec(this.details?.info?.Browser);
232
+ const versionMatch = VERSION_PATTERN.exec(this.details?.info?.Browser ?? '');
205
233
  if (versionMatch) {
206
234
  const coercedVersion = semver.coerce(versionMatch[1]);
207
235
  if (coercedVersion) {
@@ -213,11 +241,13 @@ class Chromedriver extends events.EventEmitter {
213
241
 
214
242
  // in case of WebView Browser Tester, simply try to find the underlying webview
215
243
  if (this.bundleId === WEBVIEW_SHELL_BUNDLE_ID) {
216
- for (const bundleId of WEBVIEW_BUNDLE_IDS) {
217
- chromeVersion = await getChromeVersion(this.adb, bundleId);
218
- if (chromeVersion) {
219
- this.bundleId = bundleId;
220
- return semver.coerce(chromeVersion);
244
+ if (this.adb) {
245
+ for (const bundleId of WEBVIEW_BUNDLE_IDS) {
246
+ chromeVersion = await getChromeVersion(this.adb, bundleId);
247
+ if (chromeVersion) {
248
+ this.bundleId = bundleId;
249
+ return semver.coerce(chromeVersion);
250
+ }
221
251
  }
222
252
  }
223
253
  return null;
@@ -226,8 +256,11 @@ class Chromedriver extends events.EventEmitter {
226
256
  // on Android 7-9 webviews are backed by the main Chrome, not the system webview
227
257
  if (this.adb) {
228
258
  const apiLevel = await this.adb.getApiLevel();
229
- if (apiLevel >= 24 && apiLevel <= 28 &&
230
- [WEBVIEW_SHELL_BUNDLE_ID, ...WEBVIEW_BUNDLE_IDS].includes(this.bundleId)) {
259
+ if (
260
+ apiLevel >= 24 &&
261
+ apiLevel <= 28 &&
262
+ [WEBVIEW_SHELL_BUNDLE_ID, ...WEBVIEW_BUNDLE_IDS].includes(this.bundleId ?? '')
263
+ ) {
231
264
  this.bundleId = CHROME_BUNDLE_ID;
232
265
  }
233
266
  }
@@ -239,16 +272,18 @@ class Chromedriver extends events.EventEmitter {
239
272
 
240
273
  // we have a webview of some sort, so try to find the bundle version
241
274
  for (const bundleId of WEBVIEW_BUNDLE_IDS) {
242
- chromeVersion = await getChromeVersion(this.adb, bundleId);
243
- if (chromeVersion) {
244
- this.bundleId = bundleId;
245
- break;
275
+ if (this.adb) {
276
+ chromeVersion = await getChromeVersion(this.adb, bundleId);
277
+ if (chromeVersion) {
278
+ this.bundleId = bundleId;
279
+ break;
280
+ }
246
281
  }
247
282
  }
248
283
  }
249
284
 
250
285
  // if we do not have a chrome version, it must not be a webview
251
- if (!chromeVersion) {
286
+ if (!chromeVersion && this.adb) {
252
287
  chromeVersion = await getChromeVersion(this.adb, this.bundleId);
253
288
  }
254
289
 
@@ -256,15 +291,27 @@ class Chromedriver extends events.EventEmitter {
256
291
  return chromeVersion ? semver.coerce(chromeVersion) : null;
257
292
  }
258
293
 
259
- async updateDriversMapping (newMapping) {
294
+ /**
295
+ *
296
+ * @param {ChromedriverVersionMapping} newMapping
297
+ * @returns {Promise<void>}
298
+ */
299
+ async updateDriversMapping(newMapping) {
260
300
  let shouldUpdateStaticMapping = true;
301
+ if (!this.mappingPath) {
302
+ this.log.warn('No mapping path provided');
303
+ return;
304
+ }
261
305
  if (await fs.exists(this.mappingPath)) {
262
306
  try {
263
307
  await fs.writeFile(this.mappingPath, JSON.stringify(newMapping, null, 2), 'utf8');
264
308
  shouldUpdateStaticMapping = false;
265
309
  } catch (e) {
266
- this.log.warn(`Cannot store the updated chromedrivers mapping into '${this.mappingPath}'. ` +
267
- `This may reduce the performance of further executions. Original error: ${e.message}`);
310
+ const err = /** @type {Error} */ (e);
311
+ this.log.warn(
312
+ `Cannot store the updated chromedrivers mapping into '${this.mappingPath}'. ` +
313
+ `This may reduce the performance of further executions. Original error: ${err.message}`
314
+ );
268
315
  }
269
316
  }
270
317
  if (shouldUpdateStaticMapping) {
@@ -272,7 +319,10 @@ class Chromedriver extends events.EventEmitter {
272
319
  }
273
320
  }
274
321
 
275
- async getCompatibleChromedriver () {
322
+ /**
323
+ * @returns {Promise<string>}
324
+ */
325
+ async getCompatibleChromedriver() {
276
326
  if (!this.adb) {
277
327
  return await getChromedriverBinaryPath();
278
328
  }
@@ -283,11 +333,19 @@ class Chromedriver extends events.EventEmitter {
283
333
  }
284
334
 
285
335
  let didStorageSync = false;
336
+ /**
337
+ *
338
+ * @param {import('semver').SemVer} chromeVersion
339
+ */
286
340
  const syncChromedrivers = async (chromeVersion) => {
287
341
  didStorageSync = true;
342
+ if (!this.storageClient) {
343
+ return false;
344
+ }
288
345
  const retrievedMapping = await this.storageClient.retrieveMapping();
289
- this.log.debug('Got chromedrivers mapping from the storage: ' +
290
- JSON.stringify(retrievedMapping, null, 2));
346
+ this.log.debug(
347
+ 'Got chromedrivers mapping from the storage: ' + JSON.stringify(retrievedMapping, null, 2)
348
+ );
291
349
  const driverKeys = await this.storageClient.syncDrivers({
292
350
  minBrowserVersion: chromeVersion.major,
293
351
  });
@@ -298,7 +356,7 @@ class Chromedriver extends events.EventEmitter {
298
356
  const {version, minBrowserVersion} = retrievedMapping[x];
299
357
  acc[version] = minBrowserVersion;
300
358
  return acc;
301
- }, {});
359
+ }, /** @type {ChromedriverVersionMapping} */ ({}));
302
360
  Object.assign(mapping, synchronizedDriversMapping);
303
361
  await this.updateDriversMapping(mapping);
304
362
  return true;
@@ -307,6 +365,7 @@ class Chromedriver extends events.EventEmitter {
307
365
  do {
308
366
  const cds = await this.getChromedrivers(mapping);
309
367
 
368
+ /** @type {ChromedriverVersionMapping} */
310
369
  const missingVersions = {};
311
370
  for (const {version, minChromeVersion} of cds) {
312
371
  if (!minChromeVersion || mapping[version]) {
@@ -320,20 +379,30 @@ class Chromedriver extends events.EventEmitter {
320
379
  missingVersions[version] = minChromeVersion;
321
380
  }
322
381
  if (!_.isEmpty(missingVersions)) {
323
- this.log.info(`Found ${util.pluralize('Chromedriver', _.size(missingVersions), true)}, ` +
324
- `which ${_.size(missingVersions) === 1 ? 'is' : 'are'} missing in the list of known versions: ` +
325
- JSON.stringify(missingVersions));
382
+ this.log.info(
383
+ `Found ${util.pluralize('Chromedriver', _.size(missingVersions), true)}, ` +
384
+ `which ${
385
+ _.size(missingVersions) === 1 ? 'is' : 'are'
386
+ } missing in the list of known versions: ` +
387
+ JSON.stringify(missingVersions)
388
+ );
326
389
  await this.updateDriversMapping(Object.assign(mapping, missingVersions));
327
390
  }
328
391
 
329
392
  if (this.disableBuildCheck) {
330
393
  if (_.isEmpty(cds)) {
331
- this.log.errorAndThrow(`There must be at least one Chromedriver executable available for use if ` +
332
- `'chromedriverDisableBuildCheck' capability is set to 'true'`);
394
+ this.log.errorAndThrow(
395
+ `There must be at least one Chromedriver executable available for use if ` +
396
+ `'chromedriverDisableBuildCheck' capability is set to 'true'`
397
+ );
333
398
  }
334
399
  const {version, executable} = cds[0];
335
- this.log.warn(`Chrome build check disabled. Using most recent Chromedriver version (${version}, at '${executable}')`);
336
- this.log.warn(`If this is wrong, set 'chromedriverDisableBuildCheck' capability to 'false'`);
400
+ this.log.warn(
401
+ `Chrome build check disabled. Using most recent Chromedriver version (${version}, at '${executable}')`
402
+ );
403
+ this.log.warn(
404
+ `If this is wrong, set 'chromedriverDisableBuildCheck' capability to 'false'`
405
+ );
337
406
  return executable;
338
407
  }
339
408
 
@@ -341,11 +410,15 @@ class Chromedriver extends events.EventEmitter {
341
410
  if (!chromeVersion) {
342
411
  // unable to get the chrome version
343
412
  if (_.isEmpty(cds)) {
344
- this.log.errorAndThrow(`There must be at least one Chromedriver executable available for use if ` +
345
- `the current Chrome version cannot be determined`);
413
+ this.log.errorAndThrow(
414
+ `There must be at least one Chromedriver executable available for use if ` +
415
+ `the current Chrome version cannot be determined`
416
+ );
346
417
  }
347
418
  const {version, executable} = cds[0];
348
- this.log.warn(`Unable to discover Chrome version. Using Chromedriver ${version} at '${executable}'`);
419
+ this.log.warn(
420
+ `Unable to discover Chrome version. Using Chromedriver ${version} at '${executable}'`
421
+ );
349
422
  return executable;
350
423
  }
351
424
  this.log.debug(`Found Chrome bundle '${this.bundleId}' version '${chromeVersion}'`);
@@ -367,59 +440,82 @@ class Chromedriver extends events.EventEmitter {
367
440
  continue;
368
441
  }
369
442
  } catch (e) {
370
- this.log.warn(`Cannot synchronize local chromedrivers with the remote storage at ${CD_CDN}: ` +
371
- e.message);
372
- this.log.debug(e.stack);
443
+ const err = /** @type {Error} */ (e);
444
+ this.log.warn(
445
+ `Cannot synchronize local chromedrivers with the remote storage at ${CD_CDN}: ` +
446
+ err.message
447
+ );
448
+ this.log.debug(err.stack);
373
449
  }
374
450
  }
375
451
  const autodownloadSuggestion =
376
452
  'You could also try to enable automated chromedrivers download as ' +
377
453
  'a possible workaround.';
378
- throw new Error(`No Chromedriver found that can automate Chrome '${chromeVersion}'.` +
379
- (this.storageClient ? '' : ` ${autodownloadSuggestion}`));
454
+ throw new Error(
455
+ `No Chromedriver found that can automate Chrome '${chromeVersion}'.` +
456
+ (this.storageClient ? '' : ` ${autodownloadSuggestion}`)
457
+ );
380
458
  }
381
459
 
382
460
  const binPath = matchingDrivers[0].executable;
383
- this.log.debug(`Found ${util.pluralize('executable', matchingDrivers.length, true)} ` +
384
- `capable of automating Chrome '${chromeVersion}'.\nChoosing the most recent, '${binPath}'.`);
385
- this.log.debug('If a specific version is required, specify it with the `chromedriverExecutable`' +
386
- 'desired capability.');
461
+ this.log.debug(
462
+ `Found ${util.pluralize('executable', matchingDrivers.length, true)} ` +
463
+ `capable of automating Chrome '${chromeVersion}'.\nChoosing the most recent, '${binPath}'.`
464
+ );
465
+ this.log.debug(
466
+ 'If a specific version is required, specify it with the `chromedriverExecutable`' +
467
+ 'desired capability.'
468
+ );
387
469
  return binPath;
388
- // eslint-disable-next-line no-constant-condition
470
+ // eslint-disable-next-line no-constant-condition
389
471
  } while (true);
390
472
  }
391
473
 
392
- async initChromedriverPath () {
393
- if (this.executableVerified) return; //eslint-disable-line curly
474
+ async initChromedriverPath() {
475
+ if (this.executableVerified && this.chromedriver) {
476
+ return /** @type {string} */ (this.chromedriver);
477
+ }
394
478
 
479
+ let chromedriver = this.chromedriver;
395
480
  // the executable might be set (if passed in)
396
481
  // or we might want to use the basic one installed with this driver
397
482
  // or we want to figure out the best one
398
- if (!this.chromedriver) {
399
- this.chromedriver = this.useSystemExecutable
483
+ if (!chromedriver) {
484
+ chromedriver = this.chromedriver = this.useSystemExecutable
400
485
  ? await getChromedriverBinaryPath()
401
486
  : await this.getCompatibleChromedriver();
402
487
  }
403
488
 
404
- if (!await fs.exists(this.chromedriver)) {
405
- throw new Error(`Trying to use a chromedriver binary at the path ` +
406
- `${this.chromedriver}, but it doesn't exist!`);
489
+ if (!(await fs.exists(chromedriver))) {
490
+ throw new Error(
491
+ `Trying to use a chromedriver binary at the path ` +
492
+ `${this.chromedriver}, but it doesn't exist!`
493
+ );
407
494
  }
408
495
  this.executableVerified = true;
409
496
  this.log.info(`Set chromedriver binary as: ${this.chromedriver}`);
497
+ return /** @type {string} */ (this.chromedriver);
410
498
  }
411
499
 
412
- syncProtocol (cdVersion = null) {
500
+ /**
501
+ *
502
+ * @param {string} [cdVersion]
503
+ */
504
+ syncProtocol(cdVersion) {
413
505
  const coercedVersion = semver.coerce(cdVersion);
414
506
  if (!coercedVersion || coercedVersion.major < MIN_CD_VERSION_WITH_W3C_SUPPORT) {
415
- this.log.debug(`Chromedriver v. ${cdVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` +
416
- `Defaulting to ${PROTOCOLS.MJSONWP}`);
507
+ this.log.debug(
508
+ `Chromedriver v. ${cdVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` +
509
+ `Defaulting to ${PROTOCOLS.MJSONWP}`
510
+ );
417
511
  return;
418
512
  }
419
513
  const chromeOptions = getCapValue(this.capabilities, 'chromeOptions', {});
420
514
  if (chromeOptions.w3c === false) {
421
- this.log.info(`Chromedriver v. ${cdVersion} supports ${PROTOCOLS.W3C} protocol, ` +
422
- `but ${PROTOCOLS.MJSONWP} one has been explicitly requested`);
515
+ this.log.info(
516
+ `Chromedriver v. ${cdVersion} supports ${PROTOCOLS.W3C} protocol, ` +
517
+ `but ${PROTOCOLS.MJSONWP} one has been explicitly requested`
518
+ );
423
519
  return;
424
520
  }
425
521
  this.desiredProtocol = PROTOCOLS.W3C;
@@ -429,7 +525,12 @@ class Chromedriver extends events.EventEmitter {
429
525
  this.capabilities = toW3cCapNames(this.capabilities);
430
526
  }
431
527
 
432
- async start (caps, emitStartingState = true) {
528
+ /**
529
+ *
530
+ * @param {object} caps
531
+ * @param {boolean} emitStartingState
532
+ */
533
+ async start(caps, emitStartingState = true) {
433
534
  this.capabilities = _.cloneDeep(caps);
434
535
 
435
536
  // set the logging preferences to ALL the console logs
@@ -458,16 +559,16 @@ class Chromedriver extends events.EventEmitter {
458
559
  args.push('--verbose');
459
560
  // what are the process stdout/stderr conditions wherein we know that
460
561
  // the process has started to our satisfaction?
461
- const startDetector = (stdout) => stdout.startsWith('Starting ');
562
+ const startDetector = /** @param {string} stdout */ (stdout) => stdout.startsWith('Starting ');
462
563
 
463
564
  let processIsAlive = false;
464
565
  let webviewVersion;
465
566
  try {
466
- await this.initChromedriverPath();
567
+ const chromedriverPath = await this.initChromedriverPath();
467
568
  await this.killAll();
468
569
 
469
570
  // set up our subprocess object
470
- this.proc = new SubProcess(this.chromedriver, args);
571
+ this.proc = new SubProcess(chromedriverPath, args);
471
572
  processIsAlive = true;
472
573
 
473
574
  // handle log output
@@ -513,9 +614,11 @@ class Chromedriver extends events.EventEmitter {
513
614
  // handle out-of-bound exit by simply emitting a stopped state
514
615
  this.proc.on('exit', (code, signal) => {
515
616
  processIsAlive = false;
516
- if (this.state !== Chromedriver.STATE_STOPPED &&
517
- this.state !== Chromedriver.STATE_STOPPING &&
518
- this.state !== Chromedriver.STATE_RESTARTING) {
617
+ if (
618
+ this.state !== Chromedriver.STATE_STOPPED &&
619
+ this.state !== Chromedriver.STATE_STOPPING &&
620
+ this.state !== Chromedriver.STATE_RESTARTING
621
+ ) {
519
622
  const msg = `Chromedriver exited unexpectedly with code ${code}, signal ${signal}`;
520
623
  this.log.error(msg);
521
624
  this.changeState(Chromedriver.STATE_STOPPED);
@@ -527,38 +630,41 @@ class Chromedriver extends events.EventEmitter {
527
630
  await this.waitForOnline();
528
631
  await this.startSession();
529
632
  } catch (e) {
530
- this.log.debug(e);
531
- this.emit(Chromedriver.EVENT_ERROR, e);
633
+ const err = /** @type {Error} */ (e);
634
+ this.log.debug(err);
635
+ this.emit(Chromedriver.EVENT_ERROR, err);
532
636
  // just because we had an error doesn't mean the chromedriver process
533
637
  // finished; we should clean up if necessary
534
638
  if (processIsAlive) {
535
- await this.proc.stop();
639
+ await this.proc?.stop();
536
640
  }
537
641
 
538
642
  let message = '';
539
643
  // often the user's Chrome version is not supported by the version of Chromedriver
540
- if (e.message.includes('Chrome version must be')) {
541
- message += 'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
644
+ if (err.message.includes('Chrome version must be')) {
645
+ message +=
646
+ 'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
542
647
  if (webviewVersion) {
543
648
  message += `Chrome version on the device: ${webviewVersion}\n`;
544
649
  }
545
- const versionsSupportedByDriver = /Chrome version must be (.+)/.exec(e.message)?.[1] || '';
650
+ const versionsSupportedByDriver =
651
+ /Chrome version must be (.+)/.exec(err.message)?.[1] || '';
546
652
  if (versionsSupportedByDriver) {
547
653
  message += `Chromedriver supports Chrome version(s): ${versionsSupportedByDriver}\n`;
548
654
  }
549
655
  message += 'Check the driver tutorial for troubleshooting.\n';
550
656
  }
551
657
 
552
- message += e.message;
658
+ message += err.message;
553
659
  this.log.errorAndThrow(message);
554
660
  }
555
661
  }
556
662
 
557
- sessionId () {
663
+ sessionId() {
558
664
  return this.state === Chromedriver.STATE_ONLINE ? this.jwproxy.sessionId : null;
559
665
  }
560
666
 
561
- async restart () {
667
+ async restart() {
562
668
  this.log.info('Restarting chromedriver');
563
669
  if (this.state !== Chromedriver.STATE_ONLINE) {
564
670
  throw new Error("Can't restart when we're not online");
@@ -568,7 +674,7 @@ class Chromedriver extends events.EventEmitter {
568
674
  await this.start(this.capabilities, false);
569
675
  }
570
676
 
571
- async waitForOnline () {
677
+ async waitForOnline() {
572
678
  // we need to make sure that CD hasn't crashed
573
679
  let chromedriverStopped = false;
574
680
  await retryInterval(20, 200, async () => {
@@ -584,62 +690,89 @@ class Chromedriver extends events.EventEmitter {
584
690
  }
585
691
  }
586
692
 
587
- async getStatus () {
693
+ async getStatus() {
588
694
  return await this.jwproxy.command('/status', 'GET');
589
695
  }
590
696
 
591
- async startSession () {
592
- const sessionCaps = this.desiredProtocol === PROTOCOLS.W3C
593
- ? {capabilities: {alwaysMatch: this.capabilities}}
594
- : {desiredCapabilities: this.capabilities};
595
- this.log.info(`Starting ${this.desiredProtocol} Chromedriver session with capabilities: ` +
596
- JSON.stringify(sessionCaps, null, 2));
697
+ async startSession() {
698
+ const sessionCaps =
699
+ this.desiredProtocol === PROTOCOLS.W3C
700
+ ? {capabilities: {alwaysMatch: this.capabilities}}
701
+ : {desiredCapabilities: this.capabilities};
702
+ this.log.info(
703
+ `Starting ${this.desiredProtocol} Chromedriver session with capabilities: ` +
704
+ JSON.stringify(sessionCaps, null, 2)
705
+ );
706
+ // jwproxy types have not been implemented yet
707
+ // @ts-expect-error
597
708
  await this.jwproxy.command('/session', 'POST', sessionCaps);
598
709
  this.log.prefix = generateLogPrefix(this, this.jwproxy.sessionId);
599
710
  this.changeState(Chromedriver.STATE_ONLINE);
600
711
  }
601
712
 
602
- async stop (emitStates = true) {
713
+ async stop(emitStates = true) {
603
714
  if (emitStates) {
604
715
  this.changeState(Chromedriver.STATE_STOPPING);
605
716
  }
717
+ /**
718
+ *
719
+ * @param {() => Promise<any>|any} f
720
+ */
606
721
  const runSafeStep = async (f) => {
607
722
  try {
608
723
  return await f();
609
724
  } catch (e) {
610
- this.log.warn(e.message);
611
- this.log.debug(e.stack);
725
+ const err = /** @type {Error} */ (e);
726
+ this.log.warn(err.message);
727
+ this.log.debug(err.stack);
612
728
  }
613
729
  };
614
730
  await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
615
- await runSafeStep(() => this.proc.stop('SIGTERM', 20000));
731
+ await runSafeStep(() => this.proc?.stop('SIGTERM', 20000));
616
732
  this.log.prefix = generateLogPrefix(this);
617
733
  if (emitStates) {
618
734
  this.changeState(Chromedriver.STATE_STOPPED);
619
735
  }
620
736
  }
621
737
 
622
- changeState (state) {
738
+ /**
739
+ *
740
+ * @param {string} state
741
+ */
742
+ changeState(state) {
623
743
  this.state = state;
624
744
  this.log.debug(`Changed state to '${state}'`);
625
745
  this.emit(Chromedriver.EVENT_CHANGED, {state});
626
746
  }
627
747
 
628
- async sendCommand (url, method, body) {
748
+ /**
749
+ *
750
+ * @param {string} url
751
+ * @param {'POST'|'GET'|'DELETE'} method
752
+ * @param {any} body
753
+ * @returns
754
+ */
755
+ async sendCommand(url, method, body) {
629
756
  return await this.jwproxy.command(url, method, body);
630
757
  }
631
758
 
632
- async proxyReq (req, res) {
759
+ /**
760
+ *
761
+ * @param {any} req
762
+ * @param {any} res
763
+ * @privateRemarks req / res probably from Express
764
+ */
765
+ async proxyReq(req, res) {
633
766
  return await this.jwproxy.proxyReqRes(req, res);
634
767
  }
635
768
 
636
- async killAll () {
769
+ async killAll() {
637
770
  let cmd = system.isWindows()
638
771
  ? `wmic process where "commandline like '%chromedriver.exe%--port=${this.proxyPort}%'" delete`
639
772
  : `pkill -15 -f "${this.chromedriver}.*--port=${this.proxyPort}"`;
640
773
  this.log.debug(`Killing any old chromedrivers, running: ${cmd}`);
641
774
  try {
642
- await (B.promisify(cp.exec))(cmd);
775
+ await B.promisify(cp.exec)(cmd);
643
776
  this.log.debug('Successfully cleaned up old chromedrivers');
644
777
  } catch (err) {
645
778
  this.log.warn('No old chromedrivers seem to exist');
@@ -667,13 +800,14 @@ class Chromedriver extends events.EventEmitter {
667
800
  await this.adb.removePortForward(params[1].replace(/[\D]*/, ''));
668
801
  }
669
802
  }
670
- } catch (err) {
803
+ } catch (e) {
804
+ const err = /** @type {Error} */ (e);
671
805
  this.log.warn(`Unable to clean forwarded ports. Error: '${err.message}'. Continuing.`);
672
806
  }
673
807
  }
674
808
  }
675
809
 
676
- async hasWorkingWebview () {
810
+ async hasWorkingWebview() {
677
811
  // sometimes chromedriver stops automating webviews. this method runs a
678
812
  // simple command to determine our state, and responds accordingly
679
813
  try {
@@ -693,5 +827,9 @@ Chromedriver.STATE_ONLINE = 'online';
693
827
  Chromedriver.STATE_STOPPING = 'stopping';
694
828
  Chromedriver.STATE_RESTARTING = 'restarting';
695
829
 
696
- export { Chromedriver };
830
+ export {Chromedriver};
697
831
  export default Chromedriver;
832
+
833
+ /**
834
+ * @typedef {import('./types').ChromedriverVersionMapping} ChromedriverVersionMapping
835
+ */