UNPKG

selenium-standalone

Version:

installs a `selenium-standalone` command line to install and start a standalone selenium server

323 lines (272 loc) 8.9 kB
/* eslint-disable no-shadow */ module.exports = install; const { createWriteStream } = require('fs'); const { readFile, writeFile } = require('fs').promises; const path = require('path'); const got = require('got'); const merge = require('lodash.merge'); const mapValues = require('lodash.mapvalues'); const computeDownloadUrls = require('./compute-download-urls'); const computeFsPaths = require('./compute-fs-paths'); const defaultConfig = require('./default-config')(); const noop = require('./noop'); const { uncompressDownloadedFile, asyncLogEnd, createDirs, setDriverFilePermissions, logInstallSummary, isUpToDate, getTempFileName, uncompressGzippedFile, runInstaller, } = require('./install-utils'); const { checkArgs } = require('./check-args'); const { logError } = require('./log-error'); /** * used ONLY to deal with progress bar. * Only one download bar can be shown in the same time. */ const downloadStreams = new Map(); async function install(_opts) { const opts = checkArgs('Install API', _opts); const logger = opts.logger || noop; if (!opts.baseURL) { opts.baseURL = defaultConfig.baseURL; } if (!opts.version) { opts.version = defaultConfig.version; } if (opts.drivers) { // Merge in missing driver options for those specified opts.drivers = mapValues(opts.drivers, (config, name) => { return merge({}, defaultConfig.drivers[name], config); }); } else { opts.drivers = defaultConfig.drivers; } if (opts.singleDriverInstall) { const singleDriver = opts.drivers[opts.singleDriverInstall]; if (singleDriver) { opts.drivers = {}; opts.drivers[opts.singleDriverInstall] = singleDriver; } } if (process.platform !== 'win32') { delete opts.drivers.ie; delete opts.drivers.edge; } const requestOpts = Object.assign({ timeout: 90000 }, opts.requestOpts); if (opts.proxy) { requestOpts.proxy = opts.proxy; } opts.progressCb = opts.progressCb || noop; logger('----------'); logger('selenium-standalone installation starting'); logger('----------'); logger(''); const fsPaths = computeFsPaths({ seleniumVersion: opts.version, drivers: opts.drivers, basePath: opts.basePath, }); const urls = await computeDownloadUrls({ seleniumVersion: opts.version, seleniumBaseURL: opts.baseURL, seleniumFullURL: opts.fullURL, drivers: opts.drivers, }); logInstallSummary(logger, fsPaths, urls); const tasks = [ createDirs.bind(null, fsPaths), download.bind(null, { urls: urls, fsPaths: fsPaths, }), asyncLogEnd.bind(null, logger), ]; Object.values(fsPaths) .filter(({ requireChmod }) => requireChmod) .forEach(({ installPath }) => { tasks.push(setDriverFilePermissions.bind(null, installPath)); }); // tasks should run one by one for (const t of tasks) { await t(); } async function onlyInstallMissingFiles(opts) { let etag; try { etag = await readFile(`${opts.to}.etag`); } catch (err) { // ENOENT means not found which is ok. But anything else re-raise if (err.code != 'ENOENT') throw err; } const isLatest = await isUpToDate(opts.from, requestOpts, etag); // File already exists. Prevent download/installation. if (isLatest) { logger('---'); logger('File from ' + opts.from + ' has already been downloaded'); return; } return opts.installer({ to: opts.to, from: opts.from, }); } async function download(opts) { const installers = [ { installer: installSelenium, from: opts.urls.selenium, to: opts.fsPaths.selenium.downloadPath, }, ]; if (opts.fsPaths.chrome) { installers.push({ installer: installChromeDr, from: opts.urls.chrome, to: opts.fsPaths.chrome.downloadPath, }); } if (process.platform === 'win32' && opts.fsPaths.ie) { installers.push({ installer: installIeDr, from: opts.urls.ie, to: opts.fsPaths.ie.downloadPath, }); } if (process.platform === 'win32' && opts.fsPaths.edge) { installers.push({ installer: installEdgeDr, from: opts.urls.edge, to: opts.fsPaths.edge.downloadPath, }); } if (opts.fsPaths.firefox) { installers.push({ installer: installFirefoxDr, from: opts.urls.firefox, to: opts.fsPaths.firefox.downloadPath, }); } if (opts.fsPaths.chromiumedge) { installers.push({ installer: installChromiumEdgeDr, from: opts.urls.chromiumedge, to: opts.fsPaths.chromiumedge.downloadPath, }); } return Promise.all(installers.map((i) => onlyInstallMissingFiles(i))); } function installSelenium(opts) { return installSingleFile(opts.from, opts.to); } function installEdgeDr(opts) { if (path.extname(opts.from) === '.msi') { return downloadInstallerFile(opts.from, opts.to); } return installSingleFile(opts.from, opts.to); } async function installSingleFile(from, to) { const stream = await getDownloadStream(from); return writeDownloadStream(stream, to); } async function downloadInstallerFile(from, to) { if (process.platform !== 'win32') { throw new Error('Could not install an `msi` file on the current platform'); } const stream = await getDownloadStream(from); const installerFile = getTempFileName('installer.msi'); await writeDownloadStream(stream, installerFile, `${to}.etag`); return runInstaller(installerFile, from, to); } function installChromeDr(opts) { return installZippedFile(opts.from, opts.to); } function installIeDr(opts) { return installZippedFile(opts.from, opts.to); } function installFirefoxDr(opts) { // only windows build is a zip if (path.extname(opts.from) === '.zip') { return installZippedFile(opts.from, opts.to); } return installGzippedFile(opts.from, opts.to); } function installChromiumEdgeDr(opts) { return installZippedFile(opts.from, opts.to); } async function installGzippedFile(from, to) { const stream = await getDownloadStream(from); await writeDownloadStream(stream, to); return uncompressGzippedFile(from, to); } async function installZippedFile(from, to) { const stream = await getDownloadStream(from); await writeDownloadStream(stream, to); return uncompressDownloadedFile(to); } async function getDownloadStream(downloadUrl) { let prevTransferred = 0; const downloadStream = got.stream(downloadUrl, requestOpts); return await new Promise((resolve, reject) => { downloadStream .once('response', () => { downloadStream.on('downloadProgress', ({ transferred, total }) => { const active = isDownloadActive(); if (active) { downloadStreams.set(downloadStream, true); } if (downloadStreams.get(downloadStream)) { opts.progressCb(total, transferred, transferred - prevTransferred, downloadUrl, active); } prevTransferred = transferred; }); }) .once('finish', () => { resolve(downloadStream); }) .once('end', () => { downloadStreams.delete(downloadStream); }) .once('error', (err) => { if (err.code === 'ERR_NON_2XX_3XX_RESPONSE' && downloadUrl.includes('edge')) { reject( logError( 'getDownloadStream', err, 'It may be due to the specified edge driver version ' + downloadUrl.split('/')[3] + ' is unavailable for current platform. Try downloading a different version of edge driver ' ) ); } else { reject(logError('getDownloadStream', err, 'Could not download ' + downloadUrl)); } throw new Error('Could not download ' + downloadUrl); }); }); } async function writeDownloadStream(stream, to, etagPath = `${to}.etag`) { const writeEtagPromise = new Promise((resolve, reject) => { stream.once('response', async (response) => { try { await writeFile(etagPath, response.headers.etag); resolve(); } catch (err) { reject(err); } }); }); const writeFilePromise = new Promise((resolve, reject) => { const writeStream = createWriteStream(to).once('error', (err) => reject(logError('writeDownloadStream', err))); stream.pipe(writeStream); writeStream.once('finish', resolve); }); return Promise.all([writeEtagPromise, writeFilePromise]); } } function isDownloadActive() { return !Array.from(downloadStreams.values()).includes(true); }