UNPKG

appium-chromedriver

Version:
347 lines 15.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDriversMapping = getDriversMapping; exports.getChromedrivers = getChromedrivers; exports.updateDriversMapping = updateDriversMapping; exports.getCompatibleChromedriver = getCompatibleChromedriver; exports.initChromedriverPath = initChromedriverPath; const support_1 = require("@appium/support"); const asyncbox_1 = require("asyncbox"); const compare_versions_1 = require("compare-versions"); const node_path_1 = __importDefault(require("node:path")); const semver = __importStar(require("semver")); const utils_1 = require("../utils"); const NEW_CD_VERSION_FORMAT_MAJOR_VERSION = 73; const CD_VERSION_TIMEOUT = 5000; const GET_COMPATIBLE_CHROMEDRIVER_MAX_ITERATIONS = 10; /** * Loads and normalizes Chromedriver-to-Chrome version mapping. */ async function getDriversMapping() { let mapping = structuredClone(utils_1.CHROMEDRIVER_CHROME_MAPPING); if (this.mappingPath) { this.log.debug(`Attempting to use Chromedriver->Chrome mapping from '${this.mappingPath}'`); if (!(await support_1.fs.exists(this.mappingPath))) { this.log.warn(`No file found at '${this.mappingPath}'`); this.log.info('Defaulting to the static Chromedriver->Chrome mapping'); } else { try { mapping = JSON.parse(await support_1.fs.readFile(this.mappingPath, 'utf8')); } catch (e) { const err = e; this.log.warn(`Error parsing mapping from '${this.mappingPath}': ${err.message}`); this.log.info('Defaulting to the static Chromedriver->Chrome mapping'); } } } else { this.log.debug('Using the static Chromedriver->Chrome mapping'); } for (const [cdVersion, chromeVersion] of Object.entries(mapping)) { const coercedVersion = semver.coerce(chromeVersion); if (coercedVersion) { mapping[cdVersion] = coercedVersion.version; } else { this.log.info(`'${chromeVersion}' is not a valid version number. Skipping it`); } } return mapping; } /** * Discovers available Chromedriver binaries and parses their versions. */ async function getChromedrivers(mapping) { // enumerate available executables in configured chromedriver directory const executables = await support_1.fs.glob('*', { cwd: this.executableDir, nodir: true, absolute: true, }); this.log.debug(`Found ${support_1.util.pluralize('executable', executables.length, true)} ` + `in '${this.executableDir}'`); const cds = (await (0, asyncbox_1.asyncmap)(executables, async (executable) => { const logError = ({ message, stdout, stderr, }) => { let errMsg = `Cannot retrieve version number from '${node_path_1.default.basename(executable)}' Chromedriver binary. ` + `Make sure it returns a valid version string in response to '--version' command line argument. ${message}`; if (stdout) { errMsg += `\nStdout: ${stdout}`; } if (stderr) { errMsg += `\nStderr: ${stderr}`; } this.log.warn(errMsg); return null; }; let stdout; let stderr; try { ({ stdout, stderr } = await this._execFunc(executable, ['--version'], { timeout: CD_VERSION_TIMEOUT, })); } catch (e) { const err = e; if (!(err.message || '').includes('timed out') && !(err.stdout || '').includes('Starting ChromeDriver')) { return logError(err); } // timeouts may still contain the version banner in stdout stdout = err.stdout; } const match = /ChromeDriver\s+\(?v?([\d.]+)\)?/i.exec(stdout); if (!match) { return logError({ message: 'Cannot parse the version string', stdout, stderr }); } let version = match[1]; let minChromeVersion = mapping[version] || null; const coercedVersion = semver.coerce(version); if (coercedVersion) { if (coercedVersion.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) { version = `${coercedVersion.major}.${coercedVersion.minor}`; minChromeVersion = mapping[version] || null; } if (!minChromeVersion && coercedVersion.major >= NEW_CD_VERSION_FORMAT_MAJOR_VERSION) { minChromeVersion = `${coercedVersion.major}`; } } return { executable, version, minChromeVersion }; })) .filter((cd) => !!cd) .sort((a, b) => (0, compare_versions_1.compareVersions)(b.version, a.version)); if (cds.length === 0) { this.log.info(`No Chromedrivers were found in '${this.executableDir}'`); return cds; } this.log.debug(`The following Chromedriver executables were found:`); for (const cd of cds) { this.log.debug(` '${cd.executable}' (version '${cd.version}', minimum Chrome version '${cd.minChromeVersion ? cd.minChromeVersion : 'Unknown'}')`); } return cds; } /** * Persists updated version mapping to disk or falls back to in-memory update. */ async function updateDriversMapping(newMapping) { let shouldUpdateStaticMapping = true; if (!this.mappingPath) { this.log.warn('No mapping path provided'); return; } if (await support_1.fs.exists(this.mappingPath)) { try { await support_1.fs.writeFile(this.mappingPath, JSON.stringify(newMapping, null, 2), 'utf8'); shouldUpdateStaticMapping = false; } catch (e) { const err = e; this.log.warn(`Cannot store the updated chromedrivers mapping into '${this.mappingPath}'. ` + `This may reduce the performance of further executions. Original error: ${err.message}`); } } if (shouldUpdateStaticMapping) { Object.assign(utils_1.CHROMEDRIVER_CHROME_MAPPING, newMapping); } } /** * Selects the most suitable Chromedriver binary for current environment. */ async function getCompatibleChromedriver() { if (usesDesktopChromedriverDefault(this)) { return await (0, utils_1.getChromedriverBinaryPath)(); } const ctx = this; const mapping = await ctx.getDriversMapping(); if (!support_1.util.isEmpty(mapping)) { ctx.log.debug(`The most recent known Chrome version: ${Object.values(mapping)[0]}`); } const syncState = { didStorageSync: false }; for (let iteration = 0; iteration < GET_COMPATIBLE_CHROMEDRIVER_MAX_ITERATIONS; iteration++) { const cds = await ctx.getChromedrivers(mapping); await mergeDiscoveredMappingGaps(ctx, cds, mapping); if (ctx.disableBuildCheck) { return pickChromedriverWithBuildCheckDisabled(ctx, cds); } const chromeVersion = await ctx.getChromeVersion(); if (!chromeVersion) { return pickChromedriverWhenChromeUnknown(ctx, cds); } ctx.log.debug(`Found Chrome bundle '${ctx.bundleId}' version '${chromeVersion}'`); const matchingDrivers = filterChromedriversMatchingChrome(cds, chromeVersion); if (matchingDrivers.length === 0) { if (ctx.storageClient && !syncState.didStorageSync) { try { if (await attemptChromedriverStorageSync(ctx, mapping, chromeVersion, syncState)) { continue; } } catch (e) { const err = e; ctx.log.warn(`Cannot synchronize local chromedrivers with the remote storage: ${err.message}`); if (err.stack) { ctx.log.debug(err.stack); } } } throw makeNoMatchingChromedriverError(ctx, chromeVersion); } return logChosenMatchingChromedriver(ctx, matchingDrivers, chromeVersion); } throw new Error(`Exceeded ${GET_COMPATIBLE_CHROMEDRIVER_MAX_ITERATIONS} iterations while selecting a ` + `compatible Chromedriver.`); } /** * Resolves and verifies the effective Chromedriver executable path. */ async function initChromedriverPath() { if (this.executableVerified && this.chromedriver) { return this.chromedriver; } let chromedriver = this.chromedriver; if (!chromedriver) { chromedriver = this.chromedriver = this.useSystemExecutable ? await (0, utils_1.getChromedriverBinaryPath)() : await this.getCompatibleChromedriver(); } if (!chromedriver) { throw new Error('Cannot determine a valid Chromedriver executable path'); } if (!(await support_1.fs.exists(chromedriver))) { throw new Error(`Trying to use a chromedriver binary at the path ${chromedriver}, but it doesn't exist!`); } this.executableVerified = true; this.log.info(`Set chromedriver binary as: ${chromedriver}`); return chromedriver; } function usesDesktopChromedriverDefault(ctx) { return !ctx.adb && !ctx.isCustomExecutableDir; } async function mergeDiscoveredMappingGaps(ctx, cds, mapping) { const missingVersions = {}; for (const { version, minChromeVersion } of cds) { if (!minChromeVersion || mapping[version]) { continue; } const coercedVer = semver.coerce(version); if (!coercedVer || coercedVer.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) { continue; } missingVersions[version] = minChromeVersion; } const missingCount = Object.keys(missingVersions).length; if (missingCount === 0) { return; } ctx.log.info(`Found ${support_1.util.pluralize('Chromedriver', missingCount, true)}, ` + `which ${missingCount === 1 ? 'is' : 'are'} missing in the list of known versions: ` + JSON.stringify(missingVersions)); await ctx.updateDriversMapping(Object.assign(mapping, missingVersions)); } function pickChromedriverWithBuildCheckDisabled(ctx, cds) { if (cds.length === 0) { throw ctx.log.errorWithException(`There must be at least one Chromedriver executable available for use if ` + `'chromedriverDisableBuildCheck' capability is set to 'true'`); } const { version, executable } = cds[0]; ctx.log.warn(`Chrome build check disabled. Using most recent Chromedriver version (${version}, at '${executable}')`); ctx.log.warn(`If this is wrong, set 'chromedriverDisableBuildCheck' capability to 'false'`); return executable; } function pickChromedriverWhenChromeUnknown(ctx, cds) { if (cds.length === 0) { throw ctx.log.errorWithException(`There must be at least one Chromedriver executable available for use if ` + `the current Chrome version cannot be determined`); } const { version, executable } = cds[0]; ctx.log.warn(`Unable to discover Chrome version. Using Chromedriver ${version} at '${executable}'`); return executable; } function filterChromedriversMatchingChrome(cds, chromeVersion) { return cds.filter(({ minChromeVersion }) => { const minChromeVersionS = minChromeVersion && semver.coerce(minChromeVersion); if (!minChromeVersionS) { return false; } return chromeVersion.major > NEW_CD_VERSION_FORMAT_MAJOR_VERSION ? minChromeVersionS.major === chromeVersion.major : semver.gte(chromeVersion, minChromeVersionS); }); } /** * Syncs drivers from remote storage into `mapping` and persists when possible. * Sets `syncState.didStorageSync` before any early return so a second sync is not attempted. */ async function attemptChromedriverStorageSync(ctx, mapping, chromeVersion, syncState) { syncState.didStorageSync = true; if (!ctx.storageClient) { return false; } const retrievedMapping = await ctx.storageClient.retrieveMapping(); ctx.log.debug('Got chromedrivers mapping from the storage: ' + support_1.util.truncateString(JSON.stringify(retrievedMapping, null, 2), { length: 500 })); const driverKeys = await ctx.storageClient.syncDrivers({ minBrowserVersion: chromeVersion.major, }); if (driverKeys.length === 0) { return false; } const synchronizedDriversMapping = driverKeys.reduce((acc, x) => { const { version, minBrowserVersion } = retrievedMapping[x]; acc[version] = minBrowserVersion; return acc; }, {}); Object.assign(mapping, synchronizedDriversMapping); await ctx.updateDriversMapping(mapping); return true; } function makeNoMatchingChromedriverError(ctx, chromeVersion) { const autodownloadSuggestion = 'You could also try to enable automated chromedrivers download as a possible workaround.'; return new Error(`No Chromedriver found that can automate Chrome '${chromeVersion}'.` + (ctx.storageClient ? '' : ` ${autodownloadSuggestion}`)); } function logChosenMatchingChromedriver(ctx, matchingDrivers, chromeVersion) { const binPath = matchingDrivers[0].executable; ctx.log.debug(`Found ${support_1.util.pluralize('executable', matchingDrivers.length, true)} ` + `capable of automating Chrome '${chromeVersion}'.\nChoosing the most recent, '${binPath}'.`); ctx.log.debug(`If a specific version is required, specify it with the 'chromedriverExecutable' capability.`); return binPath; } //# sourceMappingURL=binary.js.map