Skip to content

Commit 5e2c65c

Browse files
committedFeb 12, 2020
Replace request with axios and other updates
Updated other deps. Modernized code with async/await. Updated node versions to only include supported versions. Closes #189
1 parent fe02d5f commit 5e2c65c

File tree

6 files changed

+410
-592
lines changed

6 files changed

+410
-592
lines changed
 

‎.devcontainer/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ ENV DEBIAN_FRONTEND=noninteractive
55
RUN apt-get update && \
66
apt-get install -y git curl build-essential procps
77
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
8-
RUN [ "/bin/bash", "-c", "source $HOME/.nvm/nvm.sh && nvm i --no-progress 6 && nvm i 8 --no-progress && nvm i 10 --no-progress && nvm i --no-progress 12.2.0" ]
9-
RUN [ "/bin/bash", "-c", "source $HOME/.nvm/nvm.sh && nvm alias default 12" ]
8+
RUN [ "/bin/bash", "-c", "source $HOME/.nvm/nvm.sh && nvm i 10.19.0 --no-progress && nvm i --no-progress 12.12.0 && nvm i --no-progress 13.8.0" ]
9+
RUN [ "/bin/bash", "-c", "source $HOME/.nvm/nvm.sh && nvm alias default 13" ]
1010
ENV DEBIAN_FRONTEND=dialog

‎.eslintrc.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"jsx": false
1212
},
1313
"sourceType": "module",
14-
"ecmaVersion": 5
14+
"ecmaVersion": 2017
1515
},
1616
"rules": {
1717
"no-const-assign": "warn",
@@ -26,4 +26,4 @@
2626
"no-var": "error",
2727
"prefer-const": "error"
2828
}
29-
}
29+
}

‎.vscode/launch.json

+19-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": "node",
77
"request": "launch",
88
"program": "${workspaceRoot}/install.js",
9-
"runtimeVersion": "12.2.0",
9+
"runtimeVersion": "13.8.0",
1010
"cwd": "${workspaceRoot}",
1111
"runtimeArgs": [
1212
"--nolazy"
@@ -20,7 +20,7 @@
2020
"type": "node",
2121
"request": "launch",
2222
"program": "${workspaceRoot}/install.js",
23-
"runtimeVersion": "12.2.0",
23+
"runtimeVersion": "13.8.0",
2424
"cwd": "${workspaceRoot}",
2525
"runtimeArgs": [
2626
"--nolazy"
@@ -30,6 +30,22 @@
3030
"CHROMEDRIVER_VERSION": "LATEST"
3131
}
3232
},
33+
{
34+
"name": "Launch (with proxy)",
35+
"type": "node",
36+
"request": "launch",
37+
"program": "${workspaceRoot}/install.js",
38+
"runtimeVersion": "13.8.0",
39+
"cwd": "${workspaceRoot}",
40+
"runtimeArgs": [
41+
"--nolazy"
42+
],
43+
"env": {
44+
"NODE_ENV": "development",
45+
"https_proxy": "https://localhost:3128"
46+
// "npm_config_https_proxy": "https://localhost:3128"
47+
}
48+
},
3349
{
3450
"name": "Attach",
3551
"type": "node",
@@ -50,4 +66,4 @@
5066
"port": 5858,
5167
}
5268
]
53-
}
69+
}

‎install.js

+159-177
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
'use strict';
2+
// @ts-check
23

3-
const extractZip = require('extract-zip');
44
const fs = require('fs');
55
const helper = require('./lib/chromedriver');
6-
const request = require('request');
6+
const axios = require('axios').default;
77
const mkdirp = require('mkdirp');
88
const path = require('path');
99
const del = require('del');
1010
const child_process = require('child_process');
1111
const os = require('os');
12+
const url = require('url');
13+
const https = require('https');
14+
const { promisify } = require('util');
15+
const extractZip = promisify(require('extract-zip'));
1216
const { getChromeVersion } = require('@testim/chrome-version');
1317

1418
const skipDownload = process.env.npm_config_chromedriver_skip_download || process.env.CHROMEDRIVER_SKIP_DOWNLOAD;
@@ -23,115 +27,110 @@ const configuredfilePath = process.env.npm_config_chromedriver_filepath || proce
2327

2428
// adapt http://chromedriver.storage.googleapis.com/
2529
cdnUrl = cdnUrl.replace(/\/+$/, '');
26-
let platform = process.platform;
27-
30+
const platform = validatePlatform();
2831
const detect_chromedriver_version = process.env.npm_config_detect_chromedriver_version || process.env.DETECT_CHROMEDRIVER_VERSION;
2932
let chromedriver_version = process.env.npm_config_chromedriver_version || process.env.CHROMEDRIVER_VERSION || helper.version;
30-
if (platform === 'linux') {
31-
if (process.arch === 'arm64' || process.arch === 'x64') {
32-
platform += '64';
33-
} else {
34-
console.log('Only Linux 64 bits supported.');
35-
process.exit(1);
36-
}
37-
} else if (platform === 'darwin' || platform === 'freebsd') {
38-
if (process.arch === 'x64') {
39-
// @ts-ignore
40-
platform = 'mac64';
41-
} else {
42-
console.log('Only Mac 64 bits supported.');
43-
process.exit(1);
44-
}
45-
} else if (platform !== 'win32') {
46-
console.log('Unexpected platform or architecture:', process.platform, process.arch);
47-
process.exit(1);
48-
}
49-
let tmpPath;
50-
const chromedriverBinaryFileName = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
5133
let chromedriverBinaryFilePath;
5234
let downloadedFile = '';
5335

54-
Promise.resolve().then(function () {
55-
if (detect_chromedriver_version === 'true') {
56-
// Refer http://chromedriver.chromium.org/downloads/version-selection
57-
return getChromeVersion().then(function (chromeVersion) {
36+
(async function install() {
37+
try {
38+
if (detect_chromedriver_version === 'true') {
39+
// Refer http://chromedriver.chromium.org/downloads/version-selection
40+
const chromeVersion = await getChromeVersion();
5841
console.log("Your Chrome version is " + chromeVersion);
5942
const chromeVersionWithoutPatch = /^(.*?)\.\d+$/.exec(chromeVersion)[1];
60-
return getChromeDriverVersion(getRequestOptions(cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch));
61-
}).then(function () {
43+
await getChromeDriverVersion(getRequestOptions(cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch));
6244
console.log("Compatible ChromeDriver version is " + chromedriver_version);
63-
});
64-
}
65-
if (chromedriver_version === 'LATEST') {
66-
return getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE`));
67-
} else {
68-
const latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/);
69-
if (latestReleaseForVersionMatch) {
70-
const majorVersion = latestReleaseForVersionMatch[1];
71-
return getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`));
7245
}
73-
}
74-
})
75-
.then(() => {
76-
tmpPath = findSuitableTempDirectory();
46+
if (chromedriver_version === 'LATEST') {
47+
await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE`));
48+
} else {
49+
const latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/);
50+
if (latestReleaseForVersionMatch) {
51+
const majorVersion = latestReleaseForVersionMatch[1];
52+
await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`));
53+
}
54+
}
55+
const tmpPath = findSuitableTempDirectory();
56+
const chromedriverBinaryFileName = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
7757
chromedriverBinaryFilePath = path.resolve(tmpPath, chromedriverBinaryFileName);
78-
})
79-
.then(verifyIfChromedriverIsAvailableAndHasCorrectVersion)
80-
.then(chromedriverIsAvailable => {
81-
if (chromedriverIsAvailable) return;
82-
console.log('Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.');
83-
return downloadFile().then(extractDownload);
84-
})
85-
.then(() => copyIntoPlace(tmpPath, libPath))
86-
.then(fixFilePermissions)
87-
.then(() => console.log('Done. ChromeDriver binary available at', helper.path))
88-
.catch(function (err) {
58+
const chromedriverIsAvailable = await verifyIfChromedriverIsAvailableAndHasCorrectVersion();
59+
if (!chromedriverIsAvailable) {
60+
console.log('Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.');
61+
await downloadFile(tmpPath);
62+
await extractDownload(tmpPath);
63+
}
64+
await copyIntoPlace(tmpPath, libPath);
65+
fixFilePermissions();
66+
console.log('Done. ChromeDriver binary available at', helper.path);
67+
} catch (err) {
8968
console.error('ChromeDriver installation failed', err);
9069
process.exit(1);
91-
});
70+
}
71+
})();
72+
73+
function validatePlatform() {
74+
/** @type string */
75+
let thePlatform = process.platform;
76+
if (thePlatform === 'linux') {
77+
if (process.arch === 'arm64' || process.arch === 'x64') {
78+
thePlatform += '64';
79+
} else {
80+
console.log('Only Linux 64 bits supported.');
81+
process.exit(1);
82+
}
83+
} else if (thePlatform === 'darwin' || thePlatform === 'freebsd') {
84+
if (process.arch === 'x64') {
85+
thePlatform = 'mac64';
86+
} else {
87+
console.log('Only Mac 64 bits supported.');
88+
process.exit(1);
89+
}
90+
} else if (thePlatform !== 'win32') {
91+
console.log('Unexpected platform or architecture:', process.platform, process.arch);
92+
process.exit(1);
93+
}
94+
return thePlatform;
95+
}
9296

93-
function downloadFile() {
97+
async function downloadFile(dirToLoadTo) {
9498
if (detect_chromedriver_version !== 'true' && configuredfilePath) {
9599
downloadedFile = configuredfilePath;
96100
console.log('Using file: ', downloadedFile);
97-
return Promise.resolve();
101+
return;
98102
} else {
99103
const fileName = `chromedriver_${platform}.zip`;
100-
const tempDownloadedFile = path.resolve(tmpPath, fileName);
104+
const tempDownloadedFile = path.resolve(dirToLoadTo, fileName);
101105
downloadedFile = tempDownloadedFile;
102106
const formattedDownloadUrl = `${cdnUrl}/${chromedriver_version}/${fileName}`;
103107
console.log('Downloading from file: ', formattedDownloadUrl);
104108
console.log('Saving to file:', downloadedFile);
105-
return requestBinary(getRequestOptions(formattedDownloadUrl), downloadedFile);
109+
await requestBinary(getRequestOptions(formattedDownloadUrl), downloadedFile);
106110
}
107111
}
108112

109113
function verifyIfChromedriverIsAvailableAndHasCorrectVersion() {
110114
if (!fs.existsSync(chromedriverBinaryFilePath))
111-
return false;
115+
return Promise.resolve(false);
112116
const forceDownload = process.env.npm_config_chromedriver_force_download === 'true' || process.env.CHROMEDRIVER_FORCE_DOWNLOAD === 'true';
113117
if (forceDownload)
114-
return false;
118+
return Promise.resolve(false);
115119
console.log('ChromeDriver binary exists. Validating...');
116120
const deferred = new Deferred();
117121
try {
118122
fs.accessSync(chromedriverBinaryFilePath, fs.constants.X_OK);
119123
const cp = child_process.spawn(chromedriverBinaryFilePath, ['--version']);
120124
let str = '';
121-
cp.stdout.on('data', function (data) {
122-
str += data;
123-
});
124-
cp.on('error', function () {
125-
deferred.resolve(false);
126-
});
127-
cp.on('close', function (code) {
125+
cp.stdout.on('data', data => str += data);
126+
cp.on('error', () => deferred.resolve(false));
127+
cp.on('close', code => {
128128
if (code !== 0)
129129
return deferred.resolve(false);
130130
const parts = str.split(' ');
131131
if (parts.length < 3)
132132
return deferred.resolve(false);
133133
if (parts[1].startsWith(chromedriver_version)) {
134-
console.log(str);
135134
console.log(`ChromeDriver is already available at '${chromedriverBinaryFilePath}'.`);
136135
return deferred.resolve(true);
137136
}
@@ -169,59 +168,46 @@ function findSuitableTempDirectory() {
169168
console.log(candidatePath, 'is not writable:', e.message);
170169
}
171170
}
172-
173171
console.error('Can not find a writable tmp directory, please report issue on https://github.com/giggio/chromedriver/issues/ with as much information as possible.');
174172
process.exit(1);
175173
}
176174

177-
178175
function getRequestOptions(downloadPath) {
179-
const options = { uri: downloadPath, method: 'GET' };
180-
const protocol = options.uri.substring(0, options.uri.indexOf('//'));
181-
const proxyUrl = protocol === 'https:'
176+
/** @type import('axios').AxiosRequestConfig */
177+
const options = { url: downloadPath, method: "GET" };
178+
const urlParts = url.parse(downloadPath);
179+
const isHttps = urlParts.protocol === 'https:';
180+
const proxyUrl = isHttps
182181
? process.env.npm_config_https_proxy
183182
: (process.env.npm_config_proxy || process.env.npm_config_http_proxy);
184183
if (proxyUrl) {
185-
options.proxy = proxyUrl;
186-
}
187-
188-
options.strictSSL = !!process.env.npm_config_strict_ssl;
189-
190-
// Use certificate authority settings from npm
191-
let ca = process.env.npm_config_ca;
192-
193-
// Parse ca string like npm does
194-
if (ca && ca.match(/^".*"$/)) {
195-
try {
196-
ca = JSON.parse(ca.trim());
197-
} catch (e) {
198-
console.error('Could not parse ca string', process.env.npm_config_ca, e);
199-
}
184+
const proxyUrlParts = url.parse(proxyUrl);
185+
options.proxy = {
186+
host: proxyUrlParts.hostname,
187+
port: proxyUrlParts.port ? parseInt(proxyUrlParts.port) : 80,
188+
protocol: proxyUrlParts.protocol
189+
};
200190
}
201191

202-
if (!ca && process.env.npm_config_cafile) {
203-
try {
204-
ca = fs.readFileSync(process.env.npm_config_cafile, { encoding: 'utf8' })
205-
.split(/\n(?=-----BEGIN CERTIFICATE-----)/g);
206-
207-
// Comments at the beginning of the file result in the first
208-
// item not containing a certificate - in this case the
209-
// download will fail
210-
if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) {
211-
ca.shift();
192+
if (isHttps) {
193+
// Use certificate authority settings from npm
194+
let ca = process.env.npm_config_ca;
195+
if (ca)
196+
console.log('Using npmconf ca.');
197+
198+
if (!ca && process.env.npm_config_cafile) {
199+
try {
200+
ca = fs.readFileSync(process.env.npm_config_cafile, { encoding: 'utf8' });
201+
} catch (e) {
202+
console.error('Could not read cafile', process.env.npm_config_cafile, e);
212203
}
213-
214-
} catch (e) {
215-
console.error('Could not read cafile', process.env.npm_config_cafile, e);
204+
console.log('Using npmconf cafile.');
216205
}
217-
}
218206

219-
if (ca) {
220-
console.log('Using npmconf ca');
221-
options.agentOptions = {
207+
options.httpsAgent = new https.Agent({
208+
rejectUnauthorized: !!process.env.npm_config_strict_ssl,
222209
ca: ca
223-
};
224-
options.ca = ca;
210+
});
225211
}
226212

227213
// Use specific User-Agent
@@ -232,94 +218,90 @@ function getRequestOptions(downloadPath) {
232218
return options;
233219
}
234220

235-
function getChromeDriverVersion(requestOptions) {
236-
const deferred = new Deferred();
237-
request(requestOptions, function (err, response, data) {
238-
if (err) {
239-
deferred.reject('Error with http(s) request: ' + err);
240-
} else {
241-
chromedriver_version = data.trim();
242-
deferred.resolve(true);
243-
}
244-
});
245-
return deferred.promise;
221+
/**
222+
*
223+
* @param {import('axios').AxiosRequestConfig} requestOptions
224+
*/
225+
async function getChromeDriverVersion(requestOptions) {
226+
console.log('Finding Chromedriver version.');
227+
const response = await axios(requestOptions);
228+
chromedriver_version = response.data.trim();
229+
console.log(`Chromedriver version is ${chromedriver_version}.`);
246230
}
247231

248-
function requestBinary(requestOptions, filePath) {
249-
const deferred = new Deferred();
250-
232+
/**
233+
*
234+
* @param {import('axios').AxiosRequestConfig} requestOptions
235+
* @param {string} filePath
236+
*/
237+
async function requestBinary(requestOptions, filePath) {
238+
const outFile = fs.createWriteStream(filePath);
239+
let response;
240+
try {
241+
response = await axios.create(requestOptions)({ responseType: 'stream' });
242+
} catch (error) {
243+
if (error && error.response) {
244+
if (error.response.status)
245+
console.error('Error status code:', error.response.status);
246+
if (error.response.data) {
247+
error.response.data.on('data', data => console.error(data.toString('utf8')));
248+
await new Promise((resolve) => {
249+
error.response.data.on('finish', resolve);
250+
error.response.data.on('error', resolve);
251+
});
252+
}
253+
}
254+
throw new Error('Error with http(s) request: ' + error);
255+
}
251256
let count = 0;
252257
let notifiedCount = 0;
253-
const outFile = fs.openSync(filePath, 'w');
254-
255-
const client = request(requestOptions);
256-
257-
client.on('error', function (err) {
258-
deferred.reject('Error with http(s) request: ' + err);
259-
});
260-
261-
client.on('data', function (data) {
262-
fs.writeSync(outFile, data, 0, data.length, null);
258+
response.data.on('data', data => {
263259
count += data.length;
264-
if ((count - notifiedCount) > 800000) {
260+
if ((count - notifiedCount) > 1024 * 1024) {
265261
console.log('Received ' + Math.floor(count / 1024) + 'K...');
266262
notifiedCount = count;
267263
}
268264
});
269-
270-
client.on('end', function () {
271-
console.log('Received ' + Math.floor(count / 1024) + 'K total.');
272-
fs.closeSync(outFile);
273-
deferred.resolve(true);
265+
response.data.on('end', () => console.log('Received ' + Math.floor(count / 1024) + 'K total.'));
266+
const pipe = response.data.pipe(outFile);
267+
await new Promise((resolve, reject) => {
268+
pipe.on('finish', resolve);
269+
pipe.on('error', reject);
274270
});
275-
276-
return deferred.promise;
277271
}
278272

279-
function extractDownload() {
273+
async function extractDownload(dirToExtractTo) {
280274
if (path.extname(downloadedFile) !== '.zip') {
281275
fs.copyFileSync(downloadedFile, chromedriverBinaryFilePath);
282276
console.log('Skipping zip extraction - binary file found.');
283-
return Promise.resolve();
277+
return;
284278
}
285-
const deferred = new Deferred();
286279
console.log('Extracting zip contents');
287-
extractZip(path.resolve(downloadedFile), { dir: tmpPath }, function (err) {
288-
if (err) {
289-
deferred.reject('Error extracting archive: ' + err);
290-
} else {
291-
deferred.resolve(true);
292-
}
293-
});
294-
return deferred.promise;
280+
try {
281+
await extractZip(path.resolve(downloadedFile), { dir: dirToExtractTo });
282+
} catch (error) {
283+
throw new Error('Error extracting archive: ' + error);
284+
}
295285
}
296286

297-
298-
function copyIntoPlace(originPath, targetPath) {
299-
return del(targetPath)
300-
.then(function () {
301-
console.log("Copying to target path", targetPath);
302-
fs.mkdirSync(targetPath);
303-
304-
// Look for the extracted directory, so we can rename it.
305-
const files = fs.readdirSync(originPath);
306-
const promises = files.map(function (name) {
307-
const deferred = new Deferred();
308-
309-
const file = path.join(originPath, name);
310-
const reader = fs.createReadStream(file);
311-
312-
const targetFile = path.join(targetPath, name);
313-
const writer = fs.createWriteStream(targetFile);
314-
writer.on("close", function () {
315-
deferred.resolve(true);
316-
});
317-
318-
reader.pipe(writer);
319-
return deferred.promise;
320-
});
321-
return Promise.all(promises);
287+
async function copyIntoPlace(originPath, targetPath) {
288+
await del(targetPath);
289+
console.log("Copying to target path", targetPath);
290+
fs.mkdirSync(targetPath);
291+
292+
// Look for the extracted directory, so we can rename it.
293+
const files = fs.readdirSync(originPath);
294+
const promises = files.map(name => {
295+
return new Promise((resolve) => {
296+
const file = path.join(originPath, name);
297+
const reader = fs.createReadStream(file);
298+
const targetFile = path.join(targetPath, name);
299+
const writer = fs.createWriteStream(targetFile);
300+
writer.on("close", () => resolve());
301+
reader.pipe(writer);
322302
});
303+
});
304+
await Promise.all(promises);
323305
}
324306

325307

‎package-lock.json

+224-404
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chromedriver",
3-
"version": "80.0.0",
3+
"version": "80.0.1",
44
"keywords": [
55
"chromedriver",
66
"selenium"
@@ -27,10 +27,10 @@
2727
},
2828
"dependencies": {
2929
"@testim/chrome-version": "^1.0.7",
30-
"del": "^4.1.1",
30+
"axios": "^0.19.2",
31+
"del": "^5.1.0",
3132
"extract-zip": "^1.6.7",
32-
"mkdirp": "^0.5.1",
33-
"request": "^2.88.0",
33+
"mkdirp": "^1.0.3",
3434
"tcp-port-used": "^1.0.1"
3535
}
3636
}

2 commit comments

Comments
 (2)

byang183 commented on Mar 6, 2020

@byang183

Per issue from axios, if the installation happens within a corp environment, which force to use proxy, the installation will fail.

axios/axios#925

byang183 commented on Mar 6, 2020

@byang183

Just to clarify, same config works for 80.0.0, and get 400 from proxy server at 80.0.1

This conversation has been locked and limited to collaborators.