appium-chromedriver 8.0.30 → 8.1.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.
@@ -22,66 +22,78 @@ import {parseGoogleapiStorageXml} from './googleapis';
22
22
  import {parseKnownGoodVersionsWithDownloadsJson, parseLatestKnownGoodVersionsJson} from './chromelabs';
23
23
  import {compareVersions} from 'compare-versions';
24
24
  import * as semver from 'semver';
25
+ import type {
26
+ ChromedriverStorageClientOpts,
27
+ SyncOptions,
28
+ OSInfo,
29
+ ChromedriverDetailsMapping,
30
+ } from '../types';
25
31
 
26
32
  const MAX_PARALLEL_DOWNLOADS = 5;
27
- const STORAGE_INFOS = /** @type {readonly StorageInfo[]} */ ([{
28
- url: GOOGLEAPIS_CDN,
29
- accept: 'application/xml',
30
- }, {
31
- url: `${CHROMELABS_URL}/chrome-for-testing/known-good-versions-with-downloads.json`,
32
- accept: 'application/json',
33
- }]);
33
+
34
+ interface StorageInfo {
35
+ url: string;
36
+ accept: string;
37
+ }
38
+
39
+ const STORAGE_INFOS: readonly StorageInfo[] = [
40
+ {
41
+ url: GOOGLEAPIS_CDN,
42
+ accept: 'application/xml',
43
+ },
44
+ {
45
+ url: `${CHROMELABS_URL}/chrome-for-testing/known-good-versions-with-downloads.json`,
46
+ accept: 'application/json',
47
+ },
48
+ ];
34
49
 
35
50
  const CHROME_FOR_TESTING_LAST_GOOD_VERSIONS = `${CHROMELABS_URL}/chrome-for-testing/last-known-good-versions.json`;
36
51
 
37
52
  const log = logger.getLogger('ChromedriverStorageClient');
38
53
 
