d11 theme
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

340 lines
12 KiB

'use strict';
// @ts-check
const fs = require('fs');
const helper = require('./lib/chromedriver');
const axios = require('axios').default;
const mkdirp = require('mkdirp');
const path = require('path');
const del = require('del');
const child_process = require('child_process');
const os = require('os');
const url = require('url');
const https = require('https');
const extractZip = require('extract-zip');
const { getChromeVersion } = require('@testim/chrome-version');
const HttpsProxyAgent = require('https-proxy-agent');
const getProxyForUrl = require("proxy-from-env").getProxyForUrl;
const skipDownload = process.env.npm_config_chromedriver_skip_download || process.env.CHROMEDRIVER_SKIP_DOWNLOAD;
if (skipDownload === 'true') {
console.log('Found CHROMEDRIVER_SKIP_DOWNLOAD variable, skipping installation.');
process.exit(0);
}
const libPath = path.join(__dirname, 'lib', 'chromedriver');
let cdnUrl = process.env.npm_config_chromedriver_cdnurl || process.env.CHROMEDRIVER_CDNURL || 'https://chromedriver.storage.googleapis.com';
const configuredfilePath = process.env.npm_config_chromedriver_filepath || process.env.CHROMEDRIVER_FILEPATH;
// adapt http://chromedriver.storage.googleapis.com/
cdnUrl = cdnUrl.replace(/\/+$/, '');
const platform = validatePlatform();
const detect_chromedriver_version = process.env.npm_config_detect_chromedriver_version || process.env.DETECT_CHROMEDRIVER_VERSION;
let chromedriver_version = process.env.npm_config_chromedriver_version || process.env.CHROMEDRIVER_VERSION || helper.version;
let chromedriverBinaryFilePath;
let downloadedFile = '';
(async function install() {
try {
if (detect_chromedriver_version === 'true') {
// Refer http://chromedriver.chromium.org/downloads/version-selection
const chromeVersion = await getChromeVersion();
console.log("Your Chrome version is " + chromeVersion);
const chromeVersionWithoutPatch = /^(.*?)\.\d+$/.exec(chromeVersion)[1];
await getChromeDriverVersion(getRequestOptions(cdnUrl + '/LATEST_RELEASE_' + chromeVersionWithoutPatch));
console.log("Compatible ChromeDriver version is " + chromedriver_version);
}
if (chromedriver_version === 'LATEST') {
await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE`));
} else {
const latestReleaseForVersionMatch = chromedriver_version.match(/LATEST_(\d+)/);
if (latestReleaseForVersionMatch) {
const majorVersion = latestReleaseForVersionMatch[1];
await getChromeDriverVersion(getRequestOptions(`${cdnUrl}/LATEST_RELEASE_${majorVersion}`));
}
}
const tmpPath = findSuitableTempDirectory();
const chromedriverBinaryFileName = process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
chromedriverBinaryFilePath = path.resolve(tmpPath, chromedriverBinaryFileName);
const chromedriverIsAvailable = await verifyIfChromedriverIsAvailableAndHasCorrectVersion();
if (!chromedriverIsAvailable) {
console.log('Current existing ChromeDriver binary is unavailable, proceeding with download and extraction.');
await downloadFile(tmpPath);
await extractDownload(tmpPath);
}
await copyIntoPlace(tmpPath, libPath);
fixFilePermissions();
console.log('Done. ChromeDriver binary available at', helper.path);
} catch (err) {
console.error('ChromeDriver installation failed', err);
process.exit(1);
}
})();
function validatePlatform() {
/** @type string */
let thePlatform = process.platform;
if (thePlatform === 'linux') {
if (process.arch === 'arm64' || process.arch === 'x64') {
thePlatform += '64';
} else {
console.log('Only Linux 64 bits supported.');
process.exit(1);
}
} else if (thePlatform === 'darwin' || thePlatform === 'freebsd') {
if (process.arch === 'x64' || process.arch === 'arm64') {
thePlatform = 'mac64';
} else {
console.log('Only Mac 64 bits supported.');
process.exit(1);
}
} else if (thePlatform !== 'win32') {
console.log('Unexpected platform or architecture:', process.platform, process.arch);
process.exit(1);
}
return thePlatform;
}
async function downloadFile(dirToLoadTo) {
if (detect_chromedriver_version !== 'true' && configuredfilePath) {
downloadedFile = configuredfilePath;
console.log('Using file: ', downloadedFile);
return;
} else {
const fileName = `chromedriver_${platform}.zip`;
const tempDownloadedFile = path.resolve(dirToLoadTo, fileName);
downloadedFile = tempDownloadedFile;
const formattedDownloadUrl = `${cdnUrl}/${chromedriver_version}/${fileName}`;
console.log('Downloading from file: ', formattedDownloadUrl);
console.log('Saving to file:', downloadedFile);
await requestBinary(getRequestOptions(formattedDownloadUrl), downloadedFile);
}
}
function verifyIfChromedriverIsAvailableAndHasCorrectVersion() {
if (!fs.existsSync(chromedriverBinaryFilePath))
return Promise.resolve(false);
const forceDownload = process.env.npm_config_chromedriver_force_download === 'true' || process.env.CHROMEDRIVER_FORCE_DOWNLOAD === 'true';
if (forceDownload)
return Promise.resolve(false);
console.log('ChromeDriver binary exists. Validating...');
const deferred = new Deferred();
try {
fs.accessSync(chromedriverBinaryFilePath, fs.constants.X_OK);
const cp = child_process.spawn(chromedriverBinaryFilePath, ['--version']);
let str = '';
cp.stdout.on('data', data => str += data);
cp.on('error', () => deferred.resolve(false));
cp.on('close', code => {
if (code !== 0)
return deferred.resolve(false);
const parts = str.split(' ');
if (parts.length < 3)
return deferred.resolve(false);
if (parts[1].startsWith(chromedriver_version)) {
console.log(`ChromeDriver is already available at '${chromedriverBinaryFilePath}'.`);
return deferred.resolve(true);
}
deferred.resolve(false);
});
}
catch (error) {
deferred.resolve(false);
}
return deferred.promise;
}
function findSuitableTempDirectory() {
const now = Date.now();
const candidateTmpDirs = [
process.env.npm_config_tmp,
process.env.XDG_CACHE_HOME,
// Platform specific default, including TMPDIR/TMP/TEMP env
os.tmpdir(),
path.join(process.cwd(), 'tmp')
];
for (let i = 0; i < candidateTmpDirs.length; i++) {
if (!candidateTmpDirs[i]) continue;
// Prevent collision with other versions in the dependency tree
const namespace = chromedriver_version;
const candidatePath = path.join(candidateTmpDirs[i], namespace, 'chromedriver');
try {
mkdirp.sync(candidatePath, '0777');
const testFile = path.join(candidatePath, now + '.tmp');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
return candidatePath;
} catch (e) {
console.log(candidatePath, 'is not writable:', e.message);
}
}
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.');
process.exit(1);
}
function getRequestOptions(downloadPath) {
/** @type import('axios').AxiosRequestConfig */
const options = { url: downloadPath, method: "GET" };
const urlParts = url.parse(downloadPath);
const isHttps = urlParts.protocol === 'https:';
const proxyUrl = getProxyForUrl(downloadPath);
if (proxyUrl) {
const proxyUrlParts = url.parse(proxyUrl);
options.proxy = {
host: proxyUrlParts.hostname,
port: proxyUrlParts.port ? parseInt(proxyUrlParts.port) : 80,
protocol: proxyUrlParts.protocol
};
}
if (isHttps) {
// Use certificate authority settings from npm
let ca = process.env.npm_config_ca;
if (ca)
console.log('Using npmconf ca.');
if (!ca && process.env.npm_config_cafile) {
try {
ca = fs.readFileSync(process.env.npm_config_cafile, { encoding: 'utf8' });
} catch (e) {
console.error('Could not read cafile', process.env.npm_config_cafile, e);
}
console.log('Using npmconf cafile.');
}
if (proxyUrl) {
console.log('Using workaround for https-url combined with a proxy.');
const httpsProxyAgentOptions = url.parse(proxyUrl);
// @ts-ignore
httpsProxyAgentOptions.ca = ca;
// @ts-ignore
httpsProxyAgentOptions.rejectUnauthorized = !!process.env.npm_config_strict_ssl;
// @ts-ignore
options.httpsAgent = new HttpsProxyAgent(httpsProxyAgentOptions);
options.proxy = false;
} else {
options.httpsAgent = new https.Agent({
rejectUnauthorized: !!process.env.npm_config_strict_ssl,
ca: ca
});
}
}
// Use specific User-Agent
if (process.env.npm_config_user_agent) {
options.headers = { 'User-Agent': process.env.npm_config_user_agent };
}
return options;
}
/**
*
* @param {import('axios').AxiosRequestConfig} requestOptions
*/
async function getChromeDriverVersion(requestOptions) {
console.log('Finding Chromedriver version.');
const response = await axios(requestOptions);
chromedriver_version = response.data.trim();
console.log(`Chromedriver version is ${chromedriver_version}.`);
}
/**
*
* @param {import('axios').AxiosRequestConfig} requestOptions
* @param {string} filePath
*/
async function requestBinary(requestOptions, filePath) {
const outFile = fs.createWriteStream(filePath);
let response;
try {
response = await axios({ responseType: 'stream', ...requestOptions });
} catch (error) {
if (error && error.response) {
if (error.response.status)
console.error('Error status code:', error.response.status);
if (error.response.data) {
error.response.data.on('data', data => console.error(data.toString('utf8')));
await new Promise((resolve) => {
error.response.data.on('finish', resolve);
error.response.data.on('error', resolve);
});
}
}
throw new Error('Error with http(s) request: ' + error);
}
let count = 0;
let notifiedCount = 0;
response.data.on('data', data => {
count += data.length;
if ((count - notifiedCount) > 1024 * 1024) {
console.log('Received ' + Math.floor(count / 1024) + 'K...');
notifiedCount = count;
}
});
response.data.on('end', () => console.log('Received ' + Math.floor(count / 1024) + 'K total.'));
const pipe = response.data.pipe(outFile);
await new Promise((resolve, reject) => {
pipe.on('finish', resolve);
pipe.on('error', reject);
});
}
async function extractDownload(dirToExtractTo) {
if (path.extname(downloadedFile) !== '.zip') {
fs.copyFileSync(downloadedFile, chromedriverBinaryFilePath);
console.log('Skipping zip extraction - binary file found.');
return;
}
console.log(`Extracting zip contents to ${dirToExtractTo}.`);
try {
await extractZip(path.resolve(downloadedFile), { dir: dirToExtractTo });
} catch (error) {
throw new Error('Error extracting archive: ' + error);
}
}
async function copyIntoPlace(originPath, targetPath) {
await del(targetPath, { force: true });
console.log("Copying to target path", targetPath);
fs.mkdirSync(targetPath);
// Look for the extracted directory, so we can rename it.
const files = fs.readdirSync(originPath);
const promises = files.map(name => {
return new Promise((resolve) => {
const file = path.join(originPath, name);
const reader = fs.createReadStream(file);
const targetFile = path.join(targetPath, name);
const writer = fs.createWriteStream(targetFile);
writer.on("close", () => resolve());
reader.pipe(writer);
});
});
await Promise.all(promises);
}
function fixFilePermissions() {
// Check that the binary is user-executable and fix it if it isn't (problems with unzip library)
if (process.platform != 'win32') {
const stat = fs.statSync(helper.path);
// 64 == 0100 (no octal literal in strict mode)
if (!(stat.mode & 64)) {
console.log('Fixing file permissions.');
fs.chmodSync(helper.path, '755');
}
}
}
function Deferred() {
this.resolve = null;
this.reject = null;
this.promise = new Promise(function (resolve, reject) {
this.resolve = resolve;
this.reject = reject;
}.bind(this));
Object.freeze(this);
}