Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to run local browsers without specified browserVersion #1046

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/browser-installer/chrome/browser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import _ from "lodash";
import { resolveBuildId, canDownload, install as puppeteerInstall } from "@puppeteer/browsers";
import { MIN_CHROME_FOR_TESTING_VERSION } from "../constants";
import { CHROME_FOR_TESTING_LATEST_STABLE_API_URL, MIN_CHROME_FOR_TESTING_VERSION } from "../constants";
import {
browserInstallerDebug,
getBrowserPlatform,
getBrowsersDir,
getMilestone,
retryFetch,
type DownloadProgressCallback,
} from "../utils";
import registry from "../registry";
Expand Down Expand Up @@ -74,3 +76,20 @@ export const installChrome = async (

return browserPath;
};

export const resolveLatestChromeVersion = _.memoize(async (force = false): Promise<string> => {
if (!force) {
const platform = getBrowserPlatform();
const existingLocallyBrowserVersion = registry.getMatchedBrowserVersion(BrowserName.CHROME, platform);

if (existingLocallyBrowserVersion) {
return existingLocallyBrowserVersion;
}
}

return retryFetch(CHROME_FOR_TESTING_LATEST_STABLE_API_URL)
.then(res => res.text())
.catch(() => {
throw new Error("Couldn't resolve latest chrome version");
});
});
Comment on lines +80 to +95
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Достаем либо "какую-то локально имеющуюся".
Если "какой-то" локально нет, ходим в АПИ-шку и достаем последнюю оттуда.
Также всегда ходим в АПИ-шку при явном вызове install-deps (force === true, если вызвали npx testplane install-deps)

4 changes: 2 additions & 2 deletions src/browser-installer/chrome/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import waitPort from "wait-port";
import { pipeLogsWithPrefix } from "../../dev-server/utils";
import { DRIVER_WAIT_TIMEOUT } from "../constants";
import { getMilestone } from "../utils";
import { installChrome } from "./browser";
import { installChrome, resolveLatestChromeVersion } from "./browser";
import { installChromeDriver } from "./driver";
import { isUbuntu, getUbuntuLinkerEnv } from "../ubuntu-packages";

export { installChrome, installChromeDriver };
export { installChrome, resolveLatestChromeVersion, installChromeDriver };

