appium-chromedriver
Version:
Node.js wrapper around chromedriver.
347 lines • 15.1 kB
JavaScript
;
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