510 lines
21 KiB
JavaScript
510 lines
21 KiB
JavaScript
|
|
// src/node/startWebDriver.ts
|
||
|
|
import fs3 from "node:fs";
|
||
|
|
import os2 from "node:os";
|
||
|
|
import path3 from "node:path";
|
||
|
|
import cp2 from "node:child_process";
|
||
|
|
import getPort from "get-port";
|
||
|
|
import waitPort from "wait-port";
|
||
|
|
import logger2 from "@wdio/logger";
|
||
|
|
import split2 from "split2";
|
||
|
|
import { deepmerge } from "deepmerge-ts";
|
||
|
|
import { start as startSafaridriver } from "safaridriver";
|
||
|
|
import { start as startGeckodriver } from "geckodriver";
|
||
|
|
import { start as startEdgedriver, findEdgePath } from "edgedriver";
|
||
|
|
|
||
|
|
// src/node/utils.ts
|
||
|
|
import os from "node:os";
|
||
|
|
import fs from "node:fs";
|
||
|
|
import fsp from "node:fs/promises";
|
||
|
|
import path from "node:path";
|
||
|
|
import cp from "node:child_process";
|
||
|
|
import decamelize from "decamelize";
|
||
|
|
import logger from "@wdio/logger";
|
||
|
|
import {
|
||
|
|
install,
|
||
|
|
canDownload,
|
||
|
|
resolveBuildId,
|
||
|
|
detectBrowserPlatform,
|
||
|
|
Browser,
|
||
|
|
ChromeReleaseChannel,
|
||
|
|
computeExecutablePath
|
||
|
|
} from "@puppeteer/browsers";
|
||
|
|
import { download as downloadGeckodriver } from "geckodriver";
|
||
|
|
import { download as downloadEdgedriver } from "edgedriver";
|
||
|
|
import { locateChrome, locateFirefox, locateApp } from "locate-app";
|
||
|
|
var log = logger("webdriver");
|
||
|
|
var EXCLUDED_PARAMS = ["version", "help"];
|
||
|
|
var canAccess = (file) => {
|
||
|
|
if (!file) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
fs.accessSync(file);
|
||
|
|
return true;
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function parseParams(params) {
|
||
|
|
return Object.entries(params).filter(([key]) => !EXCLUDED_PARAMS.includes(key)).map(([key, val]) => {
|
||
|
|
if (typeof val === "boolean" && !val) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
const vals = Array.isArray(val) ? val : [val];
|
||
|
|
return vals.map((v) => `--${decamelize(key, { separator: "-" })}${typeof v === "boolean" ? "" : `=${v}`}`);
|
||
|
|
}).flat().filter(Boolean);
|
||
|
|
}
|
||
|
|
function getBuildIdByChromePath(chromePath) {
|
||
|
|
if (!chromePath) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (os.platform() === "win32") {
|
||
|
|
const versionPath = path.dirname(chromePath);
|
||
|
|
const contents = fs.readdirSync(versionPath);
|
||
|
|
const versions = contents.filter((a) => /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/g.test(a));
|
||
|
|
const oldest = versions.sort((a, b) => a > b ? -1 : 1)[0];
|
||
|
|
return oldest;
|
||
|
|
}
|
||
|
|
const result = cp.spawnSync(chromePath, ["--version", "--no-sandbox"], {
|
||
|
|
encoding: "utf8",
|
||
|
|
env: process.env
|
||
|
|
});
|
||
|
|
if (result.error) {
|
||
|
|
throw result.error;
|
||
|
|
}
|
||
|
|
const versionSanitized = result.stdout.trim().split(" ").find((s) => s.split(".").length === 4);
|
||
|
|
if (!versionSanitized) {
|
||
|
|
throw new Error(`Couldn't find valid Chrome version from "${result.stdout}", please raise an issue in the WebdriverIO project (https://github.com/webdriverio/webdriverio/issues/new/choose)`);
|
||
|
|
}
|
||
|
|
return versionSanitized;
|
||
|
|
}
|
||
|
|
async function getBuildIdByFirefoxPath(firefoxPath) {
|
||
|
|
if (!firefoxPath) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (os.platform() === "win32") {
|
||
|
|
const appPath = path.dirname(firefoxPath);
|
||
|
|
const contents = (await fsp.readFile(path.join(appPath, "application.ini"))).toString("utf-8");
|
||
|
|
return contents.split("\n").filter((line) => line.startsWith("Version=")).map((line) => line.replace(/Version=/g, "").replace(/\r/g, "")).pop();
|
||
|
|
}
|
||
|
|
const result = cp.spawnSync(firefoxPath, ["--version"], {
|
||
|
|
encoding: "utf8",
|
||
|
|
env: process.env
|
||
|
|
});
|
||
|
|
if (result.error) {
|
||
|
|
throw result.error;
|
||
|
|
}
|
||
|
|
return result.stdout.trim().split(" ").pop()?.trim();
|
||
|
|
}
|
||
|
|
var lastTimeCalled = Date.now();
|
||
|
|
var downloadProgressCallback = (artifact, downloadedBytes, totalBytes) => {
|
||
|
|
if (Date.now() - lastTimeCalled < 1e3) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const percentage = (downloadedBytes / totalBytes * 100).toFixed(2);
|
||
|
|
log.progress(`Downloading ${artifact} ${percentage}%`);
|
||
|
|
lastTimeCalled = Date.now();
|
||
|
|
};
|
||
|
|
var _install = async (args, retry = false) => {
|
||
|
|
await install(args).catch((err) => {
|
||
|
|
const error = `Failed downloading ${args.browser} v${args.buildId} using ${JSON.stringify(args)}: ${err.message}, retrying ...`;
|
||
|
|
if (retry) {
|
||
|
|
err.message += "\n" + error.replace(", retrying ...", "");
|
||
|
|
throw new Error(err);
|
||
|
|
}
|
||
|
|
log.error(error);
|
||
|
|
return _install(args, true);
|
||
|
|
});
|
||
|
|
log.progress("");
|
||
|
|
};
|
||
|
|
function locateChromeSafely() {
|
||
|
|
return locateChrome().catch(() => void 0);
|
||
|
|
}
|
||
|
|
async function setupPuppeteerBrowser(cacheDir, caps) {
|
||
|
|
caps.browserName = caps.browserName?.toLowerCase();
|
||
|
|
const browserName = caps.browserName === Browser.FIREFOX ? Browser.FIREFOX : caps.browserName === Browser.CHROMIUM ? Browser.CHROMIUM : Browser.CHROME;
|
||
|
|
const exist = await fsp.access(cacheDir).then(() => true, () => false);
|
||
|
|
const isChromeOrChromium = browserName === Browser.CHROME || caps.browserName === Browser.CHROMIUM;
|
||
|
|
if (!exist) {
|
||
|
|
await fsp.mkdir(cacheDir, { recursive: true });
|
||
|
|
}
|
||
|
|
if (browserName === Browser.CHROMIUM) {
|
||
|
|
caps.browserName = Browser.CHROME;
|
||
|
|
}
|
||
|
|
const browserOptions = (isChromeOrChromium ? caps["goog:chromeOptions"] : caps["moz:firefoxOptions"]) || {};
|
||
|
|
if (typeof browserOptions.binary === "string") {
|
||
|
|
return {
|
||
|
|
executablePath: browserOptions.binary,
|
||
|
|
browserVersion: caps.browserVersion || (isChromeOrChromium ? getBuildIdByChromePath(browserOptions.binary) : await getBuildIdByFirefoxPath(browserOptions.binary))
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const platform = detectBrowserPlatform();
|
||
|
|
if (!platform) {
|
||
|
|
throw new Error("The current platform is not supported.");
|
||
|
|
}
|
||
|
|
if (!caps.browserVersion) {
|
||
|
|
const executablePath2 = browserName === Browser.CHROME ? await locateChromeSafely() : browserName === Browser.CHROMIUM ? await locateApp({
|
||
|
|
appName: Browser.CHROMIUM,
|
||
|
|
macOsName: Browser.CHROMIUM,
|
||
|
|
linuxWhich: "chromium-browser"
|
||
|
|
}).catch(() => void 0) : await locateFirefox().catch(() => void 0);
|
||
|
|
const browserVersion2 = isChromeOrChromium ? getBuildIdByChromePath(executablePath2) : await getBuildIdByFirefoxPath(executablePath2);
|
||
|
|
if (browserVersion2) {
|
||
|
|
return {
|
||
|
|
executablePath: executablePath2,
|
||
|
|
browserVersion: browserVersion2
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const tag = browserName === Browser.CHROME ? caps.browserVersion || ChromeReleaseChannel.STABLE : caps.browserVersion || "latest";
|
||
|
|
const buildId = await resolveBuildId(browserName, platform, tag);
|
||
|
|
const installOptions = {
|
||
|
|
unpack: true,
|
||
|
|
cacheDir,
|
||
|
|
platform,
|
||
|
|
buildId,
|
||
|
|
browser: browserName,
|
||
|
|
downloadProgressCallback: (downloadedBytes, totalBytes) => downloadProgressCallback(`${browserName} (${buildId})`, downloadedBytes, totalBytes)
|
||
|
|
};
|
||
|
|
const isCombinationAvailable = await canDownload(installOptions);
|
||
|
|
if (!isCombinationAvailable) {
|
||
|
|
throw new Error(`Couldn't find a matching ${browserName} browser for tag "${buildId}" on platform "${platform}"`);
|
||
|
|
}
|
||
|
|
log.info(`Setting up ${browserName} v${buildId}`);
|
||
|
|
await _install(installOptions);
|
||
|
|
const executablePath = computeExecutablePath(installOptions);
|
||
|
|
let browserVersion = buildId;
|
||
|
|
if (browserName === Browser.CHROMIUM) {
|
||
|
|
browserVersion = await resolveBuildId(Browser.CHROME, platform, tag);
|
||
|
|
}
|
||
|
|
return { executablePath, browserVersion };
|
||
|
|
}
|
||
|
|
function getDriverOptions(caps) {
|
||
|
|
return caps["wdio:chromedriverOptions"] || caps["wdio:geckodriverOptions"] || caps["wdio:edgedriverOptions"] || // Safaridriver does not have any options as it already
|
||
|
|
// is installed on macOS
|
||
|
|
{};
|
||
|
|
}
|
||
|
|
function getCacheDir(options, caps) {
|
||
|
|
const driverOptions = getDriverOptions(caps);
|
||
|
|
return driverOptions.cacheDir || options.cacheDir || os.tmpdir();
|
||
|
|
}
|
||
|
|
function getMajorVersionFromString(fullVersion) {
|
||
|
|
let prefix;
|
||
|
|
if (fullVersion) {
|
||
|
|
prefix = fullVersion.match(/^[+-]?([0-9]+)/);
|
||
|
|
}
|
||
|
|
return prefix && prefix.length > 0 ? prefix[0] : "";
|
||
|
|
}
|
||
|
|
async function setupChromedriver(cacheDir, driverVersion) {
|
||
|
|
const platform = detectBrowserPlatform();
|
||
|
|
if (!platform) {
|
||
|
|
throw new Error("The current platform is not supported.");
|
||
|
|
}
|
||
|
|
const version = driverVersion || getBuildIdByChromePath(await locateChromeSafely()) || ChromeReleaseChannel.STABLE;
|
||
|
|
const buildId = await resolveBuildId(Browser.CHROMEDRIVER, platform, version);
|
||
|
|
let executablePath = computeExecutablePath({
|
||
|
|
browser: Browser.CHROMEDRIVER,
|
||
|
|
buildId,
|
||
|
|
platform,
|
||
|
|
cacheDir
|
||
|
|
});
|
||
|
|
const hasChromedriverInstalled = await fsp.access(executablePath).then(() => true, () => false);
|
||
|
|
if (!hasChromedriverInstalled) {
|
||
|
|
log.info(`Downloading Chromedriver v${buildId}`);
|
||
|
|
const chromedriverInstallOpts = {
|
||
|
|
cacheDir,
|
||
|
|
buildId,
|
||
|
|
platform,
|
||
|
|
browser: Browser.CHROMEDRIVER,
|
||
|
|
unpack: true,
|
||
|
|
downloadProgressCallback: (downloadedBytes, totalBytes) => downloadProgressCallback("Chromedriver", downloadedBytes, totalBytes)
|
||
|
|
};
|
||
|
|
let knownBuild = buildId;
|
||
|
|
if (await canDownload(chromedriverInstallOpts)) {
|
||
|
|
await _install({ ...chromedriverInstallOpts, buildId });
|
||
|
|
log.info(`Download of Chromedriver v${buildId} was successful`);
|
||
|
|
} else {
|
||
|
|
log.warn(`Chromedriver v${buildId} don't exist, trying to find known good version...`);
|
||
|
|
knownBuild = await resolveBuildId(Browser.CHROMEDRIVER, platform, getMajorVersionFromString(version));
|
||
|
|
if (knownBuild) {
|
||
|
|
await _install({ ...chromedriverInstallOpts, buildId: knownBuild });
|
||
|
|
log.info(`Download of Chromedriver v${knownBuild} was successful`);
|
||
|
|
} else {
|
||
|
|
throw new Error(`Couldn't download any known good version from Chromedriver major v${getMajorVersionFromString(version)}, requested full version - v${version}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
executablePath = computeExecutablePath({
|
||
|
|
browser: Browser.CHROMEDRIVER,
|
||
|
|
buildId: knownBuild,
|
||
|
|
platform,
|
||
|
|
cacheDir
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
log.info(`Using Chromedriver v${buildId} from cache directory ${cacheDir}`);
|
||
|
|
}
|
||
|
|
return { executablePath };
|
||
|
|
}
|
||
|
|
function setupGeckodriver(cacheDir, driverVersion) {
|
||
|
|
return downloadGeckodriver(driverVersion, cacheDir);
|
||
|
|
}
|
||
|
|
function setupEdgedriver(cacheDir, driverVersion) {
|
||
|
|
return downloadEdgedriver(driverVersion, cacheDir);
|
||
|
|
}
|
||
|
|
function generateDefaultPrefs(caps) {
|
||
|
|
return caps["goog:chromeOptions"]?.debuggerAddress ? {} : { prefs: { "profile.password_manager_leak_detection": false } };
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/utils.ts
|
||
|
|
import fs2 from "node:fs/promises";
|
||
|
|
import url from "node:url";
|
||
|
|
import path2 from "node:path";
|
||
|
|
|
||
|
|
// src/constants.ts
|
||
|
|
var SUPPORTED_BROWSERNAMES = {
|
||
|
|
chrome: ["chrome", "googlechrome", "chromium", "chromium-browser"],
|
||
|
|
firefox: ["firefox", "ff", "mozilla", "mozilla firefox"],
|
||
|
|
edge: ["edge", "microsoftedge", "msedge"],
|
||
|
|
safari: ["safari", "safari technology preview"]
|
||
|
|
};
|
||
|
|
var DEFAULT_HOSTNAME = "localhost";
|
||
|
|
var DEFAULT_PROTOCOL = "http";
|
||
|
|
var DEFAULT_PATH = "/";
|
||
|
|
|
||
|
|
// src/utils.ts
|
||
|
|
function isAppiumCapability(caps) {
|
||
|
|
return Boolean(
|
||
|
|
caps && // @ts-expect-error outdated jsonwp cap
|
||
|
|
(caps.automationName || caps["appium:automationName"] || "appium:options" in caps && caps["appium:options"]?.automationName || // @ts-expect-error outdated jsonwp cap
|
||
|
|
caps.deviceName || caps["appium:deviceName"] || "appium:options" in caps && caps["appium:options"]?.deviceName || "lt:options" in caps && caps["lt:options"]?.deviceName || // @ts-expect-error outdated jsonwp cap
|
||
|
|
caps.appiumVersion || caps["appium:appiumVersion"] || "appium:options" in caps && caps["appium:options"]?.appiumVersion || "lt:options" in caps && caps["lt:options"]?.appiumVersion)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
function definesRemoteDriver(options) {
|
||
|
|
return Boolean(
|
||
|
|
options.protocol && options.protocol !== DEFAULT_PROTOCOL || options.hostname && options.hostname !== DEFAULT_HOSTNAME || Boolean(options.port) || options.path && options.path !== DEFAULT_PATH || Boolean(options.user && options.key)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
function isChrome(browserName) {
|
||
|
|
return Boolean(browserName && SUPPORTED_BROWSERNAMES.chrome.includes(browserName.toLowerCase()));
|
||
|
|
}
|
||
|
|
function isSafari(browserName) {
|
||
|
|
return Boolean(browserName && SUPPORTED_BROWSERNAMES.safari.includes(browserName.toLowerCase()));
|
||
|
|
}
|
||
|
|
function isFirefox(browserName) {
|
||
|
|
return Boolean(browserName && SUPPORTED_BROWSERNAMES.firefox.includes(browserName.toLowerCase()));
|
||
|
|
}
|
||
|
|
function isEdge(browserName) {
|
||
|
|
return Boolean(browserName && SUPPORTED_BROWSERNAMES.edge.includes(browserName.toLowerCase()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/node/startWebDriver.ts
|
||
|
|
var log2 = logger2("@wdio/utils");
|
||
|
|
var DRIVER_WAIT_TIMEOUT = 10 * 1e3;
|
||
|
|
var DRIVER_RETRY_INTERVAL = 100;
|
||
|
|
async function startWebDriver(options) {
|
||
|
|
if (process.env.WDIO_SKIP_DRIVER_SETUP) {
|
||
|
|
options.hostname = "localhost";
|
||
|
|
options.port = 4321;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let driverProcess;
|
||
|
|
let driver = "";
|
||
|
|
const start = Date.now();
|
||
|
|
const caps = options.capabilities.alwaysMatch || options.capabilities;
|
||
|
|
if (isAppiumCapability(caps)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!caps.browserName) {
|
||
|
|
throw new Error(
|
||
|
|
`No "browserName" defined in capabilities nor hostname or port found!
|
||
|
|
If you like to run a local browser session make sure to pick from one of the following browser names: ${Object.values(SUPPORTED_BROWSERNAMES).flat(Infinity)}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const port = await getPort();
|
||
|
|
const cacheDir = getCacheDir(options, caps);
|
||
|
|
if (isChrome(caps.browserName)) {
|
||
|
|
const chromedriverOptions = caps["wdio:chromedriverOptions"] || {};
|
||
|
|
const chromedriverBinary = chromedriverOptions.binary || process.env.CHROMEDRIVER_PATH;
|
||
|
|
const { executablePath: chromeExecuteablePath, browserVersion } = await setupPuppeteerBrowser(cacheDir, caps);
|
||
|
|
const { executablePath: chromedriverExcecuteablePath } = chromedriverBinary ? { executablePath: chromedriverBinary } : await setupChromedriver(cacheDir, browserVersion);
|
||
|
|
const prefs = generateDefaultPrefs(caps);
|
||
|
|
caps["goog:chromeOptions"] = deepmerge(
|
||
|
|
{ binary: chromeExecuteablePath },
|
||
|
|
prefs,
|
||
|
|
caps["goog:chromeOptions"] || {}
|
||
|
|
);
|
||
|
|
if (process.platform === "win32" && process.env.WDIO_WORKER_ID) {
|
||
|
|
const existingArgs = caps["goog:chromeOptions"]?.args || [];
|
||
|
|
const hasUserDataDir = existingArgs.some((arg) => typeof arg === "string" && arg.includes("user-data-dir"));
|
||
|
|
if (!hasUserDataDir) {
|
||
|
|
const userDataDir = path3.join(
|
||
|
|
options.outputDir || os2.tmpdir(),
|
||
|
|
`wdio-chrome-${process.env.WDIO_WORKER_ID}-${Date.now()}`
|
||
|
|
);
|
||
|
|
caps["goog:chromeOptions"].args = [...existingArgs, `--user-data-dir=${userDataDir}`];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
chromedriverOptions.allowedOrigins = chromedriverOptions.allowedOrigins || ["*"];
|
||
|
|
chromedriverOptions.allowedIps = chromedriverOptions.allowedIps || ["0.0.0.0"];
|
||
|
|
const driverParams = parseParams({ port, ...chromedriverOptions });
|
||
|
|
driverProcess = cp2.spawn(chromedriverExcecuteablePath, driverParams, {
|
||
|
|
env: { ...process.env, NODE_OPTIONS: "" },
|
||
|
|
...chromedriverOptions.spawnOpts || {}
|
||
|
|
});
|
||
|
|
driver = `Chromedriver v${browserVersion} with params ${driverParams.join(" ")}`;
|
||
|
|
} else if (isSafari(caps.browserName)) {
|
||
|
|
const safaridriverOptions = caps["wdio:safaridriverOptions"] || {};
|
||
|
|
driver = "SafariDriver";
|
||
|
|
driverProcess = startSafaridriver({
|
||
|
|
useTechnologyPreview: /preview/i.test(caps.browserName),
|
||
|
|
...safaridriverOptions,
|
||
|
|
port
|
||
|
|
});
|
||
|
|
} else if (isFirefox(caps.browserName)) {
|
||
|
|
const { executablePath } = await setupPuppeteerBrowser(cacheDir, caps);
|
||
|
|
caps["moz:firefoxOptions"] = deepmerge(
|
||
|
|
{ binary: executablePath },
|
||
|
|
caps["moz:firefoxOptions"] || {}
|
||
|
|
);
|
||
|
|
delete caps.browserVersion;
|
||
|
|
const { binary, ...geckodriverOptions } = caps["wdio:geckodriverOptions"] || {};
|
||
|
|
if (binary) {
|
||
|
|
geckodriverOptions.customGeckoDriverPath = binary;
|
||
|
|
}
|
||
|
|
driver = "GeckoDriver";
|
||
|
|
driverProcess = await startGeckodriver({ ...geckodriverOptions, cacheDir, port, allowHosts: ["localhost"] });
|
||
|
|
} else if (isEdge(caps.browserName)) {
|
||
|
|
const { binary, ...edgedriverOptions } = caps["wdio:edgedriverOptions"] || {};
|
||
|
|
if (binary) {
|
||
|
|
edgedriverOptions.customEdgeDriverPath = binary;
|
||
|
|
}
|
||
|
|
driver = "EdgeDriver";
|
||
|
|
driverProcess = await startEdgedriver({ ...edgedriverOptions, cacheDir, port, allowedIps: ["0.0.0.0"] }).catch((err) => {
|
||
|
|
log2.warn(`Couldn't start EdgeDriver: ${err.message}, retry ...`);
|
||
|
|
return startEdgedriver({ ...edgedriverOptions, cacheDir, port });
|
||
|
|
});
|
||
|
|
caps.browserName = "MicrosoftEdge";
|
||
|
|
if (!caps["ms:edgeOptions"]?.binary) {
|
||
|
|
caps["ms:edgeOptions"] = caps["ms:edgeOptions"] || {};
|
||
|
|
caps["ms:edgeOptions"].binary = findEdgePath();
|
||
|
|
log2.info(`Found Edge binary at ${caps["ms:edgeOptions"].binary}`);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
throw new Error(
|
||
|
|
`Unknown browser name "${caps.browserName}". Make sure to pick from one of the following ` + Object.values(SUPPORTED_BROWSERNAMES).flat(Infinity)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const logIdentifier = driver.split(" ").shift()?.toLowerCase() || "driver";
|
||
|
|
if (options.outputDir) {
|
||
|
|
const logFileName = process.env.WDIO_WORKER_ID ? `wdio-${process.env.WDIO_WORKER_ID}-${logIdentifier}.log` : `wdio-${logIdentifier}-${port}.log`;
|
||
|
|
const logFile = path3.resolve(options.outputDir, logFileName);
|
||
|
|
const logStream = fs3.createWriteStream(logFile, { flags: "w" });
|
||
|
|
driverProcess.stdout?.pipe(logStream);
|
||
|
|
driverProcess.stderr?.pipe(logStream);
|
||
|
|
} else {
|
||
|
|
const driverLog = logger2(logIdentifier);
|
||
|
|
driverProcess.stdout?.pipe(split2()).on("data", driverLog.info.bind(driverLog));
|
||
|
|
driverProcess.stderr?.pipe(split2()).on("data", driverLog.warn.bind(driverLog));
|
||
|
|
}
|
||
|
|
await waitPort({ port, output: "silent", timeout: DRIVER_WAIT_TIMEOUT, interval: DRIVER_RETRY_INTERVAL }).catch((e) => {
|
||
|
|
throw new Error(`Timed out to connect to ${driver}: ${e.message}`);
|
||
|
|
});
|
||
|
|
options.hostname = "localhost";
|
||
|
|
options.port = port;
|
||
|
|
log2.info(`Started ${driver} in ${Date.now() - start}ms on port ${port}`);
|
||
|
|
return driverProcess;
|
||
|
|
}
|
||
|
|
|
||
|
|
// src/node/manager.ts
|
||
|
|
import logger3 from "@wdio/logger";
|
||
|
|
var log3 = logger3("@wdio/utils");
|
||
|
|
var UNDEFINED_BROWSER_VERSION = null;
|
||
|
|
var firefoxChannels = ["stable", "latest"];
|
||
|
|
function mapCapabilities(options, caps, task, taskItemLabel) {
|
||
|
|
const capabilitiesToRequireSetup = (Array.isArray(caps) ? caps.map((cap) => {
|
||
|
|
const w3cCaps = cap;
|
||
|
|
const multiremoteCaps = cap;
|
||
|
|
const multiremoteInstanceNames = Object.keys(multiremoteCaps);
|
||
|
|
if (typeof multiremoteCaps[multiremoteInstanceNames[0]] === "object" && "capabilities" in multiremoteCaps[multiremoteInstanceNames[0]]) {
|
||
|
|
return Object.values(multiremoteCaps).map((c) => "alwaysMatch" in c.capabilities ? c.capabilities.alwaysMatch : c.capabilities);
|
||
|
|
}
|
||
|
|
if (w3cCaps.alwaysMatch) {
|
||
|
|
return w3cCaps.alwaysMatch;
|
||
|
|
}
|
||
|
|
return cap;
|
||
|
|
}).flat() : Object.values(caps).map((mrOpts) => {
|
||
|
|
const w3cCaps = mrOpts.capabilities;
|
||
|
|
if (w3cCaps.alwaysMatch) {
|
||
|
|
return w3cCaps.alwaysMatch;
|
||
|
|
}
|
||
|
|
return mrOpts.capabilities;
|
||
|
|
})).flat().filter((cap) => (
|
||
|
|
/**
|
||
|
|
* only set up driver if
|
||
|
|
*/
|
||
|
|
// - capabilities are defined and not empty
|
||
|
|
cap && // - browserName is defined so we know it is a browser session
|
||
|
|
cap.browserName && // - we are not about to run a cloud session
|
||
|
|
!definesRemoteDriver(options) && // - we are not running Safari (driver already installed on macOS)
|
||
|
|
!isSafari(cap.browserName) && // - driver options don't define a binary path
|
||
|
|
!getDriverOptions(cap).binary && // - environment does not define a binary path
|
||
|
|
!(process.env.CHROMEDRIVER_PATH && isChrome(cap.browserName))
|
||
|
|
));
|
||
|
|
if (capabilitiesToRequireSetup.length === 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const queueByBrowserName = capabilitiesToRequireSetup.reduce((queue, cap) => {
|
||
|
|
if (!cap.browserName) {
|
||
|
|
return queue;
|
||
|
|
}
|
||
|
|
if (!queue.has(cap.browserName)) {
|
||
|
|
queue.set(cap.browserName, /* @__PURE__ */ new Map());
|
||
|
|
}
|
||
|
|
const browserVersion = cap.browserVersion || UNDEFINED_BROWSER_VERSION;
|
||
|
|
queue.get(cap.browserName).set(browserVersion, cap);
|
||
|
|
return queue;
|
||
|
|
}, /* @__PURE__ */ new Map());
|
||
|
|
const driverToSetupString = Array.from(queueByBrowserName.entries()).map(([browserName, versions]) => `${browserName}@${Array.from(versions.keys()).map((bv) => bv || "stable").join(", ")}`).join(" - ");
|
||
|
|
log3.info(`Setting up ${taskItemLabel} for: ${driverToSetupString}`);
|
||
|
|
return Promise.all(
|
||
|
|
Array.from(queueByBrowserName.entries()).map(([browserName, queueByBrowserVersion]) => {
|
||
|
|
return Array.from(queueByBrowserVersion).map(([browserVersion, cap]) => task({
|
||
|
|
...cap,
|
||
|
|
browserName,
|
||
|
|
...browserVersion !== UNDEFINED_BROWSER_VERSION ? { browserVersion } : {}
|
||
|
|
}));
|
||
|
|
}).flat()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
async function setupDriver(options, caps) {
|
||
|
|
return mapCapabilities(options, caps, async (cap) => {
|
||
|
|
const cacheDir = getCacheDir(options, cap);
|
||
|
|
if (isEdge(cap.browserName)) {
|
||
|
|
return setupEdgedriver(cacheDir, cap.browserVersion);
|
||
|
|
} else if (isFirefox(cap.browserName)) {
|
||
|
|
const version = firefoxChannels.includes(cap.browserVersion ?? "") ? void 0 : cap.browserVersion;
|
||
|
|
return setupGeckodriver(cacheDir, version);
|
||
|
|
} else if (isChrome(cap.browserName)) {
|
||
|
|
return setupChromedriver(cacheDir, cap.browserVersion);
|
||
|
|
}
|
||
|
|
return Promise.resolve();
|
||
|
|
}, "browser driver" /* DRIVER */);
|
||
|
|
}
|
||
|
|
function setupBrowser(options, caps) {
|
||
|
|
return mapCapabilities(options, caps, async (cap) => {
|
||
|
|
const cacheDir = getCacheDir(options, cap);
|
||
|
|
if (isEdge(cap.browserName)) {
|
||
|
|
return Promise.resolve();
|
||
|
|
} else if (isChrome(cap.browserName) || isFirefox(cap.browserName)) {
|
||
|
|
return setupPuppeteerBrowser(cacheDir, cap);
|
||
|
|
}
|
||
|
|
return Promise.resolve();
|
||
|
|
}, "browser binaries" /* BROWSER */);
|
||
|
|
}
|
||
|
|
export {
|
||
|
|
canAccess,
|
||
|
|
setupBrowser,
|
||
|
|
setupDriver,
|
||
|
|
startWebDriver
|
||
|
|
};
|