refactor: remove browserPaths in favor of Registry class (#5318)

This patch introduces a new Registry class that incapsulates
all logic regarding browsers and their paths.

Fixes #5278
This commit is contained in:
Andrey Lushnikov 2021-02-08 16:02:49 -08:00 committed by GitHub
parent 6680713e84
commit d499cf08d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 385 additions and 407 deletions

View File

@ -27,7 +27,7 @@ try {
console.log(`Downloading browsers...`);
const { installBrowsersWithProgressBar } = require('./lib/install/installer');
installBrowsersWithProgressBar(__dirname).catch(e => {
installBrowsersWithProgressBar().catch(e => {
console.error(`Failed to install browsers, caused by\n${e.stack}`);
process.exit(1);
});

View File

@ -16,4 +16,4 @@
const { installBrowsersWithProgressBar } = require('./lib/install/installer');
installBrowsersWithProgressBar(__dirname);
installBrowsersWithProgressBar();

View File

@ -24,7 +24,7 @@ import { installBrowsersWithProgressBar } from '../install/installer';
import { Transport } from '../protocol/transport';
import { createPlaywright } from '../server/playwright';
import { gracefullyCloseAll } from '../server/processLauncher';
import { BrowserName } from '../utils/browserPaths';
import { BrowserName } from '../utils/registry';
export function printApiJson() {
console.log(JSON.stringify(require('../../api.json')));
@ -54,6 +54,5 @@ export function runServer() {
}
export async function installBrowsers(browserNames?: BrowserName[]) {
const browsersJsonDir = path.join(__dirname, '..', '..');
await installBrowsersWithProgressBar(browsersJsonDir, browserNames);
await installBrowsersWithProgressBar(browserNames);
}

View File

@ -23,9 +23,7 @@ import * as ProgressBar from 'progress';
import { getProxyForUrl } from 'proxy-from-env';
import * as URL from 'url';
import * as util from 'util';
import { assert, getFromENV } from '../utils/utils';
import * as browserPaths from '../utils/browserPaths';
import { BrowserName, BrowserPlatform, BrowserDescriptor } from '../utils/browserPaths';
import { BrowserName, Registry, hostPlatform } from '../utils/registry';
// `https-proxy-agent` v5 is written in Typescript and exposes generated types.
// However, as of June 2020, its types are generated with tsconfig that enables
@ -42,88 +40,10 @@ const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => f
export type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void;
function getDownloadHost(browserName: BrowserName, revision: number): string {
const envDownloadHost: { [key: string]: string } = {
chromium: 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST',
firefox: 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST',
webkit: 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST',
ffmpeg: 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST',
};
return getFromENV(envDownloadHost[browserName]) ||
getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') ||
'https://playwright.azureedge.net';
}
function getDownloadUrl(browserName: BrowserName, revision: number, platform: BrowserPlatform): string | undefined {
if (browserName === 'chromium') {
return new Map<BrowserPlatform, string>([
['ubuntu18.04', '%s/builds/chromium/%s/chromium-linux.zip'],
['ubuntu20.04', '%s/builds/chromium/%s/chromium-linux.zip'],
['mac10.13', '%s/builds/chromium/%s/chromium-mac.zip'],
['mac10.14', '%s/builds/chromium/%s/chromium-mac.zip'],
['mac10.15', '%s/builds/chromium/%s/chromium-mac.zip'],
['mac11', '%s/builds/chromium/%s/chromium-mac.zip'],
['mac11-arm64', '%s/builds/chromium/%s/chromium-mac-arm64.zip'],
['win32', '%s/builds/chromium/%s/chromium-win32.zip'],
['win64', '%s/builds/chromium/%s/chromium-win64.zip'],
]).get(platform);
}
if (browserName === 'firefox') {
return new Map<BrowserPlatform, string>([
['ubuntu18.04', '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip'],
['ubuntu20.04', '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip'],
['mac10.13', '%s/builds/firefox/%s/firefox-mac-10.14.zip'],
['mac10.14', '%s/builds/firefox/%s/firefox-mac-10.14.zip'],
['mac10.15', '%s/builds/firefox/%s/firefox-mac-10.14.zip'],
['mac11', '%s/builds/firefox/%s/firefox-mac-10.14.zip'],
['mac11-arm64', '%s/builds/firefox/%s/firefox-mac-11.0-arm64.zip'],
['win32', '%s/builds/firefox/%s/firefox-win32.zip'],
['win64', '%s/builds/firefox/%s/firefox-win64.zip'],
]).get(platform);
}
if (browserName === 'webkit') {
return new Map<BrowserPlatform, string | undefined>([
['ubuntu18.04', '%s/builds/webkit/%s/webkit-ubuntu-18.04.zip'],
['ubuntu20.04', '%s/builds/webkit/%s/webkit-ubuntu-20.04.zip'],
['mac10.13', undefined],
['mac10.14', '%s/builds/webkit/%s/webkit-mac-10.14.zip'],
['mac10.15', '%s/builds/webkit/%s/webkit-mac-10.15.zip'],
['mac11', '%s/builds/webkit/%s/webkit-mac-10.15.zip'],
['mac11-arm64', '%s/builds/webkit/%s/webkit-mac-11.0-arm64.zip'],
['win32', '%s/builds/webkit/%s/webkit-win64.zip'],
['win64', '%s/builds/webkit/%s/webkit-win64.zip'],
]).get(platform);
}
if (browserName === 'ffmpeg') {
return new Map<BrowserPlatform, string | undefined>([
['ubuntu18.04', '%s/builds/ffmpeg/%s/ffmpeg-linux.zip'],
['ubuntu20.04', '%s/builds/ffmpeg/%s/ffmpeg-linux.zip'],
['mac10.13', '%s/builds/ffmpeg/%s/ffmpeg-mac.zip'],
['mac10.14', '%s/builds/ffmpeg/%s/ffmpeg-mac.zip'],
['mac10.15', '%s/builds/ffmpeg/%s/ffmpeg-mac.zip'],
['mac11', '%s/builds/ffmpeg/%s/ffmpeg-mac.zip'],
['mac11-arm64', '%s/builds/ffmpeg/%s/ffmpeg-mac.zip'],
['win32', '%s/builds/ffmpeg/%s/ffmpeg-win32.zip'],
['win64', '%s/builds/ffmpeg/%s/ffmpeg-win64.zip'],
]).get(platform);
}
}
function revisionURL(browser: BrowserDescriptor, platform = browserPaths.hostPlatform): string {
const revision = parseInt(browser.revision, 10);
const serverHost = getDownloadHost(browser.name, revision);
const urlTemplate = getDownloadUrl(browser.name, revision, platform);
assert(urlTemplate, `ERROR: Playwright does not support ${browser.name} on ${platform}`);
return util.format(urlTemplate, serverHost, browser.revision);
}
export async function downloadBrowserWithProgressBar(browsersPath: string, browser: BrowserDescriptor): Promise<boolean> {
const browserPath = browserPaths.browserDirectory(browsersPath, browser);
const progressBarName = `${browser.name} v${browser.revision}`;
if (await existsAsync(browserPath)) {
export async function downloadBrowserWithProgressBar(registry: Registry, browserName: BrowserName): Promise<boolean> {
const browserDirectory = registry.browserDirectory(browserName);
const progressBarName = `${browserName} v${registry.revision(browserName)}`;
if (await existsAsync(browserDirectory)) {
// Already downloaded.
return false;
}
@ -145,8 +65,8 @@ export async function downloadBrowserWithProgressBar(browsersPath: string, brows
progressBar.tick(delta);
}
const url = revisionURL(browser);
const zipPath = path.join(os.tmpdir(), `playwright-download-${browser.name}-${browserPaths.hostPlatform}-${browser.revision}.zip`);
const url = registry.downloadURL(browserName);
const zipPath = path.join(os.tmpdir(), `playwright-download-${browserName}-${hostPlatform}-${registry.revision(browserName)}.zip`);
try {
for (let attempt = 1, N = 3; attempt <= N; ++attempt) {
const {error} = await downloadFile(url, zipPath, progress);
@ -161,8 +81,8 @@ export async function downloadBrowserWithProgressBar(browsersPath: string, brows
throw error;
}
}
await extract(zipPath, { dir: browserPath});
await chmodAsync(browserPaths.executablePath(browserPath, browser)!, 0o755);
await extract(zipPath, { dir: browserDirectory});
await chmodAsync(registry.executablePath(browserName)!, 0o755);
} catch (e) {
process.exitCode = 1;
throw e;
@ -170,7 +90,7 @@ export async function downloadBrowserWithProgressBar(browsersPath: string, brows
if (await existsAsync(zipPath))
await unlinkAsync(zipPath);
}
logPolitely(`${progressBarName} downloaded to ${browserPath}`);
logPolitely(`${progressBarName} downloaded to ${browserDirectory}`);
return true;
}

View File

@ -14,15 +14,14 @@
* limitations under the License.
*/
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
import * as removeFolder from 'rimraf';
import * as lockfile from 'proper-lockfile';
import * as browserPaths from '../utils/browserPaths';
import {Registry, allBrowserNames, isBrowserDirectory, BrowserName, registryDirectory} from '../utils/registry';
import * as browserFetcher from './browserFetcher';
import { getAsBooleanFromENV } from '../utils/utils';
import { getAsBooleanFromENV, calculateSha1 } from '../utils/utils';
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
const fsReaddirAsync = util.promisify(fs.readdir.bind(fs));
@ -32,17 +31,18 @@ const fsUnlinkAsync = util.promisify(fs.unlink.bind(fs));
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
const removeFolderAsync = util.promisify(removeFolder);
export async function installBrowsersWithProgressBar(packagePath: string, browserNames?: browserPaths.BrowserName[]) {
const PACKAGE_PATH = path.join(__dirname, '..', '..');
export async function installBrowsersWithProgressBar(browserNames: BrowserName[] = allBrowserNames) {
// PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD should have a value of 0 or 1
if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD')) {
browserFetcher.logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
return false;
}
const browsersPath = browserPaths.browsersPath(packagePath);
await fsMkdirAsync(browsersPath, { recursive: true });
const lockfilePath = path.join(browsersPath, '__dirlock');
const releaseLock = await lockfile.lock(browsersPath, {
await fsMkdirAsync(registryDirectory, { recursive: true });
const lockfilePath = path.join(registryDirectory, '__dirlock');
const releaseLock = await lockfile.lock(registryDirectory, {
retries: {
retries: 10,
// Retry 20 times during 10 minutes with
@ -55,18 +55,18 @@ export async function installBrowsersWithProgressBar(packagePath: string, browse
},
lockfilePath,
});
const linksDir = path.join(browsersPath, '.links');
const linksDir = path.join(registryDirectory, '.links');
try {
await fsMkdirAsync(linksDir, { recursive: true });
await fsWriteFileAsync(path.join(linksDir, sha1(packagePath)), packagePath);
await validateCache(packagePath, browsersPath, linksDir, browserNames);
await fsWriteFileAsync(path.join(linksDir, calculateSha1(PACKAGE_PATH)), PACKAGE_PATH);
await validateCache(linksDir, browserNames);
} finally {
await releaseLock();
}
}
async function validateCache(packagePath: string, browsersPath: string, linksDir: string, browserNames?: browserPaths.BrowserName[]) {
async function validateCache(linksDir: string, browserNames: BrowserName[]) {
// 1. Collect used downloads and package descriptors.
const usedBrowserPaths: Set<string> = new Set();
for (const fileName of await fsReaddirAsync(linksDir)) {
@ -74,15 +74,19 @@ async function validateCache(packagePath: string, browsersPath: string, linksDir
let linkTarget = '';
try {
linkTarget = (await fsReadFileAsync(linkPath)).toString();
const browsersToDownload = await readBrowsersToDownload(linkTarget);
for (const browser of browsersToDownload) {
const usedBrowserPath = browserPaths.browserDirectory(browsersPath, browser);
const browserRevision = parseInt(browser.revision, 10);
const linkRegistry = new Registry(linkTarget);
for (const browserName of allBrowserNames) {
if (!linkRegistry.shouldDownload(browserName))
continue;
const usedBrowserPath = linkRegistry.browserDirectory(browserName);
const browserRevision = linkRegistry.revision(browserName);
// Old browser installations don't have marker file.
const shouldHaveMarkerFile = (browser.name === 'chromium' && browserRevision >= 786218) ||
(browser.name === 'firefox' && browserRevision >= 1128) ||
(browser.name === 'webkit' && browserRevision >= 1307);
if (!shouldHaveMarkerFile || (await fsExistsAsync(browserPaths.markerFilePath(browsersPath, browser))))
const shouldHaveMarkerFile = (browserName === 'chromium' && browserRevision >= 786218) ||
(browserName === 'firefox' && browserRevision >= 1128) ||
(browserName === 'webkit' && browserRevision >= 1307) ||
// All new applications have a marker file right away.
(browserName !== 'firefox' && browserName !== 'chromium' && browserName !== 'webkit');
if (!shouldHaveMarkerFile || (await fsExistsAsync(markerFilePath(usedBrowserPath))))
usedBrowserPaths.add(usedBrowserPath);
}
} catch (e) {
@ -91,37 +95,29 @@ async function validateCache(packagePath: string, browsersPath: string, linksDir
}
// 2. Delete all unused browsers.
let downloadedBrowsers = (await fsReaddirAsync(browsersPath)).map(file => path.join(browsersPath, file));
downloadedBrowsers = downloadedBrowsers.filter(file => browserPaths.isBrowserDirectory(file));
let downloadedBrowsers = (await fsReaddirAsync(registryDirectory)).map(file => path.join(registryDirectory, file));
downloadedBrowsers = downloadedBrowsers.filter(file => isBrowserDirectory(file));
const directories = new Set<string>(downloadedBrowsers);
for (const browserPath of usedBrowserPaths)
directories.delete(browserPath);
for (const browserDirectory of usedBrowserPaths)
directories.delete(browserDirectory);
for (const directory of directories) {
browserFetcher.logPolitely('Removing unused browser at ' + directory);
await removeFolderAsync(directory).catch(e => {});
}
// 3. Install missing browsers for this package.
const myBrowsersToDownload = await readBrowsersToDownload(packagePath, browserNames);
for (const browser of myBrowsersToDownload) {
await browserFetcher.downloadBrowserWithProgressBar(browsersPath, browser).catch(e => {
throw new Error(`Failed to download ${browser.name}, caused by\n${e.stack}`);
const myRegistry = new Registry(PACKAGE_PATH);
for (const browserName of browserNames) {
if (!myRegistry.shouldDownload(browserName))
continue;
await browserFetcher.downloadBrowserWithProgressBar(myRegistry, browserName).catch(e => {
throw new Error(`Failed to download ${browserName}, caused by\n${e.stack}`);
});
await fsWriteFileAsync(browserPaths.markerFilePath(browsersPath, browser), '');
await fsWriteFileAsync(markerFilePath(myRegistry.browserDirectory(browserName)), '');
}
}
async function readBrowsersToDownload(packagePath: string, browserNames?: browserPaths.BrowserName[]) {
const browsers = JSON.parse((await fsReadFileAsync(path.join(packagePath, 'browsers.json'))).toString())['browsers'] as browserPaths.BrowserDescriptor[];
// Older versions do not have "download" field. We assume they need all browsers
// from the list. So we want to skip all browsers that are explicitly marked as "download: false".
return browsers.filter(browser => {
return browserNames ? browserNames.includes(browser.name) : browser.download !== false;
});
function markerFilePath(browserDirectory: string): string {
return path.join(browserDirectory, 'INSTALLATION_COMPLETE');
}
function sha1(data: string): string {
const sum = crypto.createHash('sha1');
sum.update(data);
return sum.digest('hex');
}

View File

@ -22,7 +22,6 @@ import * as stream from 'stream';
import * as util from 'util';
import * as ws from 'ws';
import { createGuid, makeWaitForNextTask } from '../../utils/utils';
import * as browserPaths from '../../utils/browserPaths';
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
import { BrowserContext, validateBrowserContextOptions } from '../browserContext';
import { ProgressController } from '../progress';
@ -57,14 +56,10 @@ export interface SocketBackend extends EventEmitter {
export class Android {
private _backend: Backend;
private _devices = new Map<string, AndroidDevice>();
readonly _ffmpegPath: string | null;
readonly _timeoutSettings: TimeoutSettings;
readonly _playwrightOptions: PlaywrightOptions;
constructor(packagePath: string, backend: Backend, playwrightOptions: PlaywrightOptions, ffmpeg: browserPaths.BrowserDescriptor) {
const browsersPath = browserPaths.browsersPath(packagePath);
const browserPath = browserPaths.browserDirectory(browsersPath, ffmpeg);
this._ffmpegPath = browserPaths.executablePath(browserPath, ffmpeg) || null;
constructor(backend: Backend, playwrightOptions: PlaywrightOptions) {
this._backend = backend;
this._playwrightOptions = playwrightOptions;
this._timeoutSettings = new TimeoutSettings();
@ -276,7 +271,7 @@ export class AndroidDevice extends EventEmitter {
};
validateBrowserContextOptions(options, browserOptions);
const browser = await CRBrowser.connect(androidBrowser, browserOptions, this._android._ffmpegPath);
const browser = await CRBrowser.connect(androidBrowser, browserOptions);
const controller = new ProgressController();
const defaultContext = browser._defaultContext!;
await controller.run(async progress => {

View File

@ -22,6 +22,7 @@ import { Download } from './download';
import { ProxySettings } from './types';
import { ChildProcess } from 'child_process';
import { RecentLogsCollector } from '../utils/debugLogger';
import * as registry from '../utils/registry';
export interface BrowserProcess {
onclose: ((exitCode: number | null, signal: string | null) => void) | undefined;
@ -32,6 +33,7 @@ export interface BrowserProcess {
export type PlaywrightOptions = {
contextListeners: ContextListener[],
registry: registry.Registry,
isInternal: boolean
};

View File

@ -19,7 +19,7 @@ import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
import * as browserPaths from '../utils/browserPaths';
import * as registry from '../utils/registry';
import { ConnectionTransport } from './transport';
import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser';
import { launchProcess, Env, envArrayToObject } from './processLauncher';
@ -38,23 +38,18 @@ const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => f
const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-');
export abstract class BrowserType {
private _name: browserPaths.BrowserName;
private _executablePath: string;
private _browserDescriptor: browserPaths.BrowserDescriptor;
readonly _browserPath: string;
private _name: registry.BrowserName;
readonly _registry: registry.Registry;
readonly _playwrightOptions: PlaywrightOptions;
constructor(packagePath: string, browser: browserPaths.BrowserDescriptor, playwrightOptions: PlaywrightOptions) {
constructor(browserName: registry.BrowserName, playwrightOptions: PlaywrightOptions) {
this._playwrightOptions = playwrightOptions;
this._name = browser.name;
const browsersPath = browserPaths.browsersPath(packagePath);
this._browserDescriptor = browser;
this._browserPath = browserPaths.browserDirectory(browsersPath, browser);
this._executablePath = browserPaths.executablePath(this._browserPath, browser) || '';
this._name = browserName;
this._registry = playwrightOptions.registry;
}
executablePath(): string {
return this._executablePath;
return this._registry.executablePath(this._name) || '';
}
name(): string {
@ -179,7 +174,7 @@ export abstract class BrowserType {
if (!executablePath) {
// We can only validate dependencies for bundled browsers.
await validateHostRequirements(this._browserPath, this._browserDescriptor);
await validateHostRequirements(this._registry, this._name);
}
// Note: it is important to define these variables before launchProcess, so that we don't get

View File

@ -22,7 +22,6 @@ import { kBrowserCloseMessageId } from './crConnection';
import { rewriteErrorMessage } from '../../utils/stackTrace';
import { BrowserType } from '../browserType';
import { ConnectionTransport, ProtocolRequest } from '../transport';
import * as browserPaths from '../../utils/browserPaths';
import { CRDevTools } from './crDevTools';
import { BrowserOptions, PlaywrightOptions } from '../browser';
import * as types from '../types';
@ -30,20 +29,16 @@ import { isDebugMode } from '../../utils/utils';
export class Chromium extends BrowserType {
private _devtools: CRDevTools | undefined;
private _ffmpegPath: string | null;
constructor(packagePath: string, browser: browserPaths.BrowserDescriptor, ffmpeg: browserPaths.BrowserDescriptor, playwrightOptions: PlaywrightOptions) {
super(packagePath, browser, playwrightOptions);
constructor(playwrightOptions: PlaywrightOptions) {
super('chromium', playwrightOptions);
const browsersPath = browserPaths.browsersPath(packagePath);
const browserPath = browserPaths.browserDirectory(browsersPath, ffmpeg);
this._ffmpegPath = browserPaths.executablePath(browserPath, ffmpeg) || null;
if (isDebugMode())
this._devtools = this._createDevTools();
}
private _createDevTools() {
return new CRDevTools(path.join(this._browserPath, 'devtools-preferences.json'));
return new CRDevTools(path.join(this._registry.browserDirectory('chromium'), 'devtools-preferences.json'));
}
async _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<CRBrowser> {
@ -52,7 +47,7 @@ export class Chromium extends BrowserType {
devtools = this._createDevTools();
await (options as any).__testHookForDevTools(devtools);
}
return CRBrowser.connect(transport, options, this._ffmpegPath, devtools);
return CRBrowser.connect(transport, options, devtools);
}
_rewriteStartupError(error: Error): Error {

View File

@ -40,15 +40,14 @@ export class CRBrowser extends Browser {
_devtools?: CRDevTools;
_isMac = false;
private _version = '';
readonly _ffmpegPath: string | null;
private _tracingRecording = false;
private _tracingPath: string | null = '';
private _tracingClient: CRSession | undefined;
static async connect(transport: ConnectionTransport, options: BrowserOptions, ffmpegPath: string | null, devtools?: CRDevTools): Promise<CRBrowser> {
static async connect(transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise<CRBrowser> {
const connection = new CRConnection(transport, options.protocolLogger, options.browserLogsCollector);
const browser = new CRBrowser(connection, options, ffmpegPath);
const browser = new CRBrowser(connection, options);
browser._devtools = devtools;
const session = connection.rootSession;
const version = await session.send('Browser.getVersion');
@ -89,9 +88,8 @@ export class CRBrowser extends Browser {
return browser;
}
constructor(connection: CRConnection, options: BrowserOptions, ffmpegPath: string | null) {
constructor(connection: CRConnection, options: BrowserOptions) {
super(options);
this._ffmpegPath = ffmpegPath;
this._connection = connection;
this._session = this._connection.rootSession;
this._connection.on(ConnectionEvents.Disconnected, () => this._didClose());

View File

@ -811,7 +811,7 @@ class FrameSession {
async _startScreencast(screencastId: string, options: types.PageScreencastOptions): Promise<void> {
assert(!this._screencastId);
const ffmpegPath = this._crPage._browserContext._browser._ffmpegPath;
const ffmpegPath = this._crPage._browserContext._browser.options.registry.executablePath('ffmpeg');
if (!ffmpegPath)
throw new Error('ffmpeg executable was not found');
this._videoRecorder = await VideoRecorder.launch(ffmpegPath, options);

View File

@ -21,7 +21,6 @@ import { CRExecutionContext } from '../chromium/crExecutionContext';
import * as js from '../javascript';
import { Page } from '../page';
import { TimeoutSettings } from '../../utils/timeoutSettings';
import * as browserPaths from '../../utils/browserPaths';
import { WebSocketTransport } from '../transport';
import * as types from '../types';
import { launchProcess, envArrayToObject } from '../processLauncher';
@ -125,12 +124,8 @@ export class ElectronApplication extends EventEmitter {
export class Electron {
private _playwrightOptions: PlaywrightOptions;
private _ffmpegPath: string | null;
constructor(packagePath: string, playwrightOptions: PlaywrightOptions, ffmpeg: browserPaths.BrowserDescriptor) {
const browsersPath = browserPaths.browsersPath(packagePath);
const browserPath = browserPaths.browserDirectory(browsersPath, ffmpeg);
this._ffmpegPath = browserPaths.executablePath(browserPath, ffmpeg) || null;
constructor(playwrightOptions: PlaywrightOptions) {
this._playwrightOptions = playwrightOptions;
}
@ -188,7 +183,7 @@ export class Electron {
protocolLogger: helper.debugProtocolLogger(),
browserLogsCollector,
};
const browser = await CRBrowser.connect(chromeTransport, browserOptions, this._ffmpegPath);
const browser = await CRBrowser.connect(chromeTransport, browserOptions);
app = new ElectronApplication(browser, nodeConnection);
await app._init();
return app;

View File

@ -23,10 +23,14 @@ import { kBrowserCloseMessageId } from './ffConnection';
import { BrowserType } from '../browserType';
import { Env } from '../processLauncher';
import { ConnectionTransport } from '../transport';
import { BrowserOptions } from '../browser';
import { BrowserOptions, PlaywrightOptions } from '../browser';
import * as types from '../types';
export class Firefox extends BrowserType {
constructor(playwrightOptions: PlaywrightOptions) {
super('firefox', playwrightOptions);
}
_connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<FFBrowser> {
return FFBrowser.connect(transport, options);
}

View File

@ -16,7 +16,6 @@
import * as path from 'path';
import { Tracer } from '../trace/tracer';
import * as browserPaths from '../utils/browserPaths';
import { Android } from './android/android';
import { AdbBackend } from './android/backendAdb';
import { PlaywrightOptions } from './browser';
@ -27,6 +26,7 @@ import { serverSelectors } from './selectors';
import { HarTracer } from './supplements/har/harTracer';
import { InspectorController } from './supplements/inspectorController';
import { WebKit } from './webkit/webkit';
import { Registry } from '../utils/registry';
export class Playwright {
readonly selectors = serverSelectors;
@ -37,30 +37,25 @@ export class Playwright {
readonly webkit: WebKit;
readonly options: PlaywrightOptions;
constructor(isInternal: boolean, packagePath: string, browsers: browserPaths.BrowserDescriptor[]) {
constructor(isInternal: boolean) {
this.options = {
isInternal,
registry: new Registry(path.join(__dirname, '..', '..')),
contextListeners: isInternal ? [] : [
new InspectorController(),
new Tracer(),
new HarTracer()
]
};
const chromium = browsers.find(browser => browser.name === 'chromium');
const ffmpeg = browsers.find(browser => browser.name === 'ffmpeg');
this.chromium = new Chromium(packagePath, chromium!, ffmpeg!, this.options);
const firefox = browsers.find(browser => browser.name === 'firefox');
this.firefox = new Firefox(packagePath, firefox!, this.options);
const webkit = browsers.find(browser => browser.name === 'webkit');
this.webkit = new WebKit(packagePath, webkit!, this.options);
this.electron = new Electron(packagePath, this.options, ffmpeg!);
this.android = new Android(packagePath, new AdbBackend(), this.options, ffmpeg!);
this.chromium = new Chromium(this.options);
this.firefox = new Firefox(this.options);
this.webkit = new WebKit(this.options);
this.electron = new Electron(this.options);
this.android = new Android(new AdbBackend(), this.options);
}
}
export function createPlaywright(isInternal = false) {
return new Playwright(isInternal, path.join(__dirname, '..', '..'), require('../../browsers.json')['browsers']);
return new Playwright(isInternal);
}

View File

@ -19,7 +19,7 @@ import * as path from 'path';
import * as os from 'os';
import { spawn } from 'child_process';
import { getUbuntuVersion } from '../utils/ubuntuVersion';
import { linuxLddDirectories, windowsExeAndDllDirectories, BrowserDescriptor } from '../utils/browserPaths.js';
import * as registry from '../utils/registry';
import { printDepsWindowsExecutable } from '../utils/binaryPaths';
const accessAsync = util.promisify(fs.access.bind(fs));
@ -27,14 +27,14 @@ const checkExecutable = (filePath: string) => accessAsync(filePath, fs.constants
const statAsync = util.promisify(fs.stat.bind(fs));
const readdirAsync = util.promisify(fs.readdir.bind(fs));
export async function validateHostRequirements(browserPath: string, browser: BrowserDescriptor) {
export async function validateHostRequirements(registry: registry.Registry, browserName: registry.BrowserName) {
const ubuntuVersion = await getUbuntuVersion();
if (browser.name === 'firefox' && ubuntuVersion === '16.04')
if (browserName === 'firefox' && ubuntuVersion === '16.04')
throw new Error(`Cannot launch firefox on Ubuntu 16.04! Minimum required Ubuntu version for Firefox browser is 18.04`);
if (os.platform() === 'linux')
return await validateDependenciesLinux(browserPath, browser);
return await validateDependenciesLinux(registry, browserName);
if (os.platform() === 'win32' && os.arch() === 'x64')
return await validateDependenciesWindows(browserPath, browser);
return await validateDependenciesWindows(registry, browserName);
}
const DL_OPEN_LIBRARIES = {
@ -55,8 +55,8 @@ function isSupportedWindowsVersion(): boolean {
return major > 6 || (major === 6 && minor > 1);
}
async function validateDependenciesWindows(browserPath: string, browser: BrowserDescriptor) {
const directoryPaths = windowsExeAndDllDirectories(browserPath, browser);
async function validateDependenciesWindows(registry: registry.Registry, browserName: registry.BrowserName) {
const directoryPaths = registry.windowsExeAndDllDirectories(browserName);
const lddPaths: string[] = [];
for (const directoryPath of directoryPaths)
lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
@ -116,8 +116,8 @@ async function validateDependenciesWindows(browserPath: string, browser: Browser
}
}
async function validateDependenciesLinux(browserPath: string, browser: BrowserDescriptor) {
const directoryPaths = linuxLddDirectories(browserPath, browser);
async function validateDependenciesLinux(registry: registry.Registry, browserName: registry.BrowserName) {
const directoryPaths = registry.linuxLddDirectories(browserName);
const lddPaths: string[] = [];
for (const directoryPath of directoryPaths)
lddPaths.push(...(await executablesOrSharedLibraries(directoryPath)));
@ -127,7 +127,7 @@ async function validateDependenciesLinux(browserPath: string, browser: BrowserDe
for (const dep of deps)
missingDeps.add(dep);
}
for (const dep of (await missingDLOPENLibraries(browser)))
for (const dep of (await missingDLOPENLibraries(browserName)))
missingDeps.add(dep);
if (!missingDeps.size)
return;
@ -240,8 +240,8 @@ async function missingFileDependencies(filePath: string, extraLDPaths: string[])
return missingDeps;
}
async function missingDLOPENLibraries(browser: BrowserDescriptor): Promise<string[]> {
const libraries = DL_OPEN_LIBRARIES[browser.name];
async function missingDLOPENLibraries(browserName: registry.BrowserName): Promise<string[]> {
const libraries = DL_OPEN_LIBRARIES[browserName];
if (!libraries.length)
return [];
// NOTE: Using full-qualified path to `ldconfig` since `/sbin` is not part of the

View File

@ -21,10 +21,14 @@ import * as path from 'path';
import { kBrowserCloseMessageId } from './wkConnection';
import { BrowserType } from '../browserType';
import { ConnectionTransport } from '../transport';
import { BrowserOptions } from '../browser';
import { BrowserOptions, PlaywrightOptions } from '../browser';
import * as types from '../types';
export class WebKit extends BrowserType {
constructor(playwrightOptions: PlaywrightOptions) {
super('webkit', playwrightOptions);
}
_connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<WKBrowser> {
return WKBrowser.connect(transport, options);
}

View File

@ -1,183 +0,0 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { execSync } from 'child_process';
import * as os from 'os';
import * as path from 'path';
import { getUbuntuVersionSync } from './ubuntuVersion';
import { getFromENV } from './utils';
export type BrowserName = 'chromium'|'webkit'|'firefox'|'ffmpeg';
export type BrowserPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'mac11'|'mac11-arm64'|'ubuntu18.04'|'ubuntu20.04';
export type BrowserDescriptor = {
name: BrowserName,
revision: string,
download: boolean,
};
export const hostPlatform = ((): BrowserPlatform => {
const platform = os.platform();
if (platform === 'darwin') {
const [major, minor] = execSync('sw_vers -productVersion', {
stdio: ['ignore', 'pipe', 'ignore']
}).toString('utf8').trim().split('.').map(x => parseInt(x, 10));
let arm64 = false;
// BigSur is the first version that might run on Apple Silicon.
if (major >= 11) {
arm64 = execSync('sysctl -in hw.optional.arm64', {
stdio: ['ignore', 'pipe', 'ignore']
}).toString().trim() === '1';
}
// We do not want to differentiate between minor big sur releases
// since they don't change core APIs so far.
const macVersion = major === 10 ? `${major}.${minor}` : `${major}`;
const archSuffix = arm64 ? '-arm64' : '';
return `mac${macVersion}${archSuffix}` as BrowserPlatform;
}
if (platform === 'linux') {
const ubuntuVersion = getUbuntuVersionSync();
if (parseInt(ubuntuVersion, 10) <= 19)
return 'ubuntu18.04';
return 'ubuntu20.04';
}
if (platform === 'win32')
return os.arch() === 'x64' ? 'win64' : 'win32';
return platform as BrowserPlatform;
})();
export function linuxLddDirectories(browserPath: string, browser: BrowserDescriptor): string[] {
if (browser.name === 'chromium')
return [path.join(browserPath, 'chrome-linux')];
if (browser.name === 'firefox')
return [path.join(browserPath, 'firefox')];
if (browser.name === 'webkit') {
return [
path.join(browserPath, 'minibrowser-gtk'),
path.join(browserPath, 'minibrowser-gtk', 'bin'),
path.join(browserPath, 'minibrowser-gtk', 'lib'),
path.join(browserPath, 'minibrowser-wpe'),
path.join(browserPath, 'minibrowser-wpe', 'bin'),
path.join(browserPath, 'minibrowser-wpe', 'lib'),
];
}
return [];
}
export function windowsExeAndDllDirectories(browserPath: string, browser: BrowserDescriptor): string[] {
if (browser.name === 'chromium')
return [path.join(browserPath, 'chrome-win')];
if (browser.name === 'firefox')
return [path.join(browserPath, 'firefox')];
if (browser.name === 'webkit')
return [browserPath];
return [];
}
export function executablePath(browserPath: string, browser: BrowserDescriptor): string | undefined {
let tokens: string[] | undefined;
if (browser.name === 'chromium') {
tokens = new Map<BrowserPlatform, string[]>([
['ubuntu18.04', ['chrome-linux', 'chrome']],
['ubuntu20.04', ['chrome-linux', 'chrome']],
['mac10.13', ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium']],
['mac10.14', ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium']],
['mac10.15', ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium']],
['mac11', ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium']],
['mac11-arm64', ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium']],
['win32', ['chrome-win', 'chrome.exe']],
['win64', ['chrome-win', 'chrome.exe']],
]).get(hostPlatform);
}
if (browser.name === 'firefox') {
tokens = new Map<BrowserPlatform, string[]>([
['ubuntu18.04', ['firefox', 'firefox']],
['ubuntu20.04', ['firefox', 'firefox']],
['mac10.13', ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox']],
['mac10.14', ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox']],
['mac10.15', ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox']],
['mac11', ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox']],
['mac11-arm64', ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox']],
['win32', ['firefox', 'firefox.exe']],
['win64', ['firefox', 'firefox.exe']],
]).get(hostPlatform);
}
if (browser.name === 'webkit') {
tokens = new Map<BrowserPlatform, string[] | undefined>([
['ubuntu18.04', ['pw_run.sh']],
['ubuntu20.04', ['pw_run.sh']],
['mac10.13', undefined],
['mac10.14', ['pw_run.sh']],
['mac10.15', ['pw_run.sh']],
['mac11', ['pw_run.sh']],
['mac11-arm64', ['pw_run.sh']],
['win32', ['Playwright.exe']],
['win64', ['Playwright.exe']],
]).get(hostPlatform);
}
if (browser.name === 'ffmpeg') {
tokens = new Map<BrowserPlatform, string[] | undefined>([
['ubuntu18.04', ['ffmpeg-linux']],
['ubuntu20.04', ['ffmpeg-linux']],
['mac10.13', ['ffmpeg-mac']],
['mac10.14', ['ffmpeg-mac']],
['mac10.15', ['ffmpeg-mac']],
['mac11', ['ffmpeg-mac']],
['mac11-arm64', ['ffmpeg-mac']],
['win32', ['ffmpeg-win32.exe']],
['win64', ['ffmpeg-win64.exe']],
]).get(hostPlatform);
}
return tokens ? path.join(browserPath, ...tokens) : undefined;
}
function cacheDirectory() {
if (process.platform === 'linux')
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
if (process.platform === 'darwin')
return path.join(os.homedir(), 'Library', 'Caches');
if (process.platform === 'win32')
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
throw new Error('Unsupported platform: ' + process.platform);
}
const defaultBrowsersPath = ((): string | undefined => {
const envDefined = getFromENV('PLAYWRIGHT_BROWSERS_PATH');
if (envDefined === '0')
return undefined;
return envDefined || path.join(cacheDirectory(), 'ms-playwright');
})();
export function browsersPath(packagePath: string): string {
return defaultBrowsersPath || path.join(packagePath, '.local-browsers');
}
export function browserDirectory(browsersPath: string, browser: BrowserDescriptor): string {
return path.join(browsersPath, `${browser.name}-${browser.revision}`);
}
export function markerFilePath(browsersPath: string, browser: BrowserDescriptor): string {
return path.join(browserDirectory(browsersPath, browser), 'INSTALLATION_COMPLETE');
}
export function isBrowserDirectory(browserPath: string): boolean {
const baseName = path.basename(browserPath);
return baseName.startsWith('chromium-') || baseName.startsWith('firefox-') || baseName.startsWith('webkit-') || baseName.startsWith('ffmpeg-');
}

270
src/utils/registry.ts Normal file
View File

@ -0,0 +1,270 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { getUbuntuVersionSync } from './ubuntuVersion';
import { assert, getFromENV } from './utils';
export type BrowserName = 'chromium'|'webkit'|'firefox'|'ffmpeg';
export const allBrowserNames: BrowserName[] = ['chromium', 'webkit', 'firefox', 'ffmpeg'];
type BrowserPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'mac11'|'mac11-arm64'|'ubuntu18.04'|'ubuntu20.04';
type BrowserDescriptor = {
name: BrowserName,
revision: string,
download: boolean,
};
const EXECUTABLE_PATHS = {
chromium: {
'ubuntu18.04': ['chrome-linux', 'chrome'],
'ubuntu20.04': ['chrome-linux', 'chrome'],
'mac10.13': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac10.14': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac10.15': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac11': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac11-arm64': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'win32': ['chrome-win', 'chrome.exe'],
'win64': ['chrome-win', 'chrome.exe'],
},
firefox: {
'ubuntu18.04': ['firefox', 'firefox'],
'ubuntu20.04': ['firefox', 'firefox'],
'mac10.13': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac10.14': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac11': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac11-arm64': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'win32': ['firefox', 'firefox.exe'],
'win64': ['firefox', 'firefox.exe'],
},
webkit: {
'ubuntu18.04': ['pw_run.sh'],
'ubuntu20.04': ['pw_run.sh'],
'mac10.13': undefined,
'mac10.14': ['pw_run.sh'],
'mac10.15': ['pw_run.sh'],
'mac11': ['pw_run.sh'],
'mac11-arm64': ['pw_run.sh'],
'win32': ['Playwright.exe'],
'win64': ['Playwright.exe'],
},
ffmpeg: {
'ubuntu18.04': ['ffmpeg-linux'],
'ubuntu20.04': ['ffmpeg-linux'],
'mac10.13': ['ffmpeg-mac'],
'mac10.14': ['ffmpeg-mac'],
'mac10.15': ['ffmpeg-mac'],
'mac11': ['ffmpeg-mac'],
'mac11-arm64': ['ffmpeg-mac'],
'win32': ['ffmpeg-win32.exe'],
'win64': ['ffmpeg-win64.exe'],
},
};
const DOWNLOAD_URLS = {
chromium: {
'ubuntu18.04': '%s/builds/chromium/%s/chromium-linux.zip',
'ubuntu20.04': '%s/builds/chromium/%s/chromium-linux.zip',
'mac10.13': '%s/builds/chromium/%s/chromium-mac.zip',
'mac10.14': '%s/builds/chromium/%s/chromium-mac.zip',
'mac10.15': '%s/builds/chromium/%s/chromium-mac.zip',
'mac11': '%s/builds/chromium/%s/chromium-mac.zip',
'mac11-arm64': '%s/builds/chromium/%s/chromium-mac-arm64.zip',
'win32': '%s/builds/chromium/%s/chromium-win32.zip',
'win64': '%s/builds/chromium/%s/chromium-win64.zip',
},
firefox: {
'ubuntu18.04': '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip',
'ubuntu20.04': '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip',
'mac10.13': '%s/builds/firefox/%s/firefox-mac-10.14.zip',
'mac10.14': '%s/builds/firefox/%s/firefox-mac-10.14.zip',
'mac10.15': '%s/builds/firefox/%s/firefox-mac-10.14.zip',
'mac11': '%s/builds/firefox/%s/firefox-mac-10.14.zip',
'mac11-arm64': '%s/builds/firefox/%s/firefox-mac-11.0-arm64.zip',
'win32': '%s/builds/firefox/%s/firefox-win32.zip',
'win64': '%s/builds/firefox/%s/firefox-win64.zip',
},
webkit: {
'ubuntu18.04': '%s/builds/webkit/%s/webkit-ubuntu-18.04.zip',
'ubuntu20.04': '%s/builds/webkit/%s/webkit-ubuntu-20.04.zip',
'mac10.13': undefined,
'mac10.14': '%s/builds/webkit/%s/webkit-mac-10.14.zip',
'mac10.15': '%s/builds/webkit/%s/webkit-mac-10.15.zip',
'mac11': '%s/builds/webkit/%s/webkit-mac-10.15.zip',
'mac11-arm64': '%s/builds/webkit/%s/webkit-mac-11.0-arm64.zip',
'win32': '%s/builds/webkit/%s/webkit-win64.zip',
'win64': '%s/builds/webkit/%s/webkit-win64.zip',
},
ffmpeg: {
'ubuntu18.04': '%s/builds/ffmpeg/%s/ffmpeg-linux.zip',
'ubuntu20.04': '%s/builds/ffmpeg/%s/ffmpeg-linux.zip',
'mac10.13': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.14': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.15': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac11': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac11-arm64': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
'win32': '%s/builds/ffmpeg/%s/ffmpeg-win32.zip',
'win64': '%s/builds/ffmpeg/%s/ffmpeg-win64.zip',
},
};
export const hostPlatform = ((): BrowserPlatform => {
const platform = os.platform();
if (platform === 'darwin') {
const [major, minor] = execSync('sw_vers -productVersion', {
stdio: ['ignore', 'pipe', 'ignore']
}).toString('utf8').trim().split('.').map(x => parseInt(x, 10));
let arm64 = false;
// BigSur is the first version that might run on Apple Silicon.
if (major >= 11) {
arm64 = execSync('sysctl -in hw.optional.arm64', {
stdio: ['ignore', 'pipe', 'ignore']
}).toString().trim() === '1';
}
// We do not want to differentiate between minor big sur releases
// since they don't change core APIs so far.
const macVersion = major === 10 ? `${major}.${minor}` : `${major}`;
const archSuffix = arm64 ? '-arm64' : '';
return `mac${macVersion}${archSuffix}` as BrowserPlatform;
}
if (platform === 'linux') {
const ubuntuVersion = getUbuntuVersionSync();
if (parseInt(ubuntuVersion, 10) <= 19)
return 'ubuntu18.04';
return 'ubuntu20.04';
}
if (platform === 'win32')
return os.arch() === 'x64' ? 'win64' : 'win32';
return platform as BrowserPlatform;
})();
export const registryDirectory = (() => {
const envDefined = getFromENV('PLAYWRIGHT_BROWSERS_PATH');
if (envDefined === '0')
return path.join(__dirname, '..', '..', '.local-browsers');
if (envDefined)
return path.resolve(process.cwd(), envDefined);
return path.join(cacheDirectory(), 'ms-playwright');
})();
export function isBrowserDirectory(browserDirectory: string): boolean {
const baseName = path.basename(browserDirectory);
for (const browserName of allBrowserNames) {
if (baseName.startsWith(browserName + '-'))
return true;
}
return false;
}
export class Registry {
private _descriptors: BrowserDescriptor[];
constructor(packagePath: string) {
const browsersJSON = JSON.parse(fs.readFileSync(path.join(packagePath, 'browsers.json'), 'utf8'));
this._descriptors = browsersJSON['browsers'];
}
browserDirectory(browserName: BrowserName): string {
const browser = this._descriptors.find(browser => browser.name === browserName);
assert(browser, `ERROR: Playwright does not support ${browserName}`);
return path.join(registryDirectory, `${browser.name}-${browser.revision}`);
}
revision(browserName: BrowserName): number {
const browser = this._descriptors.find(browser => browser.name === browserName);
assert(browser, `ERROR: Playwright does not support ${browserName}`);
return parseInt(browser.revision, 10);
}
linuxLddDirectories(browserName: BrowserName): string[] {
const browserDirectory = this.browserDirectory(browserName);
if (browserName === 'chromium')
return [path.join(browserDirectory, 'chrome-linux')];
if (browserName === 'firefox')
return [path.join(browserDirectory, 'firefox')];
if (browserName === 'webkit') {
return [
path.join(browserDirectory, 'minibrowser-gtk'),
path.join(browserDirectory, 'minibrowser-gtk', 'bin'),
path.join(browserDirectory, 'minibrowser-gtk', 'lib'),
path.join(browserDirectory, 'minibrowser-wpe'),
path.join(browserDirectory, 'minibrowser-wpe', 'bin'),
path.join(browserDirectory, 'minibrowser-wpe', 'lib'),
];
}
return [];
}
windowsExeAndDllDirectories(browserName: BrowserName): string[] {
const browserDirectory = this.browserDirectory(browserName);
if (browserName === 'chromium')
return [path.join(browserDirectory, 'chrome-win')];
if (browserName === 'firefox')
return [path.join(browserDirectory, 'firefox')];
if (browserName === 'webkit')
return [browserDirectory];
return [];
}
executablePath(browserName: BrowserName): string | undefined {
const browserDirectory = this.browserDirectory(browserName);
const tokens = EXECUTABLE_PATHS[browserName][hostPlatform];
return tokens ? path.join(browserDirectory, ...tokens) : undefined;
}
downloadURL(browserName: BrowserName): string {
const browser = this._descriptors.find(browser => browser.name === browserName);
assert(browser, `ERROR: Playwright does not support ${browserName}`);
const envDownloadHost: { [key: string]: string } = {
chromium: 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST',
firefox: 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST',
webkit: 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST',
ffmpeg: 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST',
};
const downloadHost = getFromENV(envDownloadHost[browserName]) ||
getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') ||
'https://playwright.azureedge.net';
const urlTemplate = DOWNLOAD_URLS[browserName][hostPlatform];
assert(urlTemplate, `ERROR: Playwright does not support ${browserName} on ${hostPlatform}`);
return util.format(urlTemplate, downloadHost, browser.revision);
}
shouldDownload(browserName: BrowserName): boolean {
// Older versions do not have "download" field. We assume they need all browsers
// from the list. So we want to skip all browsers that are explicitly marked as "download: false".
const browser = this._descriptors.find(browser => browser.name === browserName);
return !!browser && browser.download !== false;
}
}
function cacheDirectory() {
if (process.platform === 'linux')
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
if (process.platform === 'darwin')
return path.join(os.homedir(), 'Library', 'Caches');
if (process.platform === 'win32')
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
throw new Error('Unsupported platform: ' + process.platform);
}

View File

@ -136,7 +136,7 @@ export function monotonicTime(): number {
return seconds * 1000 + (nanoseconds / 1000 | 0) / 1000;
}
export function calculateSha1(buffer: Buffer): string {
export function calculateSha1(buffer: Buffer | string): string {
const hash = crypto.createHash('sha1');
hash.update(buffer);
return hash.digest('hex');

View File

@ -19,14 +19,10 @@ import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import { PNG } from 'pngjs';
import * as browserPaths from '../src/utils/browserPaths';
import { Registry } from '../src/utils/registry';
const browsersJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'browsers.json'), 'utf8'));
const ffmpegDescriptor = browsersJSON.browsers.find(({name}) => name === 'ffmpeg');
const browsersPath = browserPaths.browsersPath(path.join(__dirname, '..'));
const browserPath = browserPaths.browserDirectory(browsersPath, ffmpegDescriptor);
const ffmpeg = browserPaths.executablePath(browserPath, ffmpegDescriptor) || '';
const registry = new Registry(path.join(__dirname, '..'));
const ffmpeg = registry.executablePath('ffmpeg') || '';
export class VideoPlayer {
fileName: string;

View File

@ -4,7 +4,7 @@ const fs = require('fs');
const util = require('util');
const path = require('path');
const {spawn} = require('child_process');
const browserPaths = require('playwright/lib/utils/browserPaths.js');
const {registryDirectory} = require('playwright/lib/utils/registry.js');
const readdirAsync = util.promisify(fs.readdir.bind(fs));
const readFileAsync = util.promisify(fs.readFile.bind(fs));
@ -27,12 +27,11 @@ const DL_OPEN_LIBRARIES = {
(async () => {
console.log('Working on:', await getDistributionName());
console.log('Started at:', currentTime());
const allBrowsersPath = browserPaths.browsersPath();
const browserDescriptors = (await readdirAsync(allBrowsersPath)).filter(dir => !dir.startsWith('.')).map(dir => ({
const browserDescriptors = (await readdirAsync(registryDirectory)).filter(dir => !dir.startsWith('.')).map(dir => ({
// Full browser name, e.g. `webkit-1144`
name: dir,
// Full patch to browser files
path: path.join(allBrowsersPath, dir),
path: path.join(registryDirectory, dir),
// All files that we will try to inspect for missing dependencies.
filePaths: [],
// All libraries that are missing for the browser.

View File

@ -17,6 +17,7 @@
*/
const path = require('path');
const {Registry} = require('../lib/utils/registry');
const fs = require('fs');
const protocolGenerator = require('./protocol-types-generator');
const {execSync} = require('child_process');
@ -69,14 +70,11 @@ Example:
// 3. Download new browser.
console.log('\nDownloading new browser...');
const { installBrowsersWithProgressBar } = require('../lib/install/installer');
await installBrowsersWithProgressBar(ROOT_PATH);
await installBrowsersWithProgressBar();
// 4. Generate types.
console.log('\nGenerating protocol types...');
const browser = { name: browserName, revision };
const browserPaths = require('../lib/utils/browserPaths');
const browserDir = browserPaths.browserDirectory(browserPaths.browsersPath(ROOT_PATH), browser);
const executablePath = browserPaths.executablePath(browserDir, browser);
const executablePath = new Registry(ROOT_PATH).executablePath(browserName);
await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn);
// 5. Update docs.