39
- /**
40
- *
41
- * @param {string} src
42
- * @param {string} checksum
43
- * @returns {Promise<boolean>}
44
- */
45
- async function isCrcOk(src, checksum) {
54
+ async function isCrcOk(src: string, checksum: string): Promise<boolean> {
46
55
  const md5 = await fs.hash(src, 'md5');
47
56
  return _.toLower(md5) === _.toLower(checksum);
48
57
  }
49
58
 
50
59
  export class ChromedriverStorageClient {
51
- /**
52
- *
53
- * @param {import('../types').ChromedriverStorageClientOpts} args
54
- */
55
- constructor(args = {}) {
60
+ readonly chromedriverDir: string;
61
+ readonly timeout: number;
62
+ private mapping: ChromedriverDetailsMapping;
63
+
64
+ constructor(args: ChromedriverStorageClientOpts = {}) {
56
65
  const {chromedriverDir = getChromedriverDir(), timeout = STORAGE_REQ_TIMEOUT_MS} = args;
57
66
  this.chromedriverDir = chromedriverDir;
58
67
  this.timeout = timeout;
59
- /** @type {ChromedriverDetailsMapping} */
60
68
  this.mapping = {};
61
69
  }
62
70
 
63
71
  /**
64
72
  * Retrieves chromedriver mapping from the storage
65
73
  *
66
- * @param {boolean} shouldParseNotes [true] - if set to `true`
74
+ * @param shouldParseNotes [true] - if set to `true`
67
75
  * then additional chromedrivers info is going to be retrieved and
68
76
  * parsed from release notes
69
- * @returns {Promise<ChromedriverDetailsMapping>}
77
+ * @returns Promise<ChromedriverDetailsMapping>
70
78
  */
71
- async retrieveMapping(shouldParseNotes = true) {
72
- /** @type {(si: StorageInfo) => Promise<string|undefined>} */
73
- const retrieveResponseSafely = async (/** @type {StorageInfo} */ {url, accept}) => {
79
+ async retrieveMapping(shouldParseNotes = true): Promise<ChromedriverDetailsMapping> {
80
+ const retrieveResponseSafely = async ({url, accept}: StorageInfo): Promise<string | undefined> => {
74
81
  try {
75
- return await retrieveData(url, {
76
- 'user-agent': USER_AGENT,
77
- accept: `${accept}, */*`,
78
- }, {timeout: this.timeout});
82
+ return await retrieveData(
83
+ url,
84
+ {
85
+ 'user-agent': USER_AGENT,
86
+ accept: `${accept}, */*`,
87
+ },
88
+ {timeout: this.timeout}
89
+ );
79
90
  } catch (e) {
80
- log.debug(/** @type {Error} */ (e).stack);
91
+ const err = e as Error;
92
+ log.debug(err.stack);
81
93
  log.warn(
82
94
  `Cannot retrieve Chromedrivers info from ${url}. ` +
83
- `Make sure this URL is accessible from your network. ` +
84
- `Original error: ${/** @type {Error} */(e).message}`
95
+ `Make sure this URL is accessible from your network. ` +
96
+ `Original error: ${err.message}`
85
97
  );
86
98
  }
87
99
  };
@@ -91,9 +103,9 @@ export class ChromedriverStorageClient {
91
103
  if (!xmlStr && !jsonStr) {
92
104
  throw new Error(
93
105
  `Cannot retrieve the information about available Chromedrivers from ` +
94
- `${STORAGE_INFOS.map(({url}) => url)}. Please make sure these URLs are available ` +
95
- `within your local network, check Appium server logs and/or ` +
96
- `consult the driver troubleshooting guide.`
106
+ `${STORAGE_INFOS.map(({url}) => url)}. Please make sure these URLs are available ` +
107
+ `within your local network, check Appium server logs and/or ` +
108
+ `consult the driver troubleshooting guide.`
97
109
  );
98
110
  }
99
111
  this.mapping = xmlStr ? await parseGoogleapiStorageXml(xmlStr, shouldParseNotes) : {};
@@ -104,47 +116,102 @@ export class ChromedriverStorageClient {
104
116
  }
105
117
 
106
118
  /**
107
- * Extracts downloaded chromedriver archive
108
- * into the given destination
119
+ * Retrieves chromedrivers from the remote storage to the local file system
109
120
  *
110
- * @param {string} src - The source archive path
111
- * @param {string} dst - The destination chromedriver path
121
+ * @param opts - Synchronization options (versions, minBrowserVersion, osInfo)
122
+ * @throws {Error} if there was a problem while retrieving the drivers
123
+ * @returns The list of successfully synchronized driver keys
112
124
  */
113
- async unzipDriver(src, dst) {
114
- const tmpRoot = await tempDir.openDir();
125
+ async syncDrivers(opts: SyncOptions = {}): Promise<string[]> {
126
+ if (_.isEmpty(this.mapping)) {
127
+ await this.retrieveMapping(!!opts.minBrowserVersion);
128
+ }
129
+ if (_.isEmpty(this.mapping)) {
130
+ throw new Error('Cannot retrieve chromedrivers mapping from Google storage');
131
+ }
132
+
133
+ const driversToSync = this.selectMatchingDrivers(opts.osInfo ?? (await getOsInfo()), opts);
134
+ if (_.isEmpty(driversToSync)) {
135
+ log.debug(`There are no drivers to sync. Exiting`);
136
+ return [];
137
+ }
138
+ log.debug(
139
+ `Got ${util.pluralize('driver', driversToSync.length, true)} to sync: ` +
140
+ JSON.stringify(driversToSync, null, 2)
141
+ );
142
+
143
+ const synchronizedDrivers: string[] = [];
144
+ const promises: Promise<void>[] = [];
145
+ const chunk: Promise<void>[] = [];
146
+ const archivesRoot = await tempDir.openDir();
115
147
  try {
116
- await zip.extractAllTo(src, tmpRoot);
117
- const chromedriverPath = await fs.walkDir(
118
- tmpRoot,
119
- true,
120
- (itemPath, isDirectory) =>
121
- !isDirectory && _.toLower(path.parse(itemPath).name) === 'chromedriver'
122
- );
123
- if (!chromedriverPath) {
124
- throw new Error(
125
- 'The archive was unzipped properly, but we could not find any chromedriver executable'
148
+ for (const [idx, driverKey] of driversToSync.entries()) {
149
+ const promise = B.resolve(
150
+ (async () => {
151
+ if (await this.retrieveDriver(idx, driverKey, archivesRoot, !_.isEmpty(opts))) {
152
+ synchronizedDrivers.push(driverKey);
153
+ }
154
+ })()
126
155
  );
156
+ promises.push(promise);
157
+ chunk.push(promise);
158
+ if (chunk.length >= MAX_PARALLEL_DOWNLOADS) {
159
+ await B.any(chunk);
160
+ }
161
+ _.remove(chunk, (p) => (p as B<void>).isFulfilled());
127
162
  }
128
- log.debug(`Moving the extracted '${path.basename(chromedriverPath)}' to '${dst}'`);
129
- await fs.mv(chromedriverPath, dst, {
130
- mkdirp: true,
131
- });
163
+ await B.all(promises);
132
164
  } finally {
133
- await fs.rimraf(tmpRoot);
165
+ await fs.rimraf(archivesRoot);
166
+ }
167
+ if (!_.isEmpty(synchronizedDrivers)) {
168
+ log.info(
169
+ `Successfully synchronized ` +
170
+ `${util.pluralize('chromedriver', synchronizedDrivers.length, true)}`
171
+ );
172
+ } else {
173
+ log.info(`No chromedrivers were synchronized`);
134
174
  }
175
+ return synchronizedDrivers;
135
176
  }
136
177
 
137
178
  /**
138
- * Filters `this.mapping` to only select matching
139
- * chromedriver entries by operating system information
140
- * and/or additional synchronization options (if provided)
179
+ * Returns the latest chromedriver version for Chrome for Testing
141
180
  *
142
- * @param {OSInfo} osInfo
143
- * @param {SyncOptions} opts
144
- * @returns {Array<String>} The list of filtered chromedriver
145
- * entry names (version/archive name)
181
+ * @returns The latest stable chromedriver version string
182
+ * @throws {Error} if the version cannot be fetched from the remote API
146
183
  */
147
- selectMatchingDrivers(osInfo, opts = {}) {
184
+ async getLatestKnownGoodVersion(): Promise<string> {
185
+ let jsonStr: string;
186
+ try {
187
+ jsonStr = await retrieveData(
188
+ CHROME_FOR_TESTING_LAST_GOOD_VERSIONS,
189
+ {
190
+ 'user-agent': USER_AGENT,
191
+ accept: `application/json, */*`,
192
+ },
193
+ {timeout: STORAGE_REQ_TIMEOUT_MS}
194
+ );
195
+ } catch (e) {
196
+ const err = e as Error;
197
+ throw new Error(
198
+ `Cannot fetch the latest Chromedriver version. ` +
199
+ `Make sure you can access ${CHROME_FOR_TESTING_LAST_GOOD_VERSIONS} from your machine or provide a mirror by setting ` +
200
+ `a custom value to CHROMELABS_URL environment variable. Original error: ${err.message}`
201
+ );
202
+ }
203
+ return parseLatestKnownGoodVersionsJson(jsonStr);
204
+ }
205
+
206
+ /**
207
+ * Filters `this.mapping` to only select matching chromedriver entries
208
+ * by operating system information and/or additional synchronization options
209
+ *
210
+ * @param osInfo - Operating system information to match against
211
+ * @param opts - Synchronization options (versions, minBrowserVersion)
212
+ * @returns The list of filtered chromedriver entry names (version/archive name)
213
+ */
214
+ private selectMatchingDrivers(osInfo: OSInfo, opts: SyncOptions = {}): string[] {
148
215
  const {minBrowserVersion, versions = []} = opts;
149
216
  let driversToSync = _.keys(this.mapping);
150
217
 
@@ -205,15 +272,23 @@ export class ChromedriverStorageClient {
205
272
  let result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, osInfo));
206
273
  if (_.isEmpty(result) && arch === ARCH.X64 && cpu === CPU.INTEL) {
207
274
  // Fallback to X86 if X64 architecture is not available for this driver
208
- result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, {
209
- name, arch: ARCH.X86, cpu
210
- }));
275
+ result = driversToSync.filter((cdName) =>
276
+ this.doesMatchForOsInfo(cdName, {
277
+ name,
278
+ arch: ARCH.X86,
279
+ cpu,
280
+ })
281
+ );
211
282
  }
212
283
  if (_.isEmpty(result) && name === OS.MAC && cpu === CPU.ARM) {
213
284
  // Fallback to Intel/Rosetta if ARM architecture is not available for this driver
214
- result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, {
215
- name, arch, cpu: CPU.INTEL
216
- }));
285
+ result = driversToSync.filter((cdName) =>
286
+ this.doesMatchForOsInfo(cdName, {
287
+ name,
288
+ arch,
289
+ cpu: CPU.INTEL,
290
+ })
291
+ );
217
292
  }
218
293
  driversToSync = result;
219
294
  log.debug(`Got ${util.pluralize('item', driversToSync.length, true)}`);
@@ -221,12 +296,11 @@ export class ChromedriverStorageClient {
221
296
 
222
297
  if (!_.isEmpty(driversToSync)) {
223
298
  log.debug('Excluding older patches if present');
224
- /** @type {{[key: string]: string[]}} */
225
- const patchesMap = {};
299
+ const patchesMap: {[key: string]: string[]} = {};
226
300
  // Older chromedrivers must not be excluded as they follow a different
227
301
  // versioning pattern
228
302
  const versionWithPatchPattern = /\d+\.\d+\.\d+\.\d+/;
229
- const selectedVersions = new Set();
303
+ const selectedVersions = new Set<string>();
230
304
  for (const cdName of driversToSync) {
231
305
  const cdVersion = this.mapping[cdName].version;
232
306
  if (!versionWithPatchPattern.test(cdVersion)) {
@@ -246,17 +320,15 @@ export class ChromedriverStorageClient {
246
320
  if (patchesMap[majorVersion].length <= 1) {
247
321
  continue;
248
322
  }
249
- patchesMap[majorVersion].sort(
250
- (/** @type {string} */ a, /** @type {string}} */ b) => compareVersions(b, a)
251
- );
323
+ patchesMap[majorVersion].sort((a: string, b: string) => compareVersions(b, a));
252
324
  }
253
325
  if (!_.isEmpty(patchesMap)) {
254
326
  log.debug('Versions mapping: ' + JSON.stringify(patchesMap, null, 2));
255
327
  for (const sortedVersions of _.values(patchesMap)) {
256
328
  selectedVersions.add(sortedVersions[0]);
257
329
  }
258
- driversToSync = driversToSync.filter(
259
- (cdName) => selectedVersions.has(this.mapping[cdName].version)
330
+ driversToSync = driversToSync.filter((cdName) =>
331
+ selectedVersions.has(this.mapping[cdName].version)
260
332
  );
261
333
  }
262
334
  }
@@ -267,11 +339,11 @@ export class ChromedriverStorageClient {
267
339
  /**
268
340
  * Checks whether the given chromedriver matches the operating system to run on
269
341
  *
270
- * @param {string} cdName
271
- * @param {OSInfo} osInfo
272
- * @returns {boolean}
342
+ * @param cdName - The chromedriver entry key in the mapping
343
+ * @param osInfo - Operating system information to match against
344
+ * @returns True if the chromedriver matches the OS info
273
345
  */
274
- doesMatchForOsInfo(cdName, {name, arch, cpu}) {
346
+ private doesMatchForOsInfo(cdName: string, {name, arch, cpu}: OSInfo): boolean {
275
347
  const cdInfo = this.mapping[cdName];
276
348
  if (!cdInfo) {
277
349
  return false;
@@ -291,18 +363,23 @@ export class ChromedriverStorageClient {
291
363
  * Retrieves the given chromedriver from the storage
292
364
  * and unpacks it into `this.chromedriverDir` folder
293
365
  *
294
- * @param {number} index - The unique driver index
295
- * @param {string} driverKey - The driver key in `this.mapping`
296
- * @param {string} archivesRoot - The temporary folder path to extract
366
+ * @param index - The unique driver index
367
+ * @param driverKey - The driver key in `this.mapping`
368
+ * @param archivesRoot - The temporary folder path to extract
297
369
  * downloaded archives to
298
- * @param {boolean} isStrict [true] - Whether to throw an error (`true`)
370
+ * @param isStrict [true] - Whether to throw an error (`true`)
299
371
  * or return a boolean result if the driver retrieval process fails
300
372
  * @throws {Error} if there was a failure while retrieving the driver
301
373
  * and `isStrict` is set to `true`
302
- * @returns {Promise<boolean>} if `true` then the chromedriver is successfully
374
+ * @returns if `true` then the chromedriver is successfully
303
375
  * downloaded and extracted.
304
376
  */
305
- async retrieveDriver(index, driverKey, archivesRoot, isStrict = false) {
377
+ private async retrieveDriver(
378
+ index: number,
379
+ driverKey: string,
380
+ archivesRoot: string,
381
+ isStrict = false
382
+ ): Promise<boolean> {
306
383
  const {url, etag, version} = this.mapping[driverKey];
307
384
  const archivePath = path.resolve(archivesRoot, `${index}.zip`);
308
385
  log.debug(`Retrieving '${url}' to '${archivePath}'`);
@@ -312,7 +389,7 @@ export class ChromedriverStorageClient {
312
389
  timeout: STORAGE_REQ_TIMEOUT_MS,
313
390
  });
314
391
  } catch (e) {
315
- const err = /** @type {Error} */ (e);
392
+ const err = e as Error;
316
393
  const msg = `Cannot download chromedriver archive. Original error: ${err.message}`;
317
394
  if (isStrict) {
318
395
  throw new Error(msg);
@@ -335,7 +412,7 @@ export class ChromedriverStorageClient {
335
412
  await fs.chmod(targetPath, 0o755);
336
413
  log.debug(`Permissions of the file '${targetPath}' have been changed to 755`);
337
414
  } catch (e) {
338
- const err = /** @type {Error} */ (e);
415
+ const err = e as Error;
339
416
  if (isStrict) {
340
417
  throw err;
341
418
  }
@@ -346,105 +423,34 @@ export class ChromedriverStorageClient {
346
423
  }
347
424
 
348
425
  /**
349
- * Retrieves chromedrivers from the remote storage
350
- * to the local file system
426
+ * Extracts downloaded chromedriver archive
427
+ * into the given destination
351
428
  *
352
- * @param {SyncOptions} opts
353
- * @throws {Error} if there was a problem while retrieving
354
- * the drivers
355
- * @returns {Promise<string[]>} The list of successfully synchronized driver keys
429
+ * @param src - The source archive path
430
+ * @param dst - The destination chromedriver path
356
431
  */
357
- async syncDrivers(opts = {}) {
358
- if (_.isEmpty(this.mapping)) {
359
- await this.retrieveMapping(!!opts.minBrowserVersion);
360
- }
361
- if (_.isEmpty(this.mapping)) {
362
- throw new Error('Cannot retrieve chromedrivers mapping from Google storage');
363
- }
364
-
365
- const driversToSync = this.selectMatchingDrivers(opts.osInfo ?? (await getOsInfo()), opts);
366
- if (_.isEmpty(driversToSync)) {
367
- log.debug(`There are no drivers to sync. Exiting`);
368
- return [];
369
- }
370
- log.debug(
371
- `Got ${util.pluralize('driver', driversToSync.length, true)} to sync: ` +
372
- JSON.stringify(driversToSync, null, 2)
373
- );
374
-
375
- /**
376
- * @type {string[]}
377
- */
378
- const synchronizedDrivers = [];
379
- const promises = [];
380
- const chunk = [];
381
- const archivesRoot = await tempDir.openDir();
432
+ private async unzipDriver(src: string, dst: string): Promise<void> {
433
+ const tmpRoot = await tempDir.openDir();
382
434
  try {
383
- for (const [idx, driverKey] of driversToSync.entries()) {
384
- const promise = B.resolve(
385
- (async () => {
386
- if (await this.retrieveDriver(idx, driverKey, archivesRoot, !_.isEmpty(opts))) {
387
- synchronizedDrivers.push(driverKey);
388
- }
389
- })()
435
+ await zip.extractAllTo(src, tmpRoot);
436
+ const chromedriverPath = await fs.walkDir(
437
+ tmpRoot,
438
+ true,
439
+ (itemPath, isDirectory) =>
440
+ !isDirectory && _.toLower(path.parse(itemPath).name) === 'chromedriver'
441
+ );
442
+ if (!chromedriverPath) {
443
+ throw new Error(
444
+ 'The archive was unzipped properly, but we could not find any chromedriver executable'
390
445
  );
391
- promises.push(promise);
392
- chunk.push(promise);
393
- if (chunk.length >= MAX_PARALLEL_DOWNLOADS) {
394
- await B.any(chunk);
395
- }
396
- _.remove(chunk, (p) => p.isFulfilled());
397
446
  }
398
- await B.all(promises);
447
+ log.debug(`Moving the extracted '${path.basename(chromedriverPath)}' to '${dst}'`);
448
+ await fs.mv(chromedriverPath, dst, {
449
+ mkdirp: true,
450
+ });
399
451
  } finally {
400
- await fs.rimraf(archivesRoot);
401
- }
402
- if (!_.isEmpty(synchronizedDrivers)) {
403
- log.info(
404
- `Successfully synchronized ` +
405
- `${util.pluralize('chromedriver', synchronizedDrivers.length, true)}`
406
- );
407
- } else {
408
- log.info(`No chromedrivers were synchronized`);
409
- }
410
- return synchronizedDrivers;
411
- }
412
-
413
- /**
414
- * Return latest chromedriver version for Chrome for Testing.
415
- * @returns {Promise<string>}
416
- */
417
- async getLatestKnownGoodVersion() {
418
- let jsonStr;
419
- try {
420
- jsonStr = await retrieveData(
421
- CHROME_FOR_TESTING_LAST_GOOD_VERSIONS,
422
- {
423
- 'user-agent': USER_AGENT,
424
- accept: `application/json, */*`,
425
- }, {timeout: STORAGE_REQ_TIMEOUT_MS}
426
- );
427
- } catch (e) {
428
- const err = /** @type {Error} */ (e);
429
- throw new Error(`Cannot fetch the latest Chromedriver version. ` +
430
- `Make sure you can access ${CHROME_FOR_TESTING_LAST_GOOD_VERSIONS} from your machine or provide a mirror by setting ` +
431
- `a custom value to CHROMELABS_URL environment variable. Original error: ${err.message}`);
452
+ await fs.rimraf(tmpRoot);
432
453
  }
433
- return parseLatestKnownGoodVersionsJson(jsonStr);
434
454
  }
435
455
  }
436
456
 
437
- export default ChromedriverStorageClient;
438
-
439
- /**
440
- * @typedef {import('../types').SyncOptions} SyncOptions
441
- * @typedef {import('../types').OSInfo} OSInfo
442
- * @typedef {import('../types').ChromedriverDetails} ChromedriverDetails
443
- * @typedef {import('../types').ChromedriverDetailsMapping} ChromedriverDetailsMapping
444
- */
445
-
446
- /**
447
- * @typedef {Object} StorageInfo
448
- * @property {string} url
449
- * @property {string} accept
450
- */
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "chrome",
7
7
  "android"
8
8
  ],
9
- "version": "8.0.30",
9
+ "version": "8.1.0",
10
10
  "author": "Appium Contributors",
11
11
  "license": "Apache-2.0",
12
12
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "compare-versions": "^6.0.0",
52
52
  "lodash": "^4.17.4",
53
53
  "semver": "^7.0.0",
54
- "teen_process": "^3.0.0",
54
+ "teen_process": "^4.0.4",
55
55
  "xpath": "^0.x"
56
56
  },
57
57
  "scripts": {
@@ -78,7 +78,6 @@
78
78
  "@types/node": "^25.0.0",
79
79
  "@types/semver": "^7.0.0",
80
80
  "@types/sinon": "^21.0.0",
81
- "@types/teen_process": "^2.0.0",
82
81
  "chai": "^6.0.0",
83
82
  "chai-as-promised": "^8.0.0",
84
83
  "conventional-changelog-conventionalcommits": "^9.0.0",