export const runChromeDriver = async (
chromeVersion: string,
Expand Down
11 changes: 11 additions & 0 deletions src/browser-installer/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
export const CHROMEDRIVER_STORAGE_API = "https://chromedriver.storage.googleapis.com";

const CHROME_FOR_TESTING_VERSIONS_API_URL = "https://googlechromelabs.github.io/chrome-for-testing";
export const CHROME_FOR_TESTING_MILESTONES_API_URL = `${CHROME_FOR_TESTING_VERSIONS_API_URL}/latest-versions-per-milestone.json`;
export const CHROME_FOR_TESTING_LATEST_STABLE_API_URL = `${CHROME_FOR_TESTING_VERSIONS_API_URL}/LATEST_RELEASE_STABLE`;

export const GECKODRIVER_CARGO_TOML = "https://raw.githubusercontent.com/mozilla/geckodriver/release/Cargo.toml";

const FIREFOX_VERSIONS_VERSIONS_API_URL = "https://product-details.mozilla.org/1.0";
export const FIREFOX_VERSIONS_ALL_VERSIONS_API_URL = `${FIREFOX_VERSIONS_VERSIONS_API_URL}/firefox.json`;
export const FIREFOX_VERSIONS_LATEST_VERSIONS_API_URL = `${FIREFOX_VERSIONS_VERSIONS_API_URL}/firefox_versions.json`;
Comment on lines +3 to +11
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут использую еще пару ручек тех же самых хостов


export const MSEDGEDRIVER_API = "https://msedgedriver.azureedge.net";

export const SAFARIDRIVER_PATH = "/usr/bin/safaridriver";
export const MIN_CHROME_FOR_TESTING_VERSION = 113;
export const MIN_CHROMEDRIVER_FOR_TESTING_VERSION = 115;
Expand Down
67 changes: 67 additions & 0 deletions src/browser-installer/edge/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import _ from "lodash";
import { exec } from "child_process";
import { BrowserPlatform } from "@puppeteer/browsers";
import { getBrowserPlatform } from "../utils";

const extractBrowserVersion = (cmd: string): Promise<string> =>
new Promise<string>((resolve, reject) => {
exec(cmd, (err, stdout) => {
if (err) {
const errorMessage = "Couldn't retrive edge version. Looks like its not installed";

reject(new Error(errorMessage));

return;
}

const edgeVersionRegExp = /\d+\.\d+\.\d+\.\d+/;
const version = edgeVersionRegExp.exec(stdout);

if (version && version[0]) {
resolve(version[0]);
} else {
const errorMessage = `Couldn't retrive edge version. Expected browser version, but got "${stdout}"`;

reject(new Error(errorMessage));
}
});
});

const resolveLinuxEdgeVersion = (): Promise<string> => {
const getMsEdgeStableVersion = "which microsoft-edge-stable > /dev/null && microsoft-edge-stable --version";
const getMsEdgeVersion = "which microsoft-edge > /dev/null && microsoft-edge --version";

return extractBrowserVersion(`${getMsEdgeStableVersion} || ${getMsEdgeVersion}`);
};

const resolveWindowsEdgeVersion = (): Promise<string> => {
const getMsEdgeVersion = 'reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\BLBeacon" /v version';

return extractBrowserVersion(getMsEdgeVersion);
};

const resolveMacEdgeVersion = (): Promise<string> => {
const getMsEdgeVersion = "/Applications/Microsoft\\ Edge.app/Contents/MacOS/Microsoft\\ Edge --version";

return extractBrowserVersion(getMsEdgeVersion);
};

export const resolveEdgeVersion = _.once(async () => {
const platform = getBrowserPlatform();

switch (platform) {
case BrowserPlatform.LINUX:
return resolveLinuxEdgeVersion();

case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return resolveWindowsEdgeVersion();

case BrowserPlatform.MAC:
case BrowserPlatform.MAC_ARM:
return resolveMacEdgeVersion();

default:
throw new Error(`Unsupported platform: "${platform}"`);
}
});
Comment on lines +49 to +67
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут вычисляем, какая версия edge локально установлена на компьютере

1 change: 1 addition & 0 deletions src/browser-installer/edge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import waitPort from "wait-port";
import { pipeLogsWithPrefix } from "../../dev-server/utils";
import { DRIVER_WAIT_TIMEOUT } from "../constants";

export { resolveEdgeVersion } from "./browser";
export { installEdgeDriver };

export const runEdgeDriver = async (
Expand Down
28 changes: 27 additions & 1 deletion src/browser-installer/firefox/browser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import _ from "lodash";
import { canDownload, install as puppeteerInstall } from "@puppeteer/browsers";
import { browserInstallerDebug, getBrowserPlatform, getBrowsersDir, type DownloadProgressCallback } from "../utils";
import {
browserInstallerDebug,
getBrowserPlatform,
getBrowsersDir,
retryFetch,
type DownloadProgressCallback,
} from "../utils";
import registry from "../registry";
import { getFirefoxBuildId, normalizeFirefoxVersion } from "./utils";
import { installLatestGeckoDriver } from "./driver";
import { installUbuntuPackageDependencies } from "../ubuntu-packages";
import { BrowserName } from "../../browser/types";
import { FIREFOX_VERSIONS_LATEST_VERSIONS_API_URL } from "../constants";

const installFirefoxBrowser = async (version: string, { force = false } = {}): Promise<string> => {
const platform = getBrowserPlatform();
Expand Down Expand Up @@ -59,3 +67,21 @@ export const installFirefox = async (

return browserPath;
};

export const resolveLatestFirefoxVersion = _.memoize(async (force = false): Promise<string> => {
if (!force) {
const platform = getBrowserPlatform();
const existingLocallyBrowserVersion = registry.getMatchedBrowserVersion(BrowserName.FIREFOX, platform);

if (existingLocallyBrowserVersion) {
return existingLocallyBrowserVersion;
}
}

return retryFetch(FIREFOX_VERSIONS_LATEST_VERSIONS_API_URL)
.then(res => res.json())
.then(({ LATEST_FIREFOX_VERSION }) => LATEST_FIREFOX_VERSION)
.catch(() => {
throw new Error("Couldn't resolve latest firefox version");
});
});
Comment on lines +71 to +87
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично "chrome"

4 changes: 2 additions & 2 deletions src/browser-installer/firefox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import type { ChildProcess } from "child_process";
import { start as startGeckoDriver } from "geckodriver";
import getPort from "get-port";
import waitPort from "wait-port";
import { installFirefox } from "./browser";
import { installFirefox, resolveLatestFirefoxVersion } from "./browser";
import { installLatestGeckoDriver } from "./driver";
import { pipeLogsWithPrefix } from "../../dev-server/utils";
import { DRIVER_WAIT_TIMEOUT } from "../constants";
import { getUbuntuLinkerEnv, isUbuntu } from "../ubuntu-packages";

export { installFirefox, installLatestGeckoDriver };
export { installFirefox, resolveLatestFirefoxVersion, installLatestGeckoDriver };

export const runGeckoDriver = async (
firefoxVersion: string,
Expand Down
1 change: 1 addition & 0 deletions src/browser-installer/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { installBrowser, installBrowsersWithDrivers, BrowserInstallStatus } from "./install";
export { runBrowserDriver } from "./run";
export { resolveBrowserVersion } from "./resolve-browser-version";
export type { SupportedBrowser, SupportedDriver } from "./utils";
25 changes: 12 additions & 13 deletions src/browser-installer/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ export const installBrowser = async (
browserVersion?: string,
{ force = false, shouldInstallWebDriver = false, shouldInstallUbuntuPackages = false } = {},
): Promise<string | null> => {
if (!browserVersion) {
throw new Error(
`Couldn't install browser '${browserName}' because it has invalid version: '${browserVersion}'`,
);
}

const { isUbuntu } = await import("./ubuntu-packages");

const needUbuntuPackages = shouldInstallUbuntuPackages && (await isUbuntu());
Expand All @@ -33,22 +27,25 @@ export const installBrowser = async (
switch (browserName) {
case BrowserName.CHROME:
case BrowserName.CHROMIUM: {
const { installChrome } = await import("./chrome");
const { installChrome, resolveLatestChromeVersion } = await import("./chrome");
const version = browserVersion || (await resolveLatestChromeVersion(force));

return installChrome(browserVersion, { force, needUbuntuPackages, needWebDriver: shouldInstallWebDriver });
return installChrome(version, { force, needUbuntuPackages, needWebDriver: shouldInstallWebDriver });
}

case BrowserName.FIREFOX: {
const { installFirefox } = await import("./firefox");
const { installFirefox, resolveLatestFirefoxVersion } = await import("./firefox");
const version = browserVersion || (await resolveLatestFirefoxVersion(force));

return installFirefox(browserVersion, { force, needUbuntuPackages, needWebDriver: shouldInstallWebDriver });
return installFirefox(version, { force, needUbuntuPackages, needWebDriver: shouldInstallWebDriver });
}

case BrowserName.EDGE: {
const { installEdgeDriver } = await import("./edge");
const { installEdgeDriver, resolveEdgeVersion } = await import("./edge");
const version = browserVersion || (await resolveEdgeVersion());

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

return null;
Expand Down Expand Up @@ -113,7 +110,9 @@ export const installBrowsersWithDrivers = async (
for (const { browserName, browserVersion } of uniqBrowsers) {
installPromises.push(
forceInstallBinaries(installBrowser, browserName, browserVersion).then(result => {
browsersInstallResult[`${browserName}@${browserVersion}`] = result;
const key = browserVersion ? `${browserName}@${browserVersion}` : String(browserName);

browsersInstallResult[key] = result;
}),
);
}
Expand Down
44 changes: 27 additions & 17 deletions src/browser-installer/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ const logDownloadingBrowsersWarningOnce = _.once(() => {
logger.warn("Note: this is one-time action. It may take a while...");
});

const getBuildPrefix = (browserName: SupportedBrowser, browserVersion: string): string | null => {
switch (browserName) {
case BrowserName.CHROME:
return normalizeChromeVersion(browserVersion);

case BrowserName.CHROMIUM:
return getMilestone(browserVersion);

case BrowserName.FIREFOX:
return getFirefoxBuildId(browserVersion);

default:
return null;
}
};

class Registry {
private registryPath = getRegistryPath();
private registry = this.readRegistry();
Expand Down Expand Up @@ -123,35 +139,29 @@ class Registry {
public getMatchedBrowserVersion(
browserName: SupportedBrowser,
platform: BrowserPlatform,
browserVersion: string,
browserVersion?: string,
): string | null {
const registryKey = getRegistryBinaryKey(browserName, platform);

if (!this.registry.binaries[registryKey]) {
return null;
}

let buildPrefix: string;

switch (browserName) {
case BrowserName.CHROME:
buildPrefix = normalizeChromeVersion(browserVersion);
break;
const buildIds = this.getBinaryVersions(browserName, platform);

case BrowserName.CHROMIUM:
buildPrefix = getMilestone(browserVersion);
break;
let suitableBuildIds;

case BrowserName.FIREFOX:
buildPrefix = getFirefoxBuildId(browserVersion);
break;
if (!browserVersion) {
suitableBuildIds = buildIds;
} else {
const buildPrefix = getBuildPrefix(browserName, browserVersion);

default:
if (buildPrefix === null) {
return null;
}
}

const buildIds = this.getBinaryVersions(browserName, platform);
const suitableBuildIds = buildIds.filter(buildId => buildId.startsWith(buildPrefix));
suitableBuildIds = buildIds.filter(buildId => buildId.startsWith(buildPrefix));
}

if (!suitableBuildIds.length) {
return null;
Expand Down
14 changes: 14 additions & 0 deletions src/browser-installer/resolve-browser-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BrowserName, type W3CBrowserName } from "../browser/types";

export const resolveBrowserVersion = (browserName: W3CBrowserName, { force = false } = {}): Promise<string> => {
switch (browserName) {
case BrowserName.CHROME:
return import("./chrome").then(module => module.resolveLatestChromeVersion(force));
case BrowserName.FIREFOX:
return import("./firefox").then(module => module.resolveLatestFirefoxVersion(force));
case BrowserName.EDGE:
return import("./edge").then(module => module.resolveEdgeVersion());
case BrowserName.SAFARI:
return import("./safari").then(module => module.resolveSafariVersion());
}
};
24 changes: 24 additions & 0 deletions src/browser-installer/safari/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import _ from "lodash";
import { exec } from "child_process";

export const resolveSafariVersion = _.once(
() =>
new Promise<string>((resolve, reject) => {
const getSafariVersionError = new Error("Couldn't retrive safari version.");

exec("mdls -name kMDItemVersion /Applications/Safari.app", (err, stdout) => {
if (err) {
reject(getSafariVersionError);
return;
}

const regExpResult = /kMDItemVersion = "(.*)"/.exec(stdout);

if (regExpResult && regExpResult[1]) {
resolve(regExpResult[1]);
} else {
reject(getSafariVersionError);
}
});
}),
);
Comment on lines +4 to +24
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аналогично edge: достаем локально имеющуюся версию safari

2 changes: 2 additions & 0 deletions src/browser-installer/safari/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import waitPort from "wait-port";
import { pipeLogsWithPrefix } from "../../dev-server/utils";
import { DRIVER_WAIT_TIMEOUT, SAFARIDRIVER_PATH } from "../constants";

export { resolveSafariVersion } from "./browser";

export const runSafariDriver = async ({ debug = false }: { debug?: boolean } = {}): Promise<{
gridUrl: string;
process: ChildProcess;
Expand Down
Loading
Loading