diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e9e783fc..b7ce709d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [12, 14, 16, 18, 20] + node-version: [14, 16, 18, 20] java-version: [11] include: - os: macos-latest diff --git a/HISTORY.md b/HISTORY.md index 6e6aa262..2573b515 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,7 @@ +# 10.0.0 (2024-06-24) +* BREAKING CHANGE: dropped node12 support, minimum version is node14 +* added proxy complience + # 9.2.3 (2023-10-21) * fixed onCancel bug with got diff --git a/README.md b/README.md index f865cee1..15deb025 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Node.js Selenium Standalone [![Test](https://github.com/webdriverio/selenium-standalone/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/webdriverio/selenium-standalone/actions/workflows/test.yml) ![Supported node versions](https://img.shields.io/badge/node-12%2C%2013%2C%2014%2C%2015%2C%2016%2C%2017%2C%2018%2C%2019%2C%2020-green) +Node.js Selenium Standalone [![Test](https://github.com/webdriverio/selenium-standalone/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/webdriverio/selenium-standalone/actions/workflows/test.yml) ![Supported node versions](https://img.shields.io/badge/node-%2014%2C%2016%2C%2018%2C%2020-green) =========================== > A node based CLI library for launching [Selenium](http://www.seleniumhq.org/download/) with [WebDriver](https://w3c.github.io/webdriver/) support. diff --git a/docs/API.md b/docs/API.md index 330dd034..4965afd4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -139,3 +139,7 @@ since 9.1.0 it's been checked and killed automatically ## Set `selenium-standalone` Version as NodeJS environment parameter You can set any version by `process.env.SELENIUM_VERSION=3.141.59` before starting selenium-standalone. Default values are here: [lib/default-config.js](../lib/default-config.js) + +## Use the package behind corparate proxy + +Should be specified `process.env.HTTP_PROXY=http://proxy-url:port` and `process.env.HTTPS_PROXY=http://proxy-url:port` or the same system envirement variables `HTTP_PROXY=http://proxy-url:port`, `HTTPS_PROXY=http://proxy-url:port` diff --git a/lib/check-started.js b/lib/check-started.js index d9e801b1..6d795d32 100644 --- a/lib/check-started.js +++ b/lib/check-started.js @@ -2,6 +2,7 @@ module.exports = checkStarted; const { default: got } = require('got'); const { sleep } = require('./delay'); +const { getProxtAgent } = require('./proxyManager'); async function checkStarted(selenium, seleniumStatusUrl) { const cpState = { @@ -49,7 +50,7 @@ async function checkStarted(selenium, seleniumStatusUrl) { try { // server has one minute to start - await got(seleniumStatusUrl, gotOptions); + await got(seleniumStatusUrl, { ...getProxtAgent(seleniumStatusUrl), ...gotOptions }); selenium.removeListener('exit', errorIfNeverStarted); return null; } catch (err) { diff --git a/lib/compute-download-urls.js b/lib/compute-download-urls.js index 16de7a96..cee8e348 100644 --- a/lib/compute-download-urls.js +++ b/lib/compute-download-urls.js @@ -10,6 +10,8 @@ const { getIeDriverArchitectureOld, getFirefoxDriverArchitectureOld, } = require('./platformDetection'); +const microsoftEdgeReleases = require('./microsoft-edge-releases'); +const { getProxtAgent } = require('./proxyManager'); const urls = { selenium: '%s/%s/selenium-server-standalone-%s.jar', @@ -188,8 +190,6 @@ async function computeDownloadUrls(options) { return downloadUrls; } -const microsoftEdgeReleases = require('./microsoft-edge-releases'); - function getEdgeDriverUrl(version) { const release = microsoftEdgeReleases[version]; if (!release) { @@ -209,7 +209,7 @@ async function chromiumEdgeBundleAvailable(opts) { getChromiumEdgeDriverArchitectureOld(opts.drivers.chromiumedge.platform, opts.drivers.chromiumedge.version) ); try { - await got.head(url, { timeout: 10000 }); + await got.head(url, { ...getProxtAgent(url), timeout: 10000 }); } catch (_) { return false; } @@ -247,7 +247,7 @@ async function resolveLatestVersion(opts, browserDriver, url) { async function getLatestChromium(opts, browserDriver, url) { try { - const response = await got(url, { timeout: 10000 }); + const response = await got(url, { ...getProxtAgent(url), timeout: 10000 }); // edgewebdriver latest version file contains invalid characters const version = response.body.replace(/\r|\n/g, '').replace(/[^\d|.]/g, ''); @@ -260,10 +260,10 @@ async function getLatestChromium(opts, browserDriver, url) { } async function getLatestGeckodriver(opts, browserDriver, url) { - const response = await got(url, { timeout: 10000, responseType: 'json' }); - if (typeof response.body.name === 'string' && response.body.name) { + const response = await got(url, { ...getProxtAgent(url), timeout: 10000 }).json(); + if (typeof response.name === 'string' && response.name) { // eslint-disable-next-line no-param-reassign - opts.drivers[browserDriver].version = response.body.name; + opts.drivers[browserDriver].version = response.name; return true; } } @@ -277,15 +277,18 @@ function resolveDownloadPath(platform, buildId) { } async function getLastChromedriverVersionFromMajor(version) { + const url = 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json'; const response = await got({ - method: 'get', - url: 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json', - responseType: 'json', - headers: { - 'Content-Type': 'application/json', + ...getProxtAgent(url), + ...{ + method: 'get', + url: url, + headers: { + 'Content-Type': 'application/json', + }, }, - }); - const versionsWithMajor = response.body.versions.filter( + }).json(); + const versionsWithMajor = response.versions.filter( (f) => validateMajorVersionPrefix(f.version) === validateMajorVersionPrefix(version) && 'chromedriver' in f.downloads ); @@ -299,18 +302,21 @@ async function getLastChromedriverVersionFromMajor(version) { } async function getLatestChromeVersion(possibleChanel) { + const url = 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json'; const response = await got({ - method: 'get', - url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json', - responseType: 'json', - headers: { - 'Content-Type': 'application/json', + ...getProxtAgent(url), + ...{ + method: 'get', + url: url, + headers: { + 'Content-Type': 'application/json', + }, }, - }); - const channel = Object.keys(response.body.channels).find((i) => i.toLowerCase() === possibleChanel.toLowerCase()); + }).json(); + const channel = Object.keys(response.channels).find((i) => i.toLowerCase() === possibleChanel.toLowerCase()); try { - return response.body.channels[channel].version; + return response.channels[channel].version; } catch (err) { console.log(); throw new Error(`channel can't be - ${possibleChanel}, possible only Stable, Beta, Dev, Canary`); diff --git a/lib/install-utils.js b/lib/install-utils.js index aefce34a..d8c21698 100644 --- a/lib/install-utils.js +++ b/lib/install-utils.js @@ -9,6 +9,7 @@ const { default: got } = require('got'); const debug = require('debug')('selenium-standalone:install'); const { logError } = require('./log-error'); const md5 = require('md5'); +const { getProxtAgent } = require('./proxyManager'); const installers = ['selenium', 'chrome', 'ie', 'firefox', 'edge', 'chromiumedge']; @@ -107,6 +108,7 @@ async function isUpToDate(url, file, pathToFile) { } try { const response = await got.head(url, { + ...getProxtAgent(url), timeout: 2500, }); if (response.headers['content-length'] === `${fs.statSync(pathToFile).size}`) { diff --git a/lib/install.js b/lib/install.js index a5252fd9..d9a789fc 100644 --- a/lib/install.js +++ b/lib/install.js @@ -25,6 +25,7 @@ const { } = require('./install-utils'); const { checkArgs } = require('./check-args'); const { logError } = require('./log-error'); +const { getProxtAgent } = require('./proxyManager'); /** * used ONLY to deal with progress bar. @@ -287,7 +288,7 @@ async function install(_opts) { async function getDownloadStream(downloadUrl) { let prevTransferred = 0; - const downloadStream = got.stream(downloadUrl, { ...requestOpts, isStream: true }); + const downloadStream = got.stream(downloadUrl, { ...getProxtAgent(downloadUrl), ...requestOpts, isStream: true }); return await new Promise((resolve, reject) => { downloadStream .once('response', () => { diff --git a/lib/proxyManager.js b/lib/proxyManager.js new file mode 100644 index 00000000..da6a917a --- /dev/null +++ b/lib/proxyManager.js @@ -0,0 +1,19 @@ +const { HttpsProxyAgent } = require('https-proxy-agent'); +const { HttpProxyAgent } = require('http-proxy-agent'); +/** + * Returns proxy agent if exist. + * @param {string} url + * @returns {any} + */ +function getProxtAgent(url) { + if (url.startsWith('http:') && process.env.HTTP_PROXY) { + const httpProxyAgent = new HttpProxyAgent(process.env.HTTP_PROXY); + return { agent: { http: httpProxyAgent } }; + } + if (url.startsWith('https:') && process.env.HTTPS_PROXY) { + const httpProxyAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); + return { agent: { https: httpProxyAgent } }; + } + return {}; +} +module.exports = { getProxtAgent }; diff --git a/package-lock.json b/package-lock.json index 0000af6d..f5caefde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,8 @@ "find-process": "^1.4.7", "fkill": "^7.2.1", "got": "^11.8.6", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "is-port-reachable": "^3.0.0", "lodash.mapvalues": "^4.6.0", "lodash.merge": "^4.6.2", @@ -53,7 +55,7 @@ "typescript": "^5.4.2" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1295,7 +1297,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, "dependencies": { "debug": "^4.3.4" }, @@ -4045,7 +4046,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -4070,7 +4070,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -10613,7 +10612,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, "requires": { "debug": "^4.3.4" } @@ -12524,7 +12522,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "requires": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -12543,7 +12540,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "4" diff --git a/package.json b/package.json index 0073c366..516432d0 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,11 @@ "release:patch": "npm run release -- patch", "release:minor": "npm run release -- minor", "release:major": "npm run release -- major", - "lint": "eslint --ignore-path .gitignore ." + "lint": "eslint --ignore-path .gitignore .", + "lint:fix": "eslint --fix --ignore-path .gitignore ." }, "engines": { - "node": ">=12" + "node": ">=14" }, "bin": { "selenium-standalone": "./bin/selenium-standalone" @@ -49,6 +50,8 @@ "find-process": "^1.4.7", "fkill": "^7.2.1", "got": "^11.8.6", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "is-port-reachable": "^3.0.0", "lodash.mapvalues": "^4.6.0", "lodash.merge": "^4.6.2", diff --git a/test/no-reinstall.js b/test/no-reinstall.js index 17d5778c..e6635433 100644 --- a/test/no-reinstall.js +++ b/test/no-reinstall.js @@ -5,44 +5,47 @@ const selenium = require('..'); const targetDir = path.join(__dirname, '..', '.selenium'); -describe('when files are installed', () => { - it('should not reinstall them', async function () { - this.timeout(120000); +if (process.platform !== 'darwin') { + describe('when files are installed', () => { + it('should not reinstall them', async function () { + this.timeout(120000); - /** - * Recursively find files in the given directory - * @param {string} dirname - * @param {string[]} files - * @returns {string[]} - */ - function walk(dirname, files = []) { - fs.readdirSync(dirname).forEach((name) => { - const filepath = path.join(dirname, name); - if (fs.statSync(filepath).isDirectory()) { - walk(filepath, files); - } else { - files.push(filepath); - } - }); - return files; - } - const installedFiles = walk(targetDir); + /** + * Recursively find files in the given directory + * @param {string} dirname + * @param {string[]} files + * @returns {string[]} + */ + function walk(dirname, files = []) { + fs.readdirSync(dirname).forEach((name) => { + const filepath = path.join(dirname, name); + if (fs.statSync(filepath).isDirectory()) { + walk(filepath, files); + } else { + files.push(filepath); + } + }); + return files; + } + const installedFiles = walk(targetDir); - // Get last modified time of files that should already be installed in - // the .selenium directory. - const mtimes = installedFiles.reduce((acc, filepath) => { - acc[filepath] = fs.statSync(filepath).mtime.getTime(); - return acc; - }, {}); + // Get last modified time of files that should already be installed in + // the .selenium directory. + const mtimes = installedFiles.reduce((acc, filepath) => { + acc[filepath] = fs.statSync(filepath).mtime.getTime(); + return acc; + }, {}); - // Compare last modified time of files after running the installation - // again. It shouldn't download any files, otherwise it fails. - await selenium.install(); + // Compare last modified time of files after running the installation + // again. It shouldn't download any files, otherwise it fails. + await selenium.install(); - const isModified = !installedFiles.every((filepath) => { - return mtimes[filepath] === fs.statSync(filepath).mtime.getTime(); - }); + const mtimesAfter = installedFiles.reduce((acc, filepath) => { + acc[filepath] = fs.statSync(filepath).mtime.getTime(); + return acc; + }, {}); - assert.strictEqual(isModified, false, 'It should not have reinstalled files'); + assert.strictEqual(JSON.stringify(mtimes), JSON.stringify(mtimesAfter), 'It should not have reinstalled files'); + }); }); -}); +} diff --git a/test/only-driver-tests.js b/test/only-driver-tests.js index c5e47112..dfe1069e 100644 --- a/test/only-driver-tests.js +++ b/test/only-driver-tests.js @@ -8,24 +8,23 @@ const checkPathsExistence = require('../lib/check-paths-existence'); const computeFsPaths = require('../lib/compute-fs-paths'); const path = require('path'); -/** @type {import('../lib/install').InstallOptions} */ const opts = { - version: defaultConfig.version, - baseURL: defaultConfig.baseURL, + seleniumVersion: defaultConfig.version, + seleniumBaseURL: defaultConfig.baseURL, drivers: defaultConfig.drivers, }; describe('check onlyDriver downloading only driver without selenium server and others drivers', () => { it('check onlyDriver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chrome') }; + const testOpt = { ...{ onlyDriver: 'chrome' }, ...opts }; const paths = await install(testOpt); testOpt.drivers = {}; testOpt.drivers.chrome = opts.drivers.chrome; const fsPaths = await computeFsPaths({ - seleniumVersion: opts.version, + seleniumVersion: opts.seleniumVersion, drivers: testOpt.drivers, basePath: path.join(__dirname, '..', '.selenium'), }); @@ -51,7 +50,7 @@ describe('check onlyDriver downloading only driver without selenium server and o describe('check onlyDriver with certain name of driver', () => { it('check "install" method with onlyDriver chromiumedge should return path with only certain driver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chromiumedge') }; + const testOpt = { ...{ onlyDriver: 'chromiumedge' }, ...opts }; const paths = await install(testOpt); assert(Object.keys(paths.fsPaths).length === 1 && Object.keys(paths.fsPaths).every((i) => i === 'chromiumedge')); @@ -59,7 +58,7 @@ describe('check onlyDriver with certain name of driver', () => { it('check "install" method with onlyDriver chrome should return path with only certain driver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chrome') }; + const testOpt = { ...{ onlyDriver: 'chrome' }, ...opts }; const paths = await install(testOpt); assert(Object.keys(paths.fsPaths).length === 1 && Object.keys(paths.fsPaths).every((i) => i === 'chrome')); @@ -67,7 +66,7 @@ describe('check onlyDriver with certain name of driver', () => { it('check "install" method with onlyDriver firefox should return path with only certain driver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('firefox') }; + const testOpt = { ...{ onlyDriver: 'firefox' }, ...opts }; const paths = await install(testOpt); assert(Object.keys(paths.fsPaths).length === 1 && Object.keys(paths.fsPaths).every((i) => i === 'firefox')); @@ -75,7 +74,7 @@ describe('check onlyDriver with certain name of driver', () => { it('check "install" method with onlyDriver firefox should return path with only certain driver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {any} */ ('unknown') }; + const testOpt = { ...{ onlyDriver: 'unknown' }, ...opts }; try { await install(testOpt); @@ -90,45 +89,45 @@ describe('check onlyDriver with certain name of driver', () => { describe('check staring drivers twice with onlyDriver option', () => { it('check staring twice chromedriver', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chrome') }; + const testOpt = { ...{ onlyDriver: 'chrome' }, ...opts }; const process1 = await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 2500 })); assert(process1._handle); const process2 = await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 2500 })); assert(!process1._handle); assert(process2._handle); }); it('check staring twice chromiumedge', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chromiumedge') }; + const testOpt = { ...{ onlyDriver: 'chromiumedge' }, ...opts }; const process1 = await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 3500 })); assert(process1._handle); const process2 = await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 3500 })); assert(!process1._handle); assert(process2._handle); }); it('check staring twice firefox', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('firefox') }; + const testOpt = { ...{ onlyDriver: 'firefox' }, ...opts }; const process1 = await start(testOpt); - assert(await isPortReachable(4444, { timeout: 1000, host: '127.0.0.1' })); + assert(await isPortReachable(4444, { timeout: 2500, host: '127.0.0.1' })); assert(process1._handle); const process2 = await start(testOpt); - assert(await isPortReachable(4444, { timeout: 1000, host: '127.0.0.1' })); + assert(await isPortReachable(4444, { timeout: 2500, host: '127.0.0.1' })); assert(!process1._handle); assert(process2._handle); }); @@ -141,25 +140,25 @@ describe('check staring drivers port existence', () => { it('check staring drivers port chrome', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chrome') }; + const testOpt = { ...{ onlyDriver: 'chrome' }, ...opts }; await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 2500 })); }); it('check staring drivers port firefox', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('firefox') }; + const testOpt = { ...{ onlyDriver: 'firefox' }, ...opts }; await start(testOpt); - assert(await isPortReachable(4444, { timeout: 1000, host: '127.0.0.1' })); + assert(await isPortReachable(4444, { timeout: 2500, host: '127.0.0.1' })); }); it('check staring drivers port chromiumedge', async () => { await processKiller([9515, 4444]); - const testOpt = { ...opts, onlyDriver: /** @type {const} */ ('chromiumedge') }; + const testOpt = { ...{ onlyDriver: 'chromiumedge' }, ...opts }; await start(testOpt); - assert(await isPortReachable(9515)); + assert(await isPortReachable(9515, { timeout: 2500 })); }); });