appium-chromedriver 5.5.3 → 5.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +14 -0
- package/build/lib/chromedriver.d.ts +1 -1
- package/build/lib/chromedriver.d.ts.map +1 -1
- package/build/lib/chromedriver.js +4 -4
- package/build/lib/chromedriver.js.map +1 -1
- package/build/lib/constants.d.ts +19 -0
- package/build/lib/constants.d.ts.map +1 -0
- package/build/lib/constants.js +26 -0
- package/build/lib/constants.js.map +1 -0
- package/build/lib/index.d.ts +1 -1
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +1 -1
- package/build/lib/index.js.map +1 -1
- package/build/lib/install.d.ts.map +1 -1
- package/build/lib/install.js +23 -8
- package/build/lib/install.js.map +1 -1
- package/build/lib/storage-client/chromelabs.d.ts +22 -0
- package/build/lib/storage-client/chromelabs.d.ts.map +1 -0
- package/build/lib/storage-client/chromelabs.js +148 -0
- package/build/lib/storage-client/chromelabs.js.map +1 -0
- package/build/lib/storage-client/googleapis.d.ts +32 -0
- package/build/lib/storage-client/googleapis.d.ts.map +1 -0
- package/build/lib/storage-client/googleapis.js +188 -0
- package/build/lib/storage-client/googleapis.js.map +1 -0
- package/build/lib/{storage-client.d.ts → storage-client/storage-client.d.ts} +14 -36
- package/build/lib/storage-client/storage-client.d.ts.map +1 -0
- package/build/lib/{storage-client.js → storage-client/storage-client.js} +105 -181
- package/build/lib/storage-client/storage-client.js.map +1 -0
- package/build/lib/types.d.ts +9 -3
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/utils.d.ts +6 -9
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +20 -21
- package/build/lib/utils.js.map +1 -1
- package/config/mapping.json +1 -0
- package/lib/chromedriver.js +4 -5
- package/lib/constants.js +24 -0
- package/lib/index.ts +1 -1
- package/lib/install.js +27 -10
- package/lib/storage-client/chromelabs.js +141 -0
- package/lib/storage-client/googleapis.js +209 -0
- package/lib/{storage-client.js → storage-client/storage-client.js} +124 -213
- package/lib/types.ts +9 -3
- package/lib/utils.js +20 -20
- package/package.json +1 -1
- package/build/lib/storage-client.d.ts.map +0 -1
- package/build/lib/storage-client.js.map +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import xpath from 'xpath';
|
|
3
|
+
import {util, logger} from '@appium/support';
|
|
4
|
+
import {retrieveData} from '../utils';
|
|
5
|
+
import B from 'bluebird';
|
|
6
|
+
import {
|
|
7
|
+
STORAGE_REQ_TIMEOUT_MS,
|
|
8
|
+
GOOGLEAPIS_CDN,
|
|
9
|
+
ARCH,
|
|
10
|
+
CPU,
|
|
11
|
+
APPLE_ARM_SUFFIXES,
|
|
12
|
+
} from '../constants';
|
|
13
|
+
import {DOMParser} from '@xmldom/xmldom';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const log = logger.getLogger('ChromedriverGoogleapisStorageClient');
|
|
18
|
+
const MAX_PARALLEL_DOWNLOADS = 5;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {Node|Attr} parent
|
|
23
|
+
* @param {string?} childName
|
|
24
|
+
* @param {string?} text
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export function findChildNode(parent, childName = null, text = null) {
|
|
28
|
+
if (!childName && !text) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (!parent.hasChildNodes()) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let childNodeIdx = 0; childNodeIdx < parent.childNodes.length; childNodeIdx++) {
|
|
36
|
+
const childNode = /** @type {Element|Attr} */ (parent.childNodes[childNodeIdx]);
|
|
37
|
+
if (childName && !text && childName === childNode.localName) {
|
|
38
|
+
return childNode;
|
|
39
|
+
}
|
|
40
|
+
if (text) {
|
|
41
|
+
const childText = extractNodeText(childNode);
|
|
42
|
+
if (!childText) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (childName && childName === childNode.localName && text === childText) {
|
|
46
|
+
return childNode;
|
|
47
|
+
}
|
|
48
|
+
if (!childName && text === childText) {
|
|
49
|
+
return childNode;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {Node?} node
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
function extractNodeText(node) {
|
|
62
|
+
return !node || !node.firstChild || !util.hasValue(node.firstChild.nodeValue)
|
|
63
|
+
? null
|
|
64
|
+
: node.firstChild.nodeValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets additional chromedriver details from chromedriver
|
|
69
|
+
* release notes
|
|
70
|
+
*
|
|
71
|
+
* @param {string} content - Release notes of the corresponding chromedriver
|
|
72
|
+
* @returns {import('../types').AdditionalDriverDetails}
|
|
73
|
+
*/
|
|
74
|
+
export function parseNotes(content) {
|
|
75
|
+
const result = {};
|
|
76
|
+
const versionMatch = /^\s*[-]+ChromeDriver[\D]+([\d.]+)/im.exec(content);
|
|
77
|
+
if (versionMatch) {
|
|
78
|
+
result.version = versionMatch[1];
|
|
79
|
+
}
|
|
80
|
+
const minBrowserVersionMatch = /^\s*Supports Chrome[\D]+(\d+)/im.exec(content);
|
|
81
|
+
if (minBrowserVersionMatch) {
|
|
82
|
+
result.minBrowserVersion = minBrowserVersionMatch[1];
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parses chromedriver storage XML and returns
|
|
89
|
+
* the parsed results
|
|
90
|
+
*
|
|
91
|
+
* @param {string} xml - The chromedriver storage XML
|
|
92
|
+
* @param {boolean} shouldParseNotes [true] - If set to `true`
|
|
93
|
+
* then additional drivers information is going to be parsed
|
|
94
|
+
* and assigned to `this.mapping`
|
|
95
|
+
* @returns {Promise<ChromedriverDetailsMapping>}
|
|
96
|
+
*/
|
|
97
|
+
export async function parseGoogleapiStorageXml(xml, shouldParseNotes = true) {
|
|
98
|
+
const doc = new DOMParser().parseFromString(xml);
|
|
99
|
+
const driverNodes = /** @type {Array<Node|Attr>} */ (
|
|
100
|
+
xpath.select(`//*[local-name(.)='Contents']`, doc)
|
|
101
|
+
);
|
|
102
|
+
log.debug(`Parsed ${driverNodes.length} entries from storage XML`);
|
|
103
|
+
if (_.isEmpty(driverNodes)) {
|
|
104
|
+
throw new Error('Cannot retrieve any valid Chromedriver entries from the storage config');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const promises = [];
|
|
108
|
+
const chunk = [];
|
|
109
|
+
/** @type {ChromedriverDetailsMapping} */
|
|
110
|
+
const mapping = {};
|
|
111
|
+
for (const driverNode of driverNodes) {
|
|
112
|
+
const k = extractNodeText(findChildNode(driverNode, 'Key'));
|
|
113
|
+
if (!_.includes(k, '/chromedriver_')) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const key = String(k);
|
|
117
|
+
|
|
118
|
+
const etag = extractNodeText(findChildNode(driverNode, 'ETag'));
|
|
119
|
+
if (!etag) {
|
|
120
|
+
log.debug(`The entry '${key}' does not contain the checksum. Skipping it`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const filename = path.basename(key);
|
|
125
|
+
const osNameMatch = /_([a-z]+)/i.exec(filename);
|
|
126
|
+
if (!osNameMatch) {
|
|
127
|
+
log.debug(`The entry '${key}' does not contain valid OS name. Skipping it`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @type {ChromedriverDetails} */
|
|
132
|
+
const cdInfo = {
|
|
133
|
+
url: `${GOOGLEAPIS_CDN}/${key}`,
|
|
134
|
+
etag: _.trim(etag, '"'),
|
|
135
|
+
version: /** @type {string} */ (_.first(key.split('/'))),
|
|
136
|
+
minBrowserVersion: null,
|
|
137
|
+
os: {
|
|
138
|
+
name: osNameMatch[1],
|
|
139
|
+
arch: filename.includes(ARCH.X64) ? ARCH.X64 : ARCH.X86,
|
|
140
|
+
cpu: APPLE_ARM_SUFFIXES.some((suffix) => filename.includes(suffix)) ? CPU.ARM : CPU.INTEL,
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
mapping[key] = cdInfo;
|
|
144
|
+
|
|
145
|
+
const notesPath = `${cdInfo.version}/notes.txt`;
|
|
146
|
+
const isNotesPresent = !!driverNodes.reduce(
|
|
147
|
+
(acc, node) => Boolean(acc || findChildNode(node, 'Key', notesPath)),
|
|
148
|
+
false
|
|
149
|
+
);
|
|
150
|
+
if (!isNotesPresent) {
|
|
151
|
+
cdInfo.minBrowserVersion = null;
|
|
152
|
+
if (shouldParseNotes) {
|
|
153
|
+
log.info(`The entry '${key}' does not contain any notes. Skipping it`);
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
} else if (!shouldParseNotes) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const promise = B.resolve(retrieveAdditionalDriverInfo(key, `${GOOGLEAPIS_CDN}/${notesPath}`, cdInfo));
|
|
161
|
+
promises.push(promise);
|
|
162
|
+
chunk.push(promise);
|
|
163
|
+
if (chunk.length >= MAX_PARALLEL_DOWNLOADS) {
|
|
164
|
+
await B.any(chunk);
|
|
165
|
+
}
|
|
166
|
+
_.remove(chunk, (p) => p.isFulfilled());
|
|
167
|
+
}
|
|
168
|
+
await B.all(promises);
|
|
169
|
+
log.info(`The total count of entries in the mapping: ${_.size(mapping)}`);
|
|
170
|
+
return mapping;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Downloads chromedriver release notes and puts them
|
|
175
|
+
* into the dictionary argument
|
|
176
|
+
*
|
|
177
|
+
* The method call mutates by merging `AdditionalDriverDetails`
|
|
178
|
+
* @param {string} driverKey - Driver version plus archive name
|
|
179
|
+
* @param {string} notesUrl - The URL of chromedriver notes
|
|
180
|
+
* @param {ChromedriverDetails} infoDict - The dictionary containing driver info.
|
|
181
|
+
* @param {number} timeout
|
|
182
|
+
* @throws {Error} if the release notes cannot be downloaded
|
|
183
|
+
*/
|
|
184
|
+
async function retrieveAdditionalDriverInfo(driverKey, notesUrl, infoDict, timeout = STORAGE_REQ_TIMEOUT_MS) {
|
|
185
|
+
const notes = await retrieveData(
|
|
186
|
+
notesUrl,
|
|
187
|
+
{
|
|
188
|
+
'user-agent': 'appium',
|
|
189
|
+
accept: '*/*',
|
|
190
|
+
},
|
|
191
|
+
{timeout}
|
|
192
|
+
);
|
|
193
|
+
const {minBrowserVersion} = parseNotes(notes);
|
|
194
|
+
if (!minBrowserVersion) {
|
|
195
|
+
log.debug(
|
|
196
|
+
`The driver '${driverKey}' does not contain valid release notes at ${notesUrl}. ` +
|
|
197
|
+
`Skipping it`
|
|
198
|
+
);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
infoDict.minBrowserVersion = minBrowserVersion;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @typedef {import('../types').SyncOptions} SyncOptions
|
|
206
|
+
* @typedef {import('../types').OSInfo} OSInfo
|
|
207
|
+
* @typedef {import('../types').ChromedriverDetails} ChromedriverDetails
|
|
208
|
+
* @typedef {import('../types').ChromedriverDetailsMapping} ChromedriverDetailsMapping
|
|
209
|
+
*/
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getChromedriverDir,
|
|
3
|
-
CD_CDN,
|
|
4
3
|
retrieveData,
|
|
5
4
|
getOsInfo,
|
|
6
|
-
OS,
|
|
7
|
-
X64,
|
|
8
|
-
X86,
|
|
9
|
-
APPLE_ARM_SUFFIXES,
|
|
10
5
|
convertToInt,
|
|
11
|
-
|
|
6
|
+
getCpuType,
|
|
7
|
+
} from '../utils';
|
|
12
8
|
import _ from 'lodash';
|
|
13
|
-
import xpath from 'xpath';
|
|
14
|
-
import {DOMParser} from '@xmldom/xmldom';
|
|
15
9
|
import B from 'bluebird';
|
|
16
10
|
import path from 'path';
|
|
17
|
-
import os from 'os';
|
|
18
11
|
import {system, fs, logger, tempDir, zip, util, net} from '@appium/support';
|
|
12
|
+
import {
|
|
13
|
+
STORAGE_REQ_TIMEOUT_MS,
|
|
14
|
+
GOOGLEAPIS_CDN,
|
|
15
|
+
USER_AGENT,
|
|
16
|
+
CHROMELABS_URL,
|
|
17
|
+
ARCH,
|
|
18
|
+
OS,
|
|
19
|
+
CPU,
|
|
20
|
+
} from '../constants';
|
|
21
|
+
import {parseGoogleapiStorageXml} from './googleapis';
|
|
22
|
+
import {parseKnownGoodVersionsWithDownloadsJson} from './chromelabs';
|
|
23
|
+
import {compareVersions} from 'compare-versions';
|
|
24
|
+
import semver from 'semver';
|
|
19
25
|
|
|
20
|
-
const TIMEOUT_MS = 15000;
|
|
21
26
|
const MAX_PARALLEL_DOWNLOADS = 5;
|
|
22
27
|
|
|
23
28
|
const log = logger.getLogger('ChromedriverStorageClient');
|
|
@@ -33,182 +38,19 @@ async function isCrcOk(src, checksum) {
|
|
|
33
38
|
return _.toLower(md5) === _.toLower(checksum);
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
* @param {Node|Attr} parent
|
|
39
|
-
* @param {string?} childName
|
|
40
|
-
* @param {string?} text
|
|
41
|
-
* @returns
|
|
42
|
-
*/
|
|
43
|
-
function findChildNode(parent, childName = null, text = null) {
|
|
44
|
-
if (!childName && !text) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
if (!parent.hasChildNodes()) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
for (let childNodeIdx = 0; childNodeIdx < parent.childNodes.length; childNodeIdx++) {
|
|
52
|
-
const childNode = /** @type {Element|Attr} */ (parent.childNodes[childNodeIdx]);
|
|
53
|
-
if (childName && !text && childName === childNode.localName) {
|
|
54
|
-
return childNode;
|
|
55
|
-
}
|
|
56
|
-
if (text) {
|
|
57
|
-
const childText = extractNodeText(childNode);
|
|
58
|
-
if (!childText) {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
if (childName && childName === childNode.localName && text === childText) {
|
|
62
|
-
return childNode;
|
|
63
|
-
}
|
|
64
|
-
if (!childName && text === childText) {
|
|
65
|
-
return childNode;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
*
|
|
74
|
-
* @param {Node?} node
|
|
75
|
-
* @returns
|
|
76
|
-
*/
|
|
77
|
-
function extractNodeText(node) {
|
|
78
|
-
return !node || !node.firstChild || !util.hasValue(node.firstChild.nodeValue)
|
|
79
|
-
? null
|
|
80
|
-
: node.firstChild.nodeValue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
41
|
export class ChromedriverStorageClient {
|
|
84
42
|
/**
|
|
85
43
|
*
|
|
86
|
-
* @param {import('
|
|
44
|
+
* @param {import('../types').ChromedriverStorageClientOpts} args
|
|
87
45
|
*/
|
|
88
46
|
constructor(args = {}) {
|
|
89
|
-
const {chromedriverDir = getChromedriverDir(), timeout =
|
|
47
|
+
const {chromedriverDir = getChromedriverDir(), timeout = STORAGE_REQ_TIMEOUT_MS} = args;
|
|
90
48
|
this.chromedriverDir = chromedriverDir;
|
|
91
49
|
this.timeout = timeout;
|
|
92
50
|
/** @type {ChromedriverDetailsMapping} */
|
|
93
51
|
this.mapping = {};
|
|
94
52
|
}
|
|
95
53
|
|
|
96
|
-
/**
|
|
97
|
-
* Gets additional chromedriver details from chromedriver
|
|
98
|
-
* release notes
|
|
99
|
-
*
|
|
100
|
-
* @param {string} content - Release notes of the corresponding chromedriver
|
|
101
|
-
* @returns {import('./types').AdditionalDriverDetails}
|
|
102
|
-
*/
|
|
103
|
-
parseNotes(content) {
|
|
104
|
-
const result = {};
|
|
105
|
-
const versionMatch = /^\s*[-]+ChromeDriver[\D]+([\d.]+)/im.exec(content);
|
|
106
|
-
if (versionMatch) {
|
|
107
|
-
result.version = versionMatch[1];
|
|
108
|
-
}
|
|
109
|
-
const minBrowserVersionMatch = /^\s*Supports Chrome[\D]+(\d+)/im.exec(content);
|
|
110
|
-
if (minBrowserVersionMatch) {
|
|
111
|
-
result.minBrowserVersion = minBrowserVersionMatch[1];
|
|
112
|
-
}
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Downloads chromedriver release notes and puts them
|
|
118
|
-
* into the dictionary argument
|
|
119
|
-
*
|
|
120
|
-
* The method call mutates by merging `AdditionalDriverDetails`
|
|
121
|
-
* @param {string} driverKey - Driver version plus archive name
|
|
122
|
-
* @param {string} notesUrl - The URL of chromedriver notes
|
|
123
|
-
* @param {ChromedriverDetails} infoDict - The dictionary containing driver info.
|
|
124
|
-
* @throws {Error} if the release notes cannot be downloaded
|
|
125
|
-
*/
|
|
126
|
-
async retrieveAdditionalDriverInfo(driverKey, notesUrl, infoDict) {
|
|
127
|
-
const notes = await retrieveData(
|
|
128
|
-
notesUrl,
|
|
129
|
-
{
|
|
130
|
-
'user-agent': 'appium',
|
|
131
|
-
accept: '*/*',
|
|
132
|
-
},
|
|
133
|
-
{timeout: this.timeout}
|
|
134
|
-
);
|
|
135
|
-
const {minBrowserVersion} = this.parseNotes(notes);
|
|
136
|
-
if (!minBrowserVersion) {
|
|
137
|
-
log.debug(
|
|
138
|
-
`The driver '${driverKey}' does not contain valid release notes at ${notesUrl}. ` +
|
|
139
|
-
`Skipping it`
|
|
140
|
-
);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
infoDict.minBrowserVersion = minBrowserVersion;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Parses chromedriver storage XML and stores
|
|
148
|
-
* the parsed results into `this.mapping`
|
|
149
|
-
*
|
|
150
|
-
* @param {Document} doc - The DOM representation
|
|
151
|
-
* of the chromedriver storage XML
|
|
152
|
-
* @param {boolean} shouldParseNotes [true] - If set to `true`
|
|
153
|
-
* then additional drivers information is going to be parsed
|
|
154
|
-
* and assigned to `this.mapping`
|
|
155
|
-
*/
|
|
156
|
-
async parseStorageXml(doc, shouldParseNotes = true) {
|
|
157
|
-
const driverNodes = /** @type {Array<Node|Attr>} */ (
|
|
158
|
-
xpath.select(`//*[local-name(.)='Contents']`, doc)
|
|
159
|
-
);
|
|
160
|
-
log.debug(`Parsed ${driverNodes.length} entries from storage XML`);
|
|
161
|
-
if (_.isEmpty(driverNodes)) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const promises = [];
|
|
166
|
-
for (const driverNode of driverNodes) {
|
|
167
|
-
const k = extractNodeText(findChildNode(driverNode, 'Key'));
|
|
168
|
-
if (!_.includes(k, '/chromedriver_')) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
const key = String(k);
|
|
172
|
-
|
|
173
|
-
const etag = extractNodeText(findChildNode(driverNode, 'ETag'));
|
|
174
|
-
if (!etag) {
|
|
175
|
-
log.debug(`The entry '${key}' does not contain the checksum. Skipping it`);
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** @type {ChromedriverDetails} */
|
|
180
|
-
const cdInfo = {
|
|
181
|
-
url: `${CD_CDN}/${key}`,
|
|
182
|
-
etag: _.trim(etag, '"'),
|
|
183
|
-
version: /** @type {string} */ (_.first(key.split('/'))),
|
|
184
|
-
minBrowserVersion: null,
|
|
185
|
-
};
|
|
186
|
-
this.mapping[key] = cdInfo;
|
|
187
|
-
|
|
188
|
-
const notesPath = `${cdInfo.version}/notes.txt`;
|
|
189
|
-
const isNotesPresent = !!driverNodes.reduce(
|
|
190
|
-
(acc, node) => Boolean(acc || findChildNode(node, 'Key', notesPath)),
|
|
191
|
-
false
|
|
192
|
-
);
|
|
193
|
-
if (!isNotesPresent) {
|
|
194
|
-
cdInfo.minBrowserVersion = null;
|
|
195
|
-
if (shouldParseNotes) {
|
|
196
|
-
log.info(`The entry '${key}' does not contain any notes. Skipping it`);
|
|
197
|
-
}
|
|
198
|
-
continue;
|
|
199
|
-
} else if (!shouldParseNotes) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
promises.push(this.retrieveAdditionalDriverInfo(key, `${CD_CDN}/${notesPath}`, cdInfo));
|
|
204
|
-
if (promises.length % MAX_PARALLEL_DOWNLOADS === 0) {
|
|
205
|
-
await B.all(promises);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
await B.all(promises);
|
|
209
|
-
log.info(`The total count of entries in the mapping: ${_.size(this.mapping)}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
54
|
/**
|
|
213
55
|
* Retrieves chromedriver mapping from the storage
|
|
214
56
|
*
|
|
@@ -218,17 +60,20 @@ export class ChromedriverStorageClient {
|
|
|
218
60
|
* @returns {Promise<ChromedriverDetailsMapping>}
|
|
219
61
|
*/
|
|
220
62
|
async retrieveMapping(shouldParseNotes = true) {
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
63
|
+
const [xmlStr, jsonStr] = await B.all([
|
|
64
|
+
[GOOGLEAPIS_CDN, 'application/xml'],
|
|
65
|
+
[`${CHROMELABS_URL}/chrome-for-testing/known-good-versions-with-downloads.json`, 'application/json'],
|
|
66
|
+
]
|
|
67
|
+
.map(([url, contentType]) => url
|
|
68
|
+
? retrieveData(url, {
|
|
69
|
+
'user-agent': USER_AGENT,
|
|
70
|
+
accept: `${contentType}, */*`,
|
|
71
|
+
}, {timeout: this.timeout})
|
|
72
|
+
: B.resolve()
|
|
73
|
+
));
|
|
74
|
+
this.mapping = xmlStr ? await parseGoogleapiStorageXml(xmlStr, shouldParseNotes) : {};
|
|
75
|
+
Object.assign(this.mapping, parseKnownGoodVersionsWithDownloadsJson(jsonStr));
|
|
76
|
+
return this.mapping;
|
|
232
77
|
}
|
|
233
78
|
|
|
234
79
|
/**
|
|
@@ -328,30 +173,93 @@ export class ChromedriverStorageClient {
|
|
|
328
173
|
|
|
329
174
|
if (!_.isEmpty(osInfo)) {
|
|
330
175
|
// Filter out drivers for unsupported system architectures
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
176
|
+
const {name, arch, cpu = getCpuType()} = osInfo;
|
|
177
|
+
log.debug(`Selecting chromedrivers whose platform matches to ${name}:${cpu}${arch}`);
|
|
178
|
+
let result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, osInfo));
|
|
179
|
+
if (_.isEmpty(result) && arch === ARCH.X64 && cpu === CPU.INTEL) {
|
|
180
|
+
// Fallback to X86 if X64 architecture is not available for this driver
|
|
181
|
+
result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, {
|
|
182
|
+
name, arch: ARCH.X86, cpu
|
|
183
|
+
}));
|
|
335
184
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
arch = armSuffix;
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
185
|
+
if (_.isEmpty(result) && name === OS.MAC && cpu === CPU.ARM) {
|
|
186
|
+
// Fallback to Intel/Rosetta if ARM architecture is not available for this driver
|
|
187
|
+
result = driversToSync.filter((cdName) => this.doesMatchForOsInfo(cdName, {
|
|
188
|
+
name, arch, cpu: CPU.INTEL
|
|
189
|
+
}));
|
|
345
190
|
}
|
|
346
|
-
|
|
347
|
-
const platformRe = new RegExp(`(\\b|_)${name}${arch}\\b`);
|
|
348
|
-
driversToSync = driversToSync.filter((cdName) => platformRe.test(cdName));
|
|
191
|
+
driversToSync = result;
|
|
349
192
|
log.debug(`Got ${util.pluralize('item', driversToSync.length, true)}`);
|
|
350
193
|
}
|
|
351
194
|
|
|
195
|
+
if (!_.isEmpty(driversToSync)) {
|
|
196
|
+
log.debug('Excluding older patches if present');
|
|
197
|
+
/** @type {{[key: string]: string[]}} */
|
|
198
|
+
const patchesMap = {};
|
|
199
|
+
// Older chromedrivers must not be excluded as they follow a different
|
|
200
|
+
// versioning pattern
|
|
201
|
+
const versionWithPatchPattern = /\d+\.\d+\.\d+\.\d+/;
|
|
202
|
+
const selectedVersions = new Set();
|
|
203
|
+
for (const cdName of driversToSync) {
|
|
204
|
+
const cdVersion = this.mapping[cdName].version;
|
|
205
|
+
if (!versionWithPatchPattern.test(cdVersion)) {
|
|
206
|
+
selectedVersions.add(cdVersion);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const verObj = semver.parse(cdVersion, {loose: true});
|
|
210
|
+
if (!verObj) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!_.isArray(patchesMap[verObj.major])) {
|
|
214
|
+
patchesMap[verObj.major] = [];
|
|
215
|
+
}
|
|
216
|
+
patchesMap[verObj.major].push(cdVersion);
|
|
217
|
+
}
|
|
218
|
+
for (const majorVersion of _.keys(patchesMap)) {
|
|
219
|
+
if (patchesMap[majorVersion].length <= 1) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
patchesMap[majorVersion].sort(
|
|
223
|
+
(/** @type {string} */ a, /** @type {string}} */ b) => compareVersions(b, a)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
if (!_.isEmpty(patchesMap)) {
|
|
227
|
+
log.debug('Versions mapping: ' + JSON.stringify(patchesMap, null, 2));
|
|
228
|
+
for (const sortedVersions of _.values(patchesMap)) {
|
|
229
|
+
selectedVersions.add(sortedVersions[0]);
|
|
230
|
+
}
|
|
231
|
+
driversToSync = driversToSync.filter(
|
|
232
|
+
(cdName) => selectedVersions.has(this.mapping[cdName].version)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
352
237
|
return driversToSync;
|
|
353
238
|
}
|
|
354
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Checks whether the given chromedriver matches the operating system to run on
|
|
242
|
+
*
|
|
243
|
+
* @param {string} cdName
|
|
244
|
+
* @param {OSInfo} osInfo
|
|
245
|
+
* @returns {boolean}
|
|
246
|
+
*/
|
|
247
|
+
doesMatchForOsInfo(cdName, {name, arch, cpu}) {
|
|
248
|
+
const cdInfo = this.mapping[cdName];
|
|
249
|
+
if (!cdInfo) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (cdInfo.os.name !== name || cdInfo.os.arch !== arch) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (cpu && cdInfo.os.cpu && this.mapping[cdName].os.cpu !== cpu) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
355
263
|
/**
|
|
356
264
|
* Retrieves the given chromedriver from the storage
|
|
357
265
|
* and unpacks it into `this.chromedriverDir` folder
|
|
@@ -374,7 +282,7 @@ export class ChromedriverStorageClient {
|
|
|
374
282
|
try {
|
|
375
283
|
await net.downloadFile(url, archivePath, {
|
|
376
284
|
isMetered: false,
|
|
377
|
-
timeout:
|
|
285
|
+
timeout: STORAGE_REQ_TIMEOUT_MS,
|
|
378
286
|
});
|
|
379
287
|
} catch (e) {
|
|
380
288
|
const err = /** @type {Error} */ (e);
|
|
@@ -385,7 +293,7 @@ export class ChromedriverStorageClient {
|
|
|
385
293
|
log.error(msg);
|
|
386
294
|
return false;
|
|
387
295
|
}
|
|
388
|
-
if (!(await isCrcOk(archivePath, etag))) {
|
|
296
|
+
if (etag && !(await isCrcOk(archivePath, etag))) {
|
|
389
297
|
const msg = `The checksum for the downloaded chromedriver '${driverKey}' did not match`;
|
|
390
298
|
if (isStrict) {
|
|
391
299
|
throw new Error(msg);
|
|
@@ -442,20 +350,23 @@ export class ChromedriverStorageClient {
|
|
|
442
350
|
*/
|
|
443
351
|
const synchronizedDrivers = [];
|
|
444
352
|
const promises = [];
|
|
353
|
+
const chunk = [];
|
|
445
354
|
const archivesRoot = await tempDir.openDir();
|
|
446
355
|
try {
|
|
447
356
|
for (const [idx, driverKey] of driversToSync.entries()) {
|
|
448
|
-
|
|
357
|
+
const promise = B.resolve(
|
|
449
358
|
(async () => {
|
|
450
359
|
if (await this.retrieveDriver(idx, driverKey, archivesRoot, !_.isEmpty(opts))) {
|
|
451
360
|
synchronizedDrivers.push(driverKey);
|
|
452
361
|
}
|
|
453
362
|
})()
|
|
454
363
|
);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
364
|
+
promises.push(promise);
|
|
365
|
+
chunk.push(promise);
|
|
366
|
+
if (chunk.length >= MAX_PARALLEL_DOWNLOADS) {
|
|
367
|
+
await B.any(chunk);
|
|
458
368
|
}
|
|
369
|
+
_.remove(chunk, (p) => p.isFulfilled());
|
|
459
370
|
}
|
|
460
371
|
await B.all(promises);
|
|
461
372
|
} finally {
|
|
@@ -464,7 +375,7 @@ export class ChromedriverStorageClient {
|
|
|
464
375
|
if (!_.isEmpty(synchronizedDrivers)) {
|
|
465
376
|
log.info(
|
|
466
377
|
`Successfully synchronized ` +
|
|
467
|
-
|
|
378
|
+
`${util.pluralize('chromedriver', synchronizedDrivers.length, true)}`
|
|
468
379
|
);
|
|
469
380
|
} else {
|
|
470
381
|
log.info(`No chromedrivers were synchronized`);
|
|
@@ -476,8 +387,8 @@ export class ChromedriverStorageClient {
|
|
|
476
387
|
export default ChromedriverStorageClient;
|
|
477
388
|
|
|
478
389
|
/**
|
|
479
|
-
* @typedef {import('
|
|
480
|
-
* @typedef {import('
|
|
481
|
-
* @typedef {import('
|
|
482
|
-
* @typedef {import('
|
|
390
|
+
* @typedef {import('../types').SyncOptions} SyncOptions
|
|
391
|
+
* @typedef {import('../types').OSInfo} OSInfo
|
|
392
|
+
* @typedef {import('../types').ChromedriverDetails} ChromedriverDetails
|
|
393
|
+
* @typedef {import('../types').ChromedriverDetailsMapping} ChromedriverDetailsMapping
|
|
483
394
|
*/
|
package/lib/types.ts
CHANGED
|
@@ -46,15 +46,20 @@ export interface SyncOptions {
|
|
|
46
46
|
export interface OSInfo {
|
|
47
47
|
/**
|
|
48
48
|
* The architecture of the host OS.
|
|
49
|
-
* Can be either `32
|
|
49
|
+
* Can be either `32`, `64``
|
|
50
50
|
*/
|
|
51
51
|
arch: string;
|
|
52
52
|
/**
|
|
53
53
|
*
|
|
54
54
|
* The name of the host OS.
|
|
55
|
-
* Can be either `mac`, `
|
|
55
|
+
* Can be either `mac`, `win` or `linux`
|
|
56
56
|
*/
|
|
57
57
|
name: string;
|
|
58
|
+
/**
|
|
59
|
+
* The cpu type of the host OS.
|
|
60
|
+
* Can be either `intel`, `arm`. `intel` is assumed by default
|
|
61
|
+
*/
|
|
62
|
+
cpu?: string;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
/**
|
|
@@ -68,12 +73,13 @@ export interface ChromedriverDetails {
|
|
|
68
73
|
/**
|
|
69
74
|
* CRC of driver archive
|
|
70
75
|
*/
|
|
71
|
-
etag: string;
|
|
76
|
+
etag: string | null;
|
|
72
77
|
/**
|
|
73
78
|
* Chromedriver version
|
|
74
79
|
*/
|
|
75
80
|
version: string;
|
|
76
81
|
minBrowserVersion: string | null;
|
|
82
|
+
os: OSInfo;
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/**
|