Skip to content

Commit

Permalink
chore: assert browser is installed before running its driver
Browse files Browse the repository at this point in the history
  • Loading branch information
KuznetsovRoman committed Dec 6, 2024
1 parent a7835c2 commit 4c3fd27
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 184 deletions.
27 changes: 8 additions & 19 deletions src/browser-installer/chrome/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,30 @@ import { DRIVER_WAIT_TIMEOUT } from "../constants";
import { getMilestone } from "../utils";
import { installChrome } from "./browser";
import { installChromeDriver } from "./driver";
import { isUbuntu, getUbuntuLinkerEnv, installUbuntuPackageDependencies } from "../ubuntu-packages";
import { isUbuntu, getUbuntuLinkerEnv } from "../ubuntu-packages";

export { installChrome, installChromeDriver };

export const runChromeDriver = async (
chromeVersion: string,
{ debug = false } = {},
): Promise<{ gridUrl: string; process: ChildProcess; port: number }> => {
const shouldInstallUbuntuPackageDependencies = await isUbuntu();

const [chromeDriverPath] = await Promise.all([
const [chromeDriverPath, randomPort, chromeDriverEnv] = await Promise.all([
installChromeDriver(chromeVersion),
installChrome(chromeVersion),
shouldInstallUbuntuPackageDependencies ? installUbuntuPackageDependencies() : null,
getPort(),
isUbuntu()
.then(isUbuntu => (isUbuntu ? getUbuntuLinkerEnv() : null))
.then(extraEnv => (extraEnv ? { ...process.env, ...extraEnv } : process.env)),
]);

const milestone = getMilestone(chromeVersion);
const randomPort = await getPort();
const extraSpawnOpts = shouldInstallUbuntuPackageDependencies
? {
env: {
...process.env,
...(await getUbuntuLinkerEnv()),
},
}
: {};

const chromeDriver = spawn(chromeDriverPath, [`--port=${randomPort}`, debug ? `--verbose` : "--silent"], {
windowsHide: true,
detached: false,
...extraSpawnOpts,
env: chromeDriverEnv,
});

if (debug) {
pipeLogsWithPrefix(chromeDriver, `[chromedriver@${milestone}] `);
pipeLogsWithPrefix(chromeDriver, `[chromedriver@${getMilestone(chromeVersion)}] `);
}

const gridUrl = `http://127.0.0.1:${randomPort}`;
Expand Down
3 changes: 1 addition & 2 deletions src/browser-installer/edge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ export const runEdgeDriver = async (
edgeVersion: string,
{ debug = false }: { debug?: boolean } = {},
): Promise<{ gridUrl: string; process: ChildProcess; port: number }> => {
const edgeDriverPath = await installEdgeDriver(edgeVersion);
const randomPort = await getPort();
const [edgeDriverPath, randomPort] = await Promise.all([installEdgeDriver(edgeVersion), getPort()]);

const edgeDriver = spawn(edgeDriverPath, [`--port=${randomPort}`, debug ? `--verbose` : "--silent"], {
windowsHide: true,
Expand Down
24 changes: 7 additions & 17 deletions src/browser-installer/firefox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,30 @@ import { installFirefox } from "./browser";
import { installLatestGeckoDriver } from "./driver";
import { pipeLogsWithPrefix } from "../../dev-server/utils";
import { DRIVER_WAIT_TIMEOUT } from "../constants";
import { getUbuntuLinkerEnv, installUbuntuPackageDependencies, isUbuntu } from "../ubuntu-packages";
import { getUbuntuLinkerEnv, isUbuntu } from "../ubuntu-packages";

export { installFirefox, installLatestGeckoDriver };

export const runGeckoDriver = async (
firefoxVersion: string,
{ debug = false } = {},
): Promise<{ gridUrl: string; process: ChildProcess; port: number }> => {
const shouldInstallUbuntuPackageDependencies = await isUbuntu();

const [geckoDriverPath] = await Promise.all([
const [geckoDriverPath, randomPort, geckoDriverEnv] = await Promise.all([
installLatestGeckoDriver(firefoxVersion),
installFirefox(firefoxVersion),
shouldInstallUbuntuPackageDependencies ? installUbuntuPackageDependencies() : null,
getPort(),
isUbuntu()
.then(isUbuntu => (isUbuntu ? getUbuntuLinkerEnv() : null))
.then(extraEnv => (extraEnv ? { ...process.env, ...extraEnv } : process.env)),
]);

const randomPort = await getPort();
const extraSpawnOpts = shouldInstallUbuntuPackageDependencies
? {
env: {
...process.env,
...(await getUbuntuLinkerEnv()),
},
}
: {};

const geckoDriver = await startGeckoDriver({
customGeckoDriverPath: geckoDriverPath,
port: randomPort,
log: debug ? "debug" : "fatal",
spawnOpts: {
windowsHide: true,
detached: false,
...extraSpawnOpts,
env: geckoDriverEnv,
},
});

Expand Down
2 changes: 1 addition & 1 deletion src/browser-installer/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { installBrowser, installBrowsersWithDrivers, BrowserInstallStatus } from "./install";
export { runBrowserDriver } from "./run";
export { getDriverNameForBrowserName } from "./utils";
export { getNormalizedBrowserName } from "./utils";
export type { SupportedBrowser, SupportedDriver } from "./utils";
92 changes: 51 additions & 41 deletions src/browser-installer/install.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
import _ from "lodash";
import { Browser, getNormalizedBrowserName, type SupportedBrowser } from "./utils";

/**
* @returns path to browser binary
* @returns path to installed browser binary
*/
export const installBrowser = async (
browserName?: string,
browserName: SupportedBrowser,
browserVersion?: string,
{ force = false, shouldInstallWebDriver = false, shouldInstallUbuntuPackages = true } = {},
{ force = false, shouldInstallWebDriver = false, shouldInstallUbuntuPackages = false } = {},
): Promise<string | null> => {
const unsupportedBrowserError = new Error(
[
`Couldn't install browser '${browserName}', as it is not supported`,
`Currently supported for installation browsers: 'chrome', 'firefox`,
].join("\n"),
);

if (!browserName) {
throw unsupportedBrowserError;
}

if (!browserVersion) {
throw new Error(
`Couldn't install browser '${browserName}' because it has invalid version: '${browserVersion}'`,
Expand All @@ -29,35 +19,46 @@ export const installBrowser = async (

const needToInstallUbuntuPackages = shouldInstallUbuntuPackages && (await isUbuntu());

if (/chrome/i.test(browserName)) {
const { installChrome, installChromeDriver } = await import("./chrome");

return await Promise.all([
installChrome(browserVersion, { force }),
shouldInstallWebDriver && installChromeDriver(browserVersion, { force }),
needToInstallUbuntuPackages && installUbuntuPackageDependencies(),
]).then(result => result[0]);
} else if (/firefox/i.test(browserName)) {
const { installFirefox, installLatestGeckoDriver } = await import("./firefox");

return await Promise.all([
installFirefox(browserVersion, { force }),
shouldInstallWebDriver && installLatestGeckoDriver(browserVersion, { force }),
needToInstallUbuntuPackages && installUbuntuPackageDependencies(),
]).then(result => result[0]);
} else if (/edge/i.test(browserName)) {
const { installEdgeDriver } = await import("./edge");

if (shouldInstallWebDriver) {
await installEdgeDriver(browserVersion, { force });
switch (browserName) {
case Browser.CHROME:
case Browser.CHROMIUM: {
const { installChrome, installChromeDriver } = await import("./chrome");

const [browserPath] = await Promise.all([
installChrome(browserVersion, { force }),
shouldInstallWebDriver && installChromeDriver(browserVersion, { force }),
needToInstallUbuntuPackages && installUbuntuPackageDependencies(),
]);

return browserPath;
}

return null;
} else if (/safari/i.test(browserName)) {
return null;
}
case Browser.FIREFOX: {
const { installFirefox, installLatestGeckoDriver } = await import("./firefox");

const [browserPath] = await Promise.all([
installFirefox(browserVersion, { force }),
shouldInstallWebDriver && installLatestGeckoDriver(browserVersion, { force }),
needToInstallUbuntuPackages && installUbuntuPackageDependencies(),
]);

return browserPath;
}

throw unsupportedBrowserError;
case Browser.EDGE: {
const { installEdgeDriver } = await import("./edge");

if (shouldInstallWebDriver) {
await installEdgeDriver(browserVersion, { force });
}

return null;
}

case Browser.SAFARI: {
return null;
}
}
};

export const BrowserInstallStatus = {
Expand All @@ -81,7 +82,16 @@ const forceInstallBinaries = async (
browserName?: string,
browserVersion?: string,
): ForceInstallBinaryResult => {
return installFn(browserName, browserVersion, { force: true, shouldInstallWebDriver: true })
const normalizedBrowserName = getNormalizedBrowserName(browserName);

if (!normalizedBrowserName) {
return {
status: BrowserInstallStatus.Error,
reason: `Installing ${browserName} is unsupported. Supported browsers: "chrome", "firefox", "safari", "edge"`,
};
}

return installFn(normalizedBrowserName, browserVersion, { force: true, shouldInstallWebDriver: true })
.then(successResult => {
return successResult
? { status: BrowserInstallStatus.Ok }
Expand Down
24 changes: 15 additions & 9 deletions src/browser-installer/run.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import type { ChildProcess } from "child_process";
import { Driver, type SupportedDriver } from "./utils";
import { installBrowser } from "./install";
import { Browser, type SupportedBrowser } from "./utils";

export const runBrowserDriver = async (
driverName: SupportedDriver,
browserName: SupportedBrowser,
browserVersion: string,
{ debug = false } = {},
): Promise<{ gridUrl: string; process: ChildProcess; port: number }> => {
switch (driverName) {
case Driver.CHROMEDRIVER:
const installBrowserOpts = { shouldInstallWebDriver: true, shouldInstallUbuntuPackages: true };

await installBrowser(browserName, browserVersion, installBrowserOpts);

switch (browserName) {
case Browser.CHROME:
case Browser.CHROMIUM:
return import("./chrome").then(module => module.runChromeDriver(browserVersion, { debug }));
case Driver.EDGEDRIVER:
return import("./edge").then(module => module.runEdgeDriver(browserVersion, { debug }));
case Driver.GECKODRIVER:
case Browser.FIREFOX:
return import("./firefox").then(module => module.runGeckoDriver(browserVersion, { debug }));
case Driver.SAFARIDRIVER:
case Browser.EDGE:
return import("./edge").then(module => module.runEdgeDriver(browserVersion, { debug }));
case Browser.SAFARI:
return import("./safari").then(module => module.runSafariDriver({ debug }));
default:
throw new Error(`Invalid driver name: ${driverName}. Expected one of: ${Object.values(Driver).join(", ")}`);
throw new Error(`Invalid browser: ${browserName}. Expected one of: ${Object.values(Browser).join(", ")}`);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { fetchChromiumMilestoneVersions } from "./chromium";
import { fetchChromeMilestoneVersions } from "./chrome";
import { fetchFirefoxMilestoneVersions } from "./firefox";
import type { BrowserWithVersion } from "../utils";
import { Browser, type SupportedBrowser } from "../../../utils";

export const fetchBrowsersMilestones = async (): Promise<BrowserWithVersion[]> => {
const createMapToBrowser = (browserName: string) => (data: string[]) =>
const createMapToBrowser = (browserName: SupportedBrowser) => (data: string[]) =>
data.map(browserVersion => ({ browserName, browserVersion }));

const [chromiumVersions, chromeVersions, firefoxVersions] = await Promise.all([
fetchChromiumMilestoneVersions().then(createMapToBrowser("chrome")),
fetchChromeMilestoneVersions().then(createMapToBrowser("chrome")),
fetchFirefoxMilestoneVersions().then(createMapToBrowser("firefox")),
fetchChromiumMilestoneVersions().then(createMapToBrowser(Browser.CHROME)),
fetchChromeMilestoneVersions().then(createMapToBrowser(Browser.CHROME)),
fetchFirefoxMilestoneVersions().then(createMapToBrowser(Browser.FIREFOX)),
]);

return [...chromiumVersions, ...chromeVersions, ...firefoxVersions];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type BrowserWithVersion = { browserName: string; browserVersion: string };
import type { SupportedBrowser } from "../../utils";

export type BrowserWithVersion = { browserName: SupportedBrowser; browserVersion: string };

export const getCliArgs = <T extends Record<string, boolean>>(flags?: T): string[] => {
if (!flags) {
Expand Down
24 changes: 15 additions & 9 deletions src/browser-installer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,27 @@ export const Driver = {
export type SupportedBrowser = (typeof Browser)[keyof typeof Browser];
export type SupportedDriver = (typeof Driver)[keyof typeof Driver];

export const getDriverNameForBrowserName = (browserName: SupportedBrowser): SupportedDriver | null => {
if (browserName === Browser.CHROME || browserName === Browser.CHROMIUM) {
return Driver.CHROMEDRIVER;
export const getNormalizedBrowserName = (
browserName?: string,
): Exclude<SupportedBrowser, typeof Browser.CHROMIUM> | null => {
if (!browserName) {
return null;
}

if (browserName === Browser.FIREFOX) {
return Driver.GECKODRIVER;
if (/chrome/i.test(browserName)) {
return Browser.CHROME;
}

if (browserName === Browser.SAFARI) {
return Driver.SAFARIDRIVER;
if (/firefox/i.test(browserName)) {
return Browser.FIREFOX;
}

if (browserName === Browser.EDGE) {
return Driver.EDGEDRIVER;
if (/edge/i.test(browserName)) {
return Browser.EDGE;
}

if (/safari/i.test(browserName)) {
return Browser.SAFARI;
}

return null;
Expand Down
Loading

0 comments on commit 4c3fd27

Please sign in to comment.