chore: replace Registry api with Executable list (#7544)
This commit is contained in:
parent
053d39cb19
commit
57c5e4d8cf
|
|
@ -31,7 +31,7 @@ import { Page } from '../client/page';
|
||||||
import { BrowserType } from '../client/browserType';
|
import { BrowserType } from '../client/browserType';
|
||||||
import { BrowserContextOptions, LaunchOptions } from '../client/types';
|
import { BrowserContextOptions, LaunchOptions } from '../client/types';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { allBrowserNames, BrowserName, registry } from '../utils/registry';
|
import { registry, Executable } from '../utils/registry';
|
||||||
import * as utils from '../utils/utils';
|
import * as utils from '../utils/utils';
|
||||||
|
|
||||||
const SCRIPTS_DIRECTORY = path.join(__dirname, '..', '..', 'bin');
|
const SCRIPTS_DIRECTORY = path.join(__dirname, '..', '..', 'bin');
|
||||||
|
|
@ -122,24 +122,24 @@ program
|
||||||
program
|
program
|
||||||
.command('install [browserType...]')
|
.command('install [browserType...]')
|
||||||
.description('ensure browsers necessary for this version of Playwright are installed')
|
.description('ensure browsers necessary for this version of Playwright are installed')
|
||||||
.action(async function(args) {
|
.action(async function(args: any[]) {
|
||||||
try {
|
try {
|
||||||
// Install default browsers when invoked without arguments.
|
// Install default browsers when invoked without arguments.
|
||||||
if (!args.length) {
|
if (!args.length) {
|
||||||
await registry.installBinaries();
|
await registry.install();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const browserNames: Set<BrowserName> = new Set(args.filter((browser: any) => allBrowserNames.has(browser)));
|
const binaries = args.map(arg => registry.findExecutable(arg)).filter(b => !!b) as Executable[];
|
||||||
const browserChannels: Set<BrowserChannel> = new Set(args.filter((browser: any) => allBrowserChannels.has(browser)));
|
const browserChannels: Set<BrowserChannel> = new Set(args.filter(browser => allBrowserChannels.has(browser)));
|
||||||
const faultyArguments: string[] = args.filter((browser: any) => !browserNames.has(browser) && !browserChannels.has(browser));
|
const faultyArguments: string[] = args.filter((browser: any) => !binaries.find(b => b.name === browser) && !browserChannels.has(browser));
|
||||||
if (faultyArguments.length) {
|
if (faultyArguments.length) {
|
||||||
console.log(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall}`);
|
console.log(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (browserNames.has('chromium') || browserChannels.has('chrome-beta') || browserChannels.has('chrome') || browserChannels.has('msedge') || browserChannels.has('msedge-beta'))
|
if (browserChannels.has('chrome-beta') || browserChannels.has('chrome') || browserChannels.has('msedge') || browserChannels.has('msedge-beta'))
|
||||||
browserNames.add('ffmpeg');
|
binaries.push(registry.findExecutable('ffmpeg')!);
|
||||||
if (browserNames.size)
|
if (binaries.length)
|
||||||
await registry.installBinaries([...browserNames]);
|
await registry.install(binaries);
|
||||||
for (const browserChannel of browserChannels)
|
for (const browserChannel of browserChannels)
|
||||||
await installBrowserChannel(browserChannel);
|
await installBrowserChannel(browserChannel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -188,10 +188,12 @@ async function installBrowserChannel(channel: BrowserChannel) {
|
||||||
program
|
program
|
||||||
.command('install-deps [browserType...]')
|
.command('install-deps [browserType...]')
|
||||||
.description('install dependencies necessary to run browsers (will ask for sudo permissions)')
|
.description('install dependencies necessary to run browsers (will ask for sudo permissions)')
|
||||||
.action(async function(browserTypes) {
|
.action(async function(browserTypes: string[]) {
|
||||||
try {
|
try {
|
||||||
// TODO: verify the list and print supported browserTypes in the error message.
|
// TODO: verify the list and print supported browserTypes in the error message.
|
||||||
await registry.installDeps(browserTypes);
|
const binaries = browserTypes.map(arg => registry.findExecutable(arg)).filter(b => !!b) as Executable[];
|
||||||
|
// When passed no arguments, assume default browsers.
|
||||||
|
await registry.installDeps(browserTypes.length ? binaries : undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Failed to install browser dependencies\n${e}`);
|
console.log(`Failed to install browser dependencies\n${e}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export abstract class BrowserType extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
executablePath(channel?: string): string {
|
executablePath(channel?: string): string {
|
||||||
return registry.executablePath(this._name) || '';
|
return registry.findExecutable(this._name).maybeExecutablePath() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
name(): string {
|
name(): string {
|
||||||
|
|
@ -167,10 +167,9 @@ export abstract class BrowserType extends SdkObject {
|
||||||
throw new Error(errorMessageLines.join('\n'));
|
throw new Error(errorMessageLines.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only validate dependencies for downloadable browsers.
|
// Do not validate dependencies for custom binaries.
|
||||||
const browserName: BrowserName = (options.channel || this._name) as BrowserName;
|
if (!executablePath && !options.channel)
|
||||||
if (!executablePath && registry.isSupportedBrowser(browserName))
|
await registry.findExecutable(this._name).validateHostRequirements();
|
||||||
await registry.validateHostRequirements(browserName);
|
|
||||||
|
|
||||||
let wsEndpointCallback: ((wsEndpoint: string) => void) | undefined;
|
let wsEndpointCallback: ((wsEndpoint: string) => void) | undefined;
|
||||||
const shouldWaitForWSListening = options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port'));
|
const shouldWaitForWSListening = options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port'));
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class Chromium extends BrowserType {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
let executablePath = undefined;
|
let executablePath = undefined;
|
||||||
if ((channel as any) === 'chromium-with-symbols')
|
if ((channel as any) === 'chromium-with-symbols')
|
||||||
executablePath = registry.executablePath('chromium-with-symbols');
|
executablePath = registry.findExecutable('chromium-with-symbols')!.executablePathIfExists();
|
||||||
else
|
else
|
||||||
executablePath = findChromiumChannel(channel);
|
executablePath = findChromiumChannel(channel);
|
||||||
assert(executablePath, `unsupported chromium channel "${channel}"`);
|
assert(executablePath, `unsupported chromium channel "${channel}"`);
|
||||||
|
|
@ -103,7 +103,9 @@ export class Chromium extends BrowserType {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createDevTools() {
|
private _createDevTools() {
|
||||||
return new CRDevTools(path.join(registry.browserDirectory('chromium'), 'devtools-preferences.json'));
|
// TODO: this is totally wrong when using channels.
|
||||||
|
const directory = registry.findExecutable('chromium').directoryIfExists();
|
||||||
|
return directory ? new CRDevTools(path.join(directory, 'devtools-preferences.json')) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<CRBrowser> {
|
async _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<CRBrowser> {
|
||||||
|
|
|
||||||
|
|
@ -845,7 +845,7 @@ class FrameSession {
|
||||||
|
|
||||||
async _createVideoRecorder(screencastId: string, options: types.PageScreencastOptions): Promise<void> {
|
async _createVideoRecorder(screencastId: string, options: types.PageScreencastOptions): Promise<void> {
|
||||||
assert(!this._screencastId);
|
assert(!this._screencastId);
|
||||||
const ffmpegPath = registry.executablePath('ffmpeg');
|
const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePathIfExists();
|
||||||
if (!ffmpegPath)
|
if (!ffmpegPath)
|
||||||
throw new Error('ffmpeg executable was not found');
|
throw new Error('ffmpeg executable was not found');
|
||||||
if (!canAccessFile(ffmpegPath)) {
|
if (!canAccessFile(ffmpegPath)) {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class Firefox extends BrowserType {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
let executablePath = undefined;
|
let executablePath = undefined;
|
||||||
if ((channel as any) === 'firefox-beta')
|
if ((channel as any) === 'firefox-beta')
|
||||||
executablePath = registry.executablePath('firefox-beta');
|
executablePath = registry.findExecutable('firefox-beta')!.executablePathIfExists();
|
||||||
assert(executablePath, `unsupported firefox channel "${channel}"`);
|
assert(executablePath, `unsupported firefox channel "${channel}"`);
|
||||||
assert(fs.existsSync(executablePath), `"${channel}" channel is not installed. Try running 'npx playwright install ${channel}'`);
|
assert(fs.existsSync(executablePath), `"${channel}" channel is not installed. Try running 'npx playwright install ${channel}'`);
|
||||||
return executablePath;
|
return executablePath;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { PersistentSnapshotStorage, TraceModel } from './traceModel';
|
||||||
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
import { ServerRouteHandler, HttpServer } from '../../../utils/httpServer';
|
||||||
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
import { SnapshotServer } from '../../snapshot/snapshotServer';
|
||||||
import * as consoleApiSource from '../../../generated/consoleApiSource';
|
import * as consoleApiSource from '../../../generated/consoleApiSource';
|
||||||
import { canAccessFile, isUnderTest } from '../../../utils/utils';
|
import { isUnderTest } from '../../../utils/utils';
|
||||||
import { internalCallMetadata } from '../../instrumentation';
|
import { internalCallMetadata } from '../../instrumentation';
|
||||||
import { ProgressController } from '../../progress';
|
import { ProgressController } from '../../progress';
|
||||||
import { BrowserContext } from '../../browserContext';
|
import { BrowserContext } from '../../browserContext';
|
||||||
|
|
@ -140,7 +140,7 @@ export class TraceViewer {
|
||||||
// Null means no installation and no channels found.
|
// Null means no installation and no channels found.
|
||||||
let channel = null;
|
let channel = null;
|
||||||
if (traceViewerBrowser === 'chromium') {
|
if (traceViewerBrowser === 'chromium') {
|
||||||
if (canAccessFile(registry.executablePath('chromium')!)) {
|
if (registry.findExecutable('chromium').executablePathIfExists()) {
|
||||||
// This means we have a browser downloaded.
|
// This means we have a browser downloaded.
|
||||||
channel = undefined;
|
channel = undefined;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,10 @@ import * as util from 'util';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import lockfile from 'proper-lockfile';
|
import lockfile from 'proper-lockfile';
|
||||||
import { getUbuntuVersion } from './ubuntuVersion';
|
import { getUbuntuVersion } from './ubuntuVersion';
|
||||||
import { assert, getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform } from './utils';
|
import { getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile } from './utils';
|
||||||
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
|
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
|
||||||
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
|
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
|
||||||
|
|
||||||
export type BrowserName = 'chromium'|'chromium-with-symbols'|'webkit'|'firefox'|'firefox-beta'|'ffmpeg';
|
|
||||||
export const allBrowserNames: Set<BrowserName> = new Set(['chromium', 'chromium-with-symbols', 'webkit', 'firefox', 'ffmpeg', 'firefox-beta']);
|
|
||||||
|
|
||||||
const PACKAGE_PATH = path.join(__dirname, '..', '..');
|
const PACKAGE_PATH = path.join(__dirname, '..', '..');
|
||||||
|
|
||||||
const EXECUTABLE_PATHS = {
|
const EXECUTABLE_PATHS = {
|
||||||
|
|
@ -42,17 +39,6 @@ const EXECUTABLE_PATHS = {
|
||||||
'win32': ['chrome-win', 'chrome.exe'],
|
'win32': ['chrome-win', 'chrome.exe'],
|
||||||
'win64': ['chrome-win', 'chrome.exe'],
|
'win64': ['chrome-win', 'chrome.exe'],
|
||||||
},
|
},
|
||||||
'chromium-with-symbols': {
|
|
||||||
'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': {
|
'firefox': {
|
||||||
'ubuntu18.04': ['firefox', 'firefox'],
|
'ubuntu18.04': ['firefox', 'firefox'],
|
||||||
'ubuntu20.04': ['firefox', 'firefox'],
|
'ubuntu20.04': ['firefox', 'firefox'],
|
||||||
|
|
@ -64,17 +50,6 @@ const EXECUTABLE_PATHS = {
|
||||||
'win32': ['firefox', 'firefox.exe'],
|
'win32': ['firefox', 'firefox.exe'],
|
||||||
'win64': ['firefox', 'firefox.exe'],
|
'win64': ['firefox', 'firefox.exe'],
|
||||||
},
|
},
|
||||||
'firefox-beta': {
|
|
||||||
'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': {
|
'webkit': {
|
||||||
'ubuntu18.04': ['pw_run.sh'],
|
'ubuntu18.04': ['pw_run.sh'],
|
||||||
'ubuntu20.04': ['pw_run.sh'],
|
'ubuntu20.04': ['pw_run.sh'],
|
||||||
|
|
@ -202,18 +177,18 @@ const registryDirectory = (() => {
|
||||||
|
|
||||||
function isBrowserDirectory(browserDirectory: string): boolean {
|
function isBrowserDirectory(browserDirectory: string): boolean {
|
||||||
const baseName = path.basename(browserDirectory);
|
const baseName = path.basename(browserDirectory);
|
||||||
for (const browserName of allBrowserNames) {
|
for (const browserName of allDownloadable) {
|
||||||
if (baseName.startsWith(browserName + '-'))
|
if (baseName.startsWith(browserName + '-'))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BrowserDescriptor = {
|
type BrowsersJSONDescriptor = {
|
||||||
name: BrowserName,
|
name: string,
|
||||||
revision: string,
|
revision: string,
|
||||||
installByDefault: boolean,
|
installByDefault: boolean,
|
||||||
browserDirectory: string,
|
dir: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function readDescriptors(packagePath: string) {
|
function readDescriptors(packagePath: string) {
|
||||||
|
|
@ -223,7 +198,7 @@ function readDescriptors(packagePath: string) {
|
||||||
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
|
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
|
||||||
const revision = revisionOverride || obj.revision;
|
const revision = revisionOverride || obj.revision;
|
||||||
const browserDirectoryPrefix = revisionOverride ? `${name}_${hostPlatform}_special` : `${name}`;
|
const browserDirectoryPrefix = revisionOverride ? `${name}_${hostPlatform}_special` : `${name}`;
|
||||||
const descriptor: BrowserDescriptor = {
|
const descriptor: BrowsersJSONDescriptor = {
|
||||||
name,
|
name,
|
||||||
revision,
|
revision,
|
||||||
installByDefault: !!obj.installByDefault,
|
installByDefault: !!obj.installByDefault,
|
||||||
|
|
@ -232,123 +207,176 @@ function readDescriptors(packagePath: string) {
|
||||||
// are prefixes of others, e.g. 'webkit' is a prefix of `webkit-technology-preview`.
|
// are prefixes of others, e.g. 'webkit' is a prefix of `webkit-technology-preview`.
|
||||||
// To avoid older registries erroneously removing 'webkit-technology-preview', we have to
|
// To avoid older registries erroneously removing 'webkit-technology-preview', we have to
|
||||||
// ensure that browser folders to never include dashes inside.
|
// ensure that browser folders to never include dashes inside.
|
||||||
browserDirectory: browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision,
|
dir: path.join(registryDirectory, browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision),
|
||||||
};
|
};
|
||||||
return descriptor;
|
return descriptor;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||||
|
type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-with-symbols';
|
||||||
|
const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-with-symbols'];
|
||||||
|
|
||||||
|
export interface Executable {
|
||||||
|
type: 'browser' | 'tool';
|
||||||
|
name: BrowserName | InternalTool;
|
||||||
|
browserName: BrowserName | undefined;
|
||||||
|
installType: 'download-by-default' | 'download-on-demand';
|
||||||
|
maybeExecutablePath(): string | undefined;
|
||||||
|
executablePathIfExists(): string | undefined;
|
||||||
|
directoryIfExists(): string | undefined;
|
||||||
|
validateHostRequirements(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecutableImpl extends Executable {
|
||||||
|
_download?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export class Registry {
|
export class Registry {
|
||||||
private _descriptors: BrowserDescriptor[];
|
private _executables: ExecutableImpl[];
|
||||||
|
|
||||||
constructor(packagePath: string) {
|
constructor(packagePath: string) {
|
||||||
this._descriptors = readDescriptors(packagePath);
|
const descriptors = readDescriptors(packagePath);
|
||||||
}
|
const executablePath = (dir: string, name: keyof typeof EXECUTABLE_PATHS) => {
|
||||||
|
const tokens = EXECUTABLE_PATHS[name][hostPlatform];
|
||||||
browserDirectory(browserName: BrowserName): string {
|
return tokens ? path.join(dir, ...tokens) : undefined;
|
||||||
const browser = this._descriptors.find(browser => browser.name === browserName);
|
|
||||||
assert(browser, `ERROR: Playwright does not support ${browserName}`);
|
|
||||||
return path.join(registryDirectory, browser.browserDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _revision(browserName: BrowserName): string {
|
|
||||||
const browser = this._descriptors.find(browser => browser.name === browserName);
|
|
||||||
assert(browser, `ERROR: Playwright does not support ${browserName}`);
|
|
||||||
return browser.revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
executablePath(browserName: BrowserName): string | undefined {
|
|
||||||
const browserDirectory = this.browserDirectory(browserName);
|
|
||||||
const tokens = EXECUTABLE_PATHS[browserName][hostPlatform];
|
|
||||||
return tokens ? path.join(browserDirectory, ...tokens) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _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',
|
|
||||||
'chromium-with-symbols': 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST',
|
|
||||||
'firefox': 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST',
|
|
||||||
'firefox-beta': 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST',
|
|
||||||
'webkit': 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST',
|
|
||||||
'ffmpeg': 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST',
|
|
||||||
};
|
};
|
||||||
const downloadHost = getFromENV(envDownloadHost[browserName]) ||
|
const directoryIfExists = (d: string) => fs.existsSync(d) ? d : undefined;
|
||||||
getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') ||
|
const executablePathIfExists = (e: string | undefined) => e && canAccessFile(e) ? e : undefined;
|
||||||
'https://playwright.azureedge.net';
|
this._executables = [];
|
||||||
const urlTemplate = DOWNLOAD_URLS[browserName][hostPlatform];
|
|
||||||
assert(urlTemplate, `ERROR: Playwright does not support ${browserName} on ${hostPlatform}`);
|
const chromium = descriptors.find(d => d.name === 'chromium')!;
|
||||||
return util.format(urlTemplate, downloadHost, browser.revision);
|
const chromiumExecutable = executablePath(chromium.dir, 'chromium');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'browser',
|
||||||
|
name: 'chromium',
|
||||||
|
browserName: 'chromium',
|
||||||
|
directoryIfExists: () => directoryIfExists(chromium.dir),
|
||||||
|
maybeExecutablePath: () => chromiumExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(chromiumExecutable),
|
||||||
|
installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => this._validateHostRequirements('chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
|
_download: () => this._downloadExecutable(chromium, chromiumExecutable, DOWNLOAD_URLS['chromium'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const chromiumWithSymbols = descriptors.find(d => d.name === 'chromium-with-symbols')!;
|
||||||
|
const chromiumWithSymbolsExecutable = executablePath(chromiumWithSymbols.dir, 'chromium');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'tool',
|
||||||
|
name: 'chromium-with-symbols',
|
||||||
|
browserName: 'chromium',
|
||||||
|
directoryIfExists: () => directoryIfExists(chromiumWithSymbols.dir),
|
||||||
|
maybeExecutablePath: () => chromiumWithSymbolsExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(chromiumWithSymbolsExecutable),
|
||||||
|
installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => this._validateHostRequirements('chromium', chromiumWithSymbols.dir, ['chrome-linux'], [], ['chrome-win']),
|
||||||
|
_download: () => this._downloadExecutable(chromiumWithSymbols, chromiumWithSymbolsExecutable, DOWNLOAD_URLS['chromium-with-symbols'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const firefox = descriptors.find(d => d.name === 'firefox')!;
|
||||||
|
const firefoxExecutable = executablePath(firefox.dir, 'firefox');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'browser',
|
||||||
|
name: 'firefox',
|
||||||
|
browserName: 'firefox',
|
||||||
|
directoryIfExists: () => directoryIfExists(firefox.dir),
|
||||||
|
maybeExecutablePath: () => firefoxExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(firefoxExecutable),
|
||||||
|
installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => this._validateHostRequirements('firefox', firefox.dir, ['firefox'], [], ['firefox']),
|
||||||
|
_download: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_URLS['firefox'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta')!;
|
||||||
|
const firefoxBetaExecutable = executablePath(firefoxBeta.dir, 'firefox');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'tool',
|
||||||
|
name: 'firefox-beta',
|
||||||
|
browserName: 'firefox',
|
||||||
|
directoryIfExists: () => directoryIfExists(firefoxBeta.dir),
|
||||||
|
maybeExecutablePath: () => firefoxBetaExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(firefoxBetaExecutable),
|
||||||
|
installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => this._validateHostRequirements('firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']),
|
||||||
|
_download: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable, DOWNLOAD_URLS['firefox-beta'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const webkit = descriptors.find(d => d.name === 'webkit')!;
|
||||||
|
const webkitExecutable = executablePath(webkit.dir, 'webkit');
|
||||||
|
const webkitLinuxLddDirectories = [
|
||||||
|
path.join('minibrowser-gtk'),
|
||||||
|
path.join('minibrowser-gtk', 'bin'),
|
||||||
|
path.join('minibrowser-gtk', 'lib'),
|
||||||
|
path.join('minibrowser-wpe'),
|
||||||
|
path.join('minibrowser-wpe', 'bin'),
|
||||||
|
path.join('minibrowser-wpe', 'lib'),
|
||||||
|
];
|
||||||
|
this._executables.push({
|
||||||
|
type: 'browser',
|
||||||
|
name: 'webkit',
|
||||||
|
browserName: 'webkit',
|
||||||
|
directoryIfExists: () => directoryIfExists(webkit.dir),
|
||||||
|
maybeExecutablePath: () => webkitExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(webkitExecutable),
|
||||||
|
installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => this._validateHostRequirements('webkit', webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
|
||||||
|
_download: () => this._downloadExecutable(webkit, webkitExecutable, DOWNLOAD_URLS['webkit'][hostPlatform], 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!;
|
||||||
|
const ffmpegExecutable = executablePath(ffmpeg.dir, 'ffmpeg');
|
||||||
|
this._executables.push({
|
||||||
|
type: 'tool',
|
||||||
|
name: 'ffmpeg',
|
||||||
|
browserName: undefined,
|
||||||
|
directoryIfExists: () => directoryIfExists(ffmpeg.dir),
|
||||||
|
maybeExecutablePath: () => ffmpegExecutable,
|
||||||
|
executablePathIfExists: () => executablePathIfExists(ffmpegExecutable),
|
||||||
|
installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand',
|
||||||
|
validateHostRequirements: () => Promise.resolve(),
|
||||||
|
_download: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_URLS['ffmpeg'][hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isSupportedBrowser(browserName: string): boolean {
|
findExecutable(name: BrowserName): Executable;
|
||||||
// We retain browsers if they are found in the descriptor.
|
findExecutable(name: string): Executable | undefined;
|
||||||
// Note, however, that there are older versions out in the wild that rely on
|
findExecutable(name: string): Executable | undefined {
|
||||||
// the "download" field in the browser descriptor and use its value
|
return this._executables.find(b => b.name === name);
|
||||||
// to retain and download browsers.
|
|
||||||
// As of v1.10, we decided to abandon "download" field.
|
|
||||||
return this._descriptors.some(browser => browser.name === browserName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _installByDefault(): BrowserName[] {
|
private _addRequirementsAndDedupe(executables: Executable[] | undefined): ExecutableImpl[] {
|
||||||
return this._descriptors.filter(browser => browser.installByDefault).map(browser => browser.name);
|
const set = new Set<ExecutableImpl>();
|
||||||
|
if (!executables)
|
||||||
|
executables = this._executables.filter(executable => executable.installType === 'download-by-default');
|
||||||
|
for (const executable of executables as ExecutableImpl[]) {
|
||||||
|
set.add(executable);
|
||||||
|
if (executable.browserName === 'chromium')
|
||||||
|
set.add(this.findExecutable('ffmpeg')!);
|
||||||
|
}
|
||||||
|
return Array.from(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateHostRequirements(browserName: BrowserName) {
|
private async _validateHostRequirements(browserName: BrowserName, browserDirectory: string, linuxLddDirectories: string[], dlOpenLibraries: string[], windowsExeAndDllDirectories: string[]) {
|
||||||
if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS')) {
|
if (getAsBooleanFromENV('PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS')) {
|
||||||
process.stdout.write('Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.\n');
|
process.stdout.write('Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ubuntuVersion = await getUbuntuVersion();
|
const ubuntuVersion = await getUbuntuVersion();
|
||||||
if ((browserName === 'firefox' || browserName === 'firefox-beta') && ubuntuVersion === '16.04')
|
if (browserName === 'firefox' && ubuntuVersion === '16.04')
|
||||||
throw new Error(`Cannot launch ${browserName} on Ubuntu 16.04! Minimum required Ubuntu version for Firefox browser is 18.04`);
|
throw new Error(`Cannot launch Firefox on Ubuntu 16.04! Minimum required Ubuntu version for Firefox browser is 18.04`);
|
||||||
const browserDirectory = this.browserDirectory(browserName);
|
|
||||||
|
|
||||||
if (os.platform() === 'linux') {
|
if (os.platform() === 'linux')
|
||||||
const dlOpenLibraries: string[] = [];
|
return await validateDependenciesLinux(linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries);
|
||||||
const linuxLddDirectories: string[] = [];
|
if (os.platform() === 'win32' && os.arch() === 'x64')
|
||||||
if (browserName === 'chromium' || browserName === 'chromium-with-symbols')
|
return await validateDependenciesWindows(windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d)));
|
||||||
linuxLddDirectories.push(path.join(browserDirectory, 'chrome-linux'));
|
|
||||||
if (browserName === 'webkit') {
|
|
||||||
linuxLddDirectories.push(
|
|
||||||
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'),
|
|
||||||
);
|
|
||||||
dlOpenLibraries.push('libGLESv2.so.2', 'libx264.so');
|
|
||||||
}
|
|
||||||
if (browserName === 'firefox' || browserName === 'firefox-beta')
|
|
||||||
linuxLddDirectories.push(path.join(browserDirectory, 'firefox'));
|
|
||||||
return await validateDependenciesLinux(linuxLddDirectories, dlOpenLibraries);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (os.platform() === 'win32' && os.arch() === 'x64') {
|
|
||||||
const windowsExeAndDllDirectories: string[] = [];
|
|
||||||
if (browserName === 'chromium' || browserName === 'chromium-with-symbols')
|
|
||||||
windowsExeAndDllDirectories.push(path.join(browserDirectory, 'chrome-win'));
|
|
||||||
if (browserName === 'firefox' || browserName === 'firefox-beta')
|
|
||||||
windowsExeAndDllDirectories.push(path.join(browserDirectory, 'firefox'));
|
|
||||||
if (browserName === 'webkit')
|
|
||||||
windowsExeAndDllDirectories.push(browserDirectory);
|
|
||||||
return await validateDependenciesWindows(windowsExeAndDllDirectories);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async installDeps(browserNames: BrowserName[]) {
|
async installDeps(executablesToInstallDeps?: Executable[]) {
|
||||||
|
const executables = this._addRequirementsAndDedupe(executablesToInstallDeps);
|
||||||
const targets = new Set<'chromium' | 'firefox' | 'webkit' | 'tools'>();
|
const targets = new Set<'chromium' | 'firefox' | 'webkit' | 'tools'>();
|
||||||
if (!browserNames.length)
|
for (const executable of executables) {
|
||||||
browserNames = this._installByDefault();
|
if (executable.browserName)
|
||||||
for (const browserName of browserNames) {
|
targets.add(executable.browserName);
|
||||||
if (browserName === 'chromium' || browserName === 'chromium-with-symbols')
|
|
||||||
targets.add('chromium');
|
|
||||||
if (browserName === 'firefox' || browserName === 'firefox-beta')
|
|
||||||
targets.add('firefox');
|
|
||||||
if (browserName === 'webkit')
|
|
||||||
targets.add('webkit');
|
|
||||||
}
|
}
|
||||||
targets.add('tools');
|
targets.add('tools');
|
||||||
if (os.platform() === 'win32')
|
if (os.platform() === 'win32')
|
||||||
|
|
@ -357,9 +385,8 @@ export class Registry {
|
||||||
return await installDependenciesLinux(targets);
|
return await installDependenciesLinux(targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
async installBinaries(browserNames?: BrowserName[]) {
|
async install(executablesToInstall?: Executable[]) {
|
||||||
if (!browserNames)
|
const executables = this._addRequirementsAndDedupe(executablesToInstall);
|
||||||
browserNames = this._installByDefault();
|
|
||||||
await fs.promises.mkdir(registryDirectory, { recursive: true });
|
await fs.promises.mkdir(registryDirectory, { recursive: true });
|
||||||
const lockfilePath = path.join(registryDirectory, '__dirlock');
|
const lockfilePath = path.join(registryDirectory, '__dirlock');
|
||||||
const releaseLock = await lockfile.lock(registryDirectory, {
|
const releaseLock = await lockfile.lock(registryDirectory, {
|
||||||
|
|
@ -385,22 +412,34 @@ export class Registry {
|
||||||
// Remove stale browsers.
|
// Remove stale browsers.
|
||||||
await this._validateInstallationCache(linksDir);
|
await this._validateInstallationCache(linksDir);
|
||||||
|
|
||||||
// Install missing browsers for this package.
|
// Install browsers for this package.
|
||||||
for (const browserName of browserNames) {
|
for (const executable of executables) {
|
||||||
const revision = this._revision(browserName);
|
if (executable._download)
|
||||||
const browserDirectory = this.browserDirectory(browserName);
|
await executable._download();
|
||||||
const title = `${browserName} v${revision}`;
|
else
|
||||||
const downloadFileName = `playwright-download-${browserName}-${hostPlatform}-${revision}.zip`;
|
throw new Error(`ERROR: Playwright does not support installing ${executable.name}`);
|
||||||
await downloadBrowserWithProgressBar(title, browserDirectory, this.executablePath(browserName)!, this._downloadURL(browserName), downloadFileName).catch(e => {
|
|
||||||
throw new Error(`Failed to download ${title}, caused by\n${e.stack}`);
|
|
||||||
});
|
|
||||||
await fs.promises.writeFile(markerFilePath(browserDirectory), '');
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await releaseLock();
|
await releaseLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _downloadExecutable(descriptor: BrowsersJSONDescriptor, executablePath: string | undefined, downloadURLTemplate: string | undefined, downloadHostEnv: string) {
|
||||||
|
if (!downloadURLTemplate || !executablePath)
|
||||||
|
throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${hostPlatform}`);
|
||||||
|
const downloadHost =
|
||||||
|
(downloadHostEnv && getFromENV(downloadHostEnv)) ||
|
||||||
|
getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') ||
|
||||||
|
'https://playwright.azureedge.net';
|
||||||
|
const downloadURL = util.format(downloadURLTemplate, downloadHost, descriptor.revision);
|
||||||
|
const title = `${descriptor.name} v${descriptor.revision}`;
|
||||||
|
const downloadFileName = `playwright-download-${descriptor.name}-${hostPlatform}-${descriptor.revision}.zip`;
|
||||||
|
await downloadBrowserWithProgressBar(title, descriptor.dir, executablePath, downloadURL, downloadFileName).catch(e => {
|
||||||
|
throw new Error(`Failed to download ${title}, caused by\n${e.stack}`);
|
||||||
|
});
|
||||||
|
await fs.promises.writeFile(markerFilePath(descriptor.dir), '');
|
||||||
|
}
|
||||||
|
|
||||||
private async _validateInstallationCache(linksDir: string) {
|
private async _validateInstallationCache(linksDir: string) {
|
||||||
// 1. Collect used downloads and package descriptors.
|
// 1. Collect used downloads and package descriptors.
|
||||||
const usedBrowserPaths: Set<string> = new Set();
|
const usedBrowserPaths: Set<string> = new Set();
|
||||||
|
|
@ -410,11 +449,16 @@ export class Registry {
|
||||||
try {
|
try {
|
||||||
linkTarget = (await fs.promises.readFile(linkPath)).toString();
|
linkTarget = (await fs.promises.readFile(linkPath)).toString();
|
||||||
const descriptors = readDescriptors(linkTarget);
|
const descriptors = readDescriptors(linkTarget);
|
||||||
for (const browserName of allBrowserNames) {
|
for (const browserName of allDownloadable) {
|
||||||
|
// We retain browsers if they are found in the descriptor.
|
||||||
|
// Note, however, that there are older versions out in the wild that rely on
|
||||||
|
// the "download" field in the browser descriptor and use its value
|
||||||
|
// to retain and download browsers.
|
||||||
|
// As of v1.10, we decided to abandon "download" field.
|
||||||
const descriptor = descriptors.find(d => d.name === browserName);
|
const descriptor = descriptors.find(d => d.name === browserName);
|
||||||
if (!descriptor)
|
if (!descriptor)
|
||||||
continue;
|
continue;
|
||||||
const usedBrowserPath = path.join(registryDirectory, descriptor.browserDirectory);
|
const usedBrowserPath = descriptor.dir;
|
||||||
const browserRevision = parseInt(descriptor.revision, 10);
|
const browserRevision = parseInt(descriptor.revision, 10);
|
||||||
// Old browser installations don't have marker file.
|
// Old browser installations don't have marker file.
|
||||||
const shouldHaveMarkerFile = (browserName === 'chromium' && browserRevision >= 786218) ||
|
const shouldHaveMarkerFile = (browserName === 'chromium' && browserRevision >= 786218) ||
|
||||||
|
|
@ -454,7 +498,7 @@ export async function installDefaultBrowsersForNpmInstall() {
|
||||||
logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
|
logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await registry.installBinaries();
|
await registry.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registry = new Registry(PACKAGE_PATH);
|
export const registry = new Registry(PACKAGE_PATH);
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ it('should not throw with remote-debugging-port argument', async ({browserType,
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open devtools when "devtools: true" option is given', async ({browserType, browserOptions, mode, platform}) => {
|
it('should open devtools when "devtools: true" option is given', async ({browserType, browserOptions, mode, platform, channel}) => {
|
||||||
it.skip(mode !== 'default' || platform === 'win32');
|
it.skip(mode !== 'default' || platform === 'win32' || !!channel);
|
||||||
|
|
||||||
let devtoolsCallback;
|
let devtoolsCallback;
|
||||||
const devtoolsPromise = new Promise(f => devtoolsCallback = f);
|
const devtoolsPromise = new Promise(f => devtoolsCallback = f);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import path from 'path';
|
||||||
import { spawnSync } from 'child_process';
|
import { spawnSync } from 'child_process';
|
||||||
import { registry } from '../../src/utils/registry';
|
import { registry } from '../../src/utils/registry';
|
||||||
|
|
||||||
const ffmpeg = registry.executablePath('ffmpeg') || '';
|
const ffmpeg = registry.findExecutable('ffmpeg')!.executablePathIfExists() || '';
|
||||||
|
|
||||||
export class VideoPlayer {
|
export class VideoPlayer {
|
||||||
videoWidth: number;
|
videoWidth: number;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { spawnSync } from 'child_process';
|
||||||
import { PNG } from 'pngjs';
|
import { PNG } from 'pngjs';
|
||||||
import { registry } from '../src/utils/registry';
|
import { registry } from '../src/utils/registry';
|
||||||
|
|
||||||
const ffmpeg = registry.executablePath('ffmpeg') || '';
|
const ffmpeg = registry.findExecutable('ffmpeg')!.executablePathIfExists() || '';
|
||||||
|
|
||||||
export class VideoPlayer {
|
export class VideoPlayer {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ Example:
|
||||||
|
|
||||||
// 4. Generate types.
|
// 4. Generate types.
|
||||||
console.log('\nGenerating protocol types...');
|
console.log('\nGenerating protocol types...');
|
||||||
const executablePath = new Registry(ROOT_PATH).executablePath(browserName);
|
const executablePath = new Registry(ROOT_PATH).findBinary(binaryName).executablePathIfExists();
|
||||||
await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn);
|
await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn);
|
||||||
|
|
||||||
// 5. Update docs.
|
// 5. Update docs.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue