chore: simplify Registry api (#7451)

This commit is contained in:
Dmitry Gozman 2021-07-09 16:10:23 -07:00 committed by GitHub
parent 9897fc5b60
commit 65606c093a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 106 additions and 117 deletions

View file

@ -31,7 +31,7 @@ import { Page } from '../client/page';
import { BrowserType } from '../client/browserType';
import { BrowserContextOptions, LaunchOptions } from '../client/types';
import { spawn } from 'child_process';
import { allBrowserNames, BrowserName, Registry } from '../utils/registry';
import { allBrowserNames, BrowserName, registry } from '../utils/registry';
import * as utils from '../utils/utils';
const SCRIPTS_DIRECTORY = path.join(__dirname, '..', '..', 'bin');
@ -126,7 +126,7 @@ program
try {
// Install default browsers when invoked without arguments.
if (!args.length) {
await Registry.currentPackageRegistry().installBinaries();
await registry.installBinaries();
return;
}
const browserNames: Set<BrowserName> = new Set(args.filter((browser: any) => allBrowserNames.has(browser)));
@ -139,7 +139,7 @@ program
if (browserNames.has('chromium') || browserChannels.has('chrome-beta') || browserChannels.has('chrome') || browserChannels.has('msedge') || browserChannels.has('msedge-beta'))
browserNames.add('ffmpeg');
if (browserNames.size)
await Registry.currentPackageRegistry().installBinaries([...browserNames]);
await registry.installBinaries([...browserNames]);
for (const browserChannel of browserChannels)
await installBrowserChannel(browserChannel);
} catch (e) {
@ -191,7 +191,7 @@ program
.action(async function(browserTypes) {
try {
// TODO: verify the list and print supported browserTypes in the error message.
await Registry.currentPackageRegistry().installDeps(browserTypes);
await registry.installDeps(browserTypes);
} catch (e) {
console.log(`Failed to install browser dependencies\n${e}`);
process.exit(1);

View file

@ -21,7 +21,6 @@ import { Download } from './download';
import { ProxySettings } from './types';
import { ChildProcess } from 'child_process';
import { RecentLogsCollector } from '../utils/debugLogger';
import * as registry from '../utils/registry';
import { SdkObject } from './instrumentation';
import { Artifact } from './artifact';
import { Selectors } from './selectors';
@ -34,7 +33,6 @@ export interface BrowserProcess {
}
export type PlaywrightOptions = {
registry: registry.Registry,
rootSdkObject: SdkObject,
selectors: Selectors,
loopbackProxyOverride?: () => string,

View file

@ -18,7 +18,7 @@ import fs from 'fs';
import * as os from 'os';
import path from 'path';
import { BrowserContext, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
import * as registry from '../utils/registry';
import { registry, BrowserName } from '../utils/registry';
import { ConnectionTransport, WebSocketTransport } from './transport';
import { BrowserOptions, Browser, BrowserProcess, PlaywrightOptions } from './browser';
import { launchProcess, Env, envArrayToObject } from '../utils/processLauncher';
@ -34,20 +34,18 @@ import { CallMetadata, SdkObject } from './instrumentation';
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export abstract class BrowserType extends SdkObject {
private _name: registry.BrowserName;
readonly _registry: registry.Registry;
private _name: BrowserName;
readonly _playwrightOptions: PlaywrightOptions;
constructor(browserName: registry.BrowserName, playwrightOptions: PlaywrightOptions) {
constructor(browserName: BrowserName, playwrightOptions: PlaywrightOptions) {
super(playwrightOptions.rootSdkObject, 'browser-type');
this.attribution.browserType = this;
this._playwrightOptions = playwrightOptions;
this._name = browserName;
this._registry = playwrightOptions.registry;
}
executablePath(channel?: string): string {
return this._registry.executablePath(this._name) || '';
return registry.executablePath(this._name) || '';
}
name(): string {
@ -170,9 +168,9 @@ export abstract class BrowserType extends SdkObject {
}
// Only validate dependencies for downloadable browsers.
const browserName: registry.BrowserName = (options.channel || this._name) as registry.BrowserName;
if (!executablePath && this._registry.isSupportedBrowser(browserName))
await this._registry.validateHostRequirements(browserName);
const browserName: BrowserName = (options.channel || this._name) as BrowserName;
if (!executablePath && registry.isSupportedBrowser(browserName))
await registry.validateHostRequirements(browserName);
let wsEndpointCallback: ((wsEndpoint: string) => void) | undefined;
const shouldWaitForWSListening = options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port'));

View file

@ -35,6 +35,7 @@ import { helper } from '../helper';
import { CallMetadata } from '../instrumentation';
import { findChromiumChannel } from './findChromiumChannel';
import http from 'http';
import { registry } from '../../utils/registry';
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
@ -52,7 +53,7 @@ export class Chromium extends BrowserType {
if (channel) {
let executablePath = undefined;
if ((channel as any) === 'chromium-with-symbols')
executablePath = this._registry.executablePath('chromium-with-symbols');
executablePath = registry.executablePath('chromium-with-symbols');
else
executablePath = findChromiumChannel(channel);
assert(executablePath, `unsupported chromium channel "${channel}"`);
@ -102,7 +103,7 @@ export class Chromium extends BrowserType {
}
private _createDevTools() {
return new CRDevTools(path.join(this._registry.browserDirectory('chromium'), 'devtools-preferences.json'));
return new CRDevTools(path.join(registry.browserDirectory('chromium'), 'devtools-preferences.json'));
}
async _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<CRBrowser> {

View file

@ -40,6 +40,7 @@ import { assert, headersArrayToObject, createGuid, canAccessFile } from '../../u
import { VideoRecorder } from './videoRecorder';
import { Progress } from '../progress';
import { DragManager } from './crDragDrop';
import { registry } from '../../utils/registry';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -844,7 +845,7 @@ class FrameSession {
async _createVideoRecorder(screencastId: string, options: types.PageScreencastOptions): Promise<void> {
assert(!this._screencastId);
const ffmpegPath = this._crPage._browserContext._browser.options.registry.executablePath('ffmpeg');
const ffmpegPath = registry.executablePath('ffmpeg');
if (!ffmpegPath)
throw new Error('ffmpeg executable was not found');
if (!canAccessFile(ffmpegPath)) {

View file

@ -26,6 +26,7 @@ import { Env } from '../../utils/processLauncher';
import { ConnectionTransport } from '../transport';
import { BrowserOptions, PlaywrightOptions } from '../browser';
import * as types from '../types';
import { registry } from '../../utils/registry';
export class Firefox extends BrowserType {
constructor(playwrightOptions: PlaywrightOptions) {
@ -36,7 +37,7 @@ export class Firefox extends BrowserType {
if (channel) {
let executablePath = undefined;
if ((channel as any) === 'firefox-beta')
executablePath = this._registry.executablePath('firefox-beta');
executablePath = registry.executablePath('firefox-beta');
assert(executablePath, `unsupported firefox channel "${channel}"`);
assert(fs.existsSync(executablePath), `"${channel}" channel is not installed. Try running 'npx playwright install ${channel}'`);
return executablePath;

View file

@ -14,7 +14,6 @@
* limitations under the License.
*/
import path from 'path';
import { Android } from './android/android';
import { AdbBackend } from './android/backendAdb';
import { PlaywrightOptions } from './browser';
@ -23,7 +22,6 @@ import { Electron } from './electron/electron';
import { Firefox } from './firefox/firefox';
import { Selectors } from './selectors';
import { WebKit } from './webkit/webkit';
import { Registry } from '../utils/registry';
import { CallMetadata, createInstrumentation, SdkObject } from './instrumentation';
import { debugLogger } from '../utils/debugLogger';
import { PortForwardingServer } from './socksSocket';
@ -48,7 +46,6 @@ export class Playwright extends SdkObject {
}
});
this.options = {
registry: new Registry(path.join(__dirname, '..', '..')),
rootSdkObject: this,
selectors: new Selectors(),
};

View file

@ -29,7 +29,7 @@ import { canAccessFile, isUnderTest } from '../../../utils/utils';
import { internalCallMetadata } from '../../instrumentation';
import { ProgressController } from '../../progress';
import { BrowserContext } from '../../browserContext';
import { Registry } from '../../../utils/registry';
import { registry } from '../../../utils/registry';
import { findChromiumChannel } from '../../chromium/findChromiumChannel';
import { installAppIcon } from '../../chromium/crApp';
@ -140,7 +140,6 @@ export class TraceViewer {
// Null means no installation and no channels found.
let channel = null;
if (traceViewerBrowser === 'chromium') {
const registry = Registry.currentPackageRegistry();
if (canAccessFile(registry.executablePath('chromium')!)) {
// This means we have a browser downloaded.
channel = undefined;

View file

@ -19,8 +19,7 @@ import * as jpeg from 'jpeg-js';
import path from 'path';
import * as png from 'pngjs';
import { splitErrorMessage } from '../../utils/stackTrace';
import { hostPlatform } from '../../utils/registry';
import { assert, createGuid, debugAssert, headersArrayToObject, headersObjectToArray } from '../../utils/utils';
import { assert, createGuid, debugAssert, headersArrayToObject, headersObjectToArray, hostPlatform } from '../../utils/utils';
import * as accessibility from '../accessibility';
import * as dialog from '../dialog';
import * as dom from '../dom';
@ -769,14 +768,13 @@ export class WKPage implements PageDelegate {
private _toolbarHeight(): number {
if (this._page._browserContext._browser?.options.headful)
return hostPlatform.startsWith('10.15') ? 55 : 59;
return hostPlatform === 'mac10.15' ? 55 : 59;
return 0;
}
private async _startVideo(options: types.PageScreencastOptions): Promise<void> {
assert(!this._recordingVideoFile);
const START_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.start' : 'Screencast.startVideo';
const { screencastId } = await this._pageProxySession.send(START_VIDEO_PROTOCOL_COMMAND as any, {
const { screencastId } = await this._pageProxySession.send('Screencast.startVideo', {
file: options.outputFile,
width: options.width,
height: options.height,
@ -789,8 +787,7 @@ export class WKPage implements PageDelegate {
private async _stopVideo(): Promise<void> {
if (!this._recordingVideoFile)
return;
const STOP_VIDEO_PROTOCOL_COMMAND = hostPlatform === 'mac10.14' ? 'Screencast.stop' : 'Screencast.stopVideo';
await this._pageProxySession.sendMayFail(STOP_VIDEO_PROTOCOL_COMMAND as any);
await this._pageProxySession.sendMayFail('Screencast.stopVideo');
this._recordingVideoFile = null;
}

View file

@ -51,7 +51,7 @@ export async function downloadBrowserWithProgressBar(title: string, browserDirec
}
const url = downloadURL;
const zipPath = path.join(os.tmpdir(), `${downloadFileName}.zip`);
const zipPath = path.join(os.tmpdir(), downloadFileName);
try {
for (let attempt = 1, N = 3; attempt <= N; ++attempt) {
debugLogger.log('install', `downloading ${progressBarName} - attempt #${attempt}`);

View file

@ -20,8 +20,8 @@ import path from 'path';
import * as util from 'util';
import * as fs from 'fs';
import lockfile from 'proper-lockfile';
import { getUbuntuVersion, getUbuntuVersionSync } from './ubuntuVersion';
import { assert, getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync } from './utils';
import { getUbuntuVersion } from './ubuntuVersion';
import { assert, getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform } from './utils';
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
@ -30,14 +30,6 @@ export const allBrowserNames: Set<BrowserName> = new Set(['chromium', 'chromium-
const PACKAGE_PATH = path.join(__dirname, '..', '..');
type BrowserPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'mac11'|'mac11-arm64'|'ubuntu18.04'|'ubuntu20.04';
type BrowserDescriptor = {
name: BrowserName,
revision: string,
installByDefault: boolean,
browserDirectory: string,
};
const EXECUTABLE_PATHS = {
'chromium': {
'ubuntu18.04': ['chrome-linux', 'chrome'],
@ -176,41 +168,7 @@ const DOWNLOAD_URLS = {
},
};
export const hostPlatform = ((): BrowserPlatform => {
const platform = os.platform();
if (platform === 'darwin') {
const ver = os.release().split('.').map((a: string) => parseInt(a, 10));
let macVersion = '';
if (ver[0] < 18) {
// Everything before 10.14 is considered 10.13.
macVersion = 'mac10.13';
} else if (ver[0] === 18) {
macVersion = 'mac10.14';
} else if (ver[0] === 19) {
macVersion = 'mac10.15';
} else {
// ver[0] >= 20
const LAST_STABLE_MAC_MAJOR_VERSION = 11;
// Best-effort support for MacOS beta versions.
macVersion = 'mac' + Math.min(ver[0] - 9, LAST_STABLE_MAC_MAJOR_VERSION);
// BigSur is the first version that might run on Apple Silicon.
if (os.cpus().some(cpu => cpu.model.includes('Apple')))
macVersion += '-arm64';
}
return macVersion 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 registryDirectory = (() => {
let result: string;
const envDefined = getFromENV('PLAYWRIGHT_BROWSERS_PATH');
@ -242,7 +200,7 @@ export const registryDirectory = (() => {
return result;
})();
export function isBrowserDirectory(browserDirectory: string): boolean {
function isBrowserDirectory(browserDirectory: string): boolean {
const baseName = path.basename(browserDirectory);
for (const browserName of allBrowserNames) {
if (baseName.startsWith(browserName + '-'))
@ -251,38 +209,40 @@ export function isBrowserDirectory(browserDirectory: string): boolean {
return false;
}
let currentPackageRegistry: Registry | undefined = undefined;
type BrowserDescriptor = {
name: BrowserName,
revision: string,
installByDefault: boolean,
browserDirectory: string,
};
function readDescriptors(packagePath: string) {
const browsersJSON = require(path.join(packagePath, 'browsers.json'));
return (browsersJSON['browsers'] as any[]).map(obj => {
const name = obj.name;
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
const revision = revisionOverride || obj.revision;
const browserDirectoryPrefix = revisionOverride ? `${name}_${hostPlatform}_special` : `${name}`;
const descriptor: BrowserDescriptor = {
name,
revision,
installByDefault: !!obj.installByDefault,
// Method `isBrowserDirectory` determines directory to be browser iff
// it starts with some browser name followed by '-'. Some browser names
// 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
// ensure that browser folders to never include dashes inside.
browserDirectory: browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision,
};
return descriptor;
});
}
export class Registry {
private _descriptors: BrowserDescriptor[];
static currentPackageRegistry() {
if (!currentPackageRegistry)
currentPackageRegistry = new Registry(PACKAGE_PATH);
return currentPackageRegistry;
}
constructor(packagePath: string) {
// require() needs to be used there otherwise it breaks on Vercel serverless
// functions. See https://github.com/microsoft/playwright/pull/6186
const browsersJSON = require(path.join(packagePath, 'browsers.json'));
this._descriptors = browsersJSON['browsers'].map((obj: any) => {
const name = obj.name;
const revisionOverride = (obj.revisionOverrides || {})[hostPlatform];
const revision = revisionOverride || obj.revision;
const browserDirectoryPrefix = revisionOverride ? `${name}_${hostPlatform}_special` : `${name}`;
return {
name,
revision,
installByDefault: !!obj.installByDefault,
// Method `isBrowserDirectory` determines directory to be browser iff
// it starts with some browser name followed by '-'. Some browser names
// 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
// ensure that browser folders to never include dashes inside.
browserDirectory: browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision,
};
});
this._descriptors = readDescriptors(packagePath);
}
browserDirectory(browserName: BrowserName): string {
@ -291,10 +251,10 @@ export class Registry {
return path.join(registryDirectory, browser.browserDirectory);
}
private _revision(browserName: BrowserName): number {
private _revision(browserName: BrowserName): string {
const browser = this._descriptors.find(browser => browser.name === browserName);
assert(browser, `ERROR: Playwright does not support ${browserName}`);
return parseInt(browser.revision, 10);
return browser.revision;
}
executablePath(browserName: BrowserName): string | undefined {
@ -430,7 +390,7 @@ export class Registry {
const revision = this._revision(browserName);
const browserDirectory = this.browserDirectory(browserName);
const title = `${browserName} v${revision}`;
const downloadFileName = `playwright-download-${browserName}-${hostPlatform}-${revision}`;
const downloadFileName = `playwright-download-${browserName}-${hostPlatform}-${revision}.zip`;
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}`);
});
@ -449,12 +409,13 @@ export class Registry {
let linkTarget = '';
try {
linkTarget = (await fs.promises.readFile(linkPath)).toString();
const linkRegistry = new Registry(linkTarget);
const descriptors = readDescriptors(linkTarget);
for (const browserName of allBrowserNames) {
if (!linkRegistry.isSupportedBrowser(browserName))
const descriptor = descriptors.find(d => d.name === browserName);
if (!descriptor)
continue;
const usedBrowserPath = linkRegistry.browserDirectory(browserName);
const browserRevision = linkRegistry._revision(browserName);
const usedBrowserPath = path.join(registryDirectory, descriptor.browserDirectory);
const browserRevision = parseInt(descriptor.revision, 10);
// Old browser installations don't have marker file.
const shouldHaveMarkerFile = (browserName === 'chromium' && browserRevision >= 786218) ||
(browserName === 'firefox' && browserRevision >= 1128) ||
@ -493,5 +454,7 @@ export async function installDefaultBrowsersForNpmInstall() {
logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
return false;
}
await Registry.currentPackageRegistry().installBinaries();
await registry.installBinaries();
}
export const registry = new Registry(PACKAGE_PATH);

View file

@ -22,6 +22,7 @@ import os from 'os';
import { spawn } from 'child_process';
import { getProxyForUrl } from 'proxy-from-env';
import * as URL from 'url';
import { getUbuntuVersionSync } from './ubuntuVersion';
// `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
@ -321,3 +322,38 @@ export function constructURLBasedOnBaseURL(baseURL: string | undefined, givenURL
return givenURL;
}
}
export type HostPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'mac11'|'mac11-arm64'|'ubuntu18.04'|'ubuntu20.04';
export const hostPlatform = ((): HostPlatform => {
const platform = os.platform();
if (platform === 'darwin') {
const ver = os.release().split('.').map((a: string) => parseInt(a, 10));
let macVersion = '';
if (ver[0] < 18) {
// Everything before 10.14 is considered 10.13.
macVersion = 'mac10.13';
} else if (ver[0] === 18) {
macVersion = 'mac10.14';
} else if (ver[0] === 19) {
macVersion = 'mac10.15';
} else {
// ver[0] >= 20
const LAST_STABLE_MAC_MAJOR_VERSION = 11;
// Best-effort support for MacOS beta versions.
macVersion = 'mac' + Math.min(ver[0] - 9, LAST_STABLE_MAC_MAJOR_VERSION);
// BigSur is the first version that might run on Apple Silicon.
if (os.cpus().some(cpu => cpu.model.includes('Apple')))
macVersion += '-arm64';
}
return macVersion as HostPlatform;
}
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 HostPlatform;
})();

View file

@ -18,9 +18,8 @@ import { test, expect, stripAscii } from './playwright-test-fixtures';
import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import { Registry } from '../../src/utils/registry';
import { registry } from '../../src/utils/registry';
const registry = new Registry(path.join(__dirname, '..', '..'));
const ffmpeg = registry.executablePath('ffmpeg') || '';
export class VideoPlayer {

View file

@ -19,9 +19,8 @@ import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import { PNG } from 'pngjs';
import { Registry } from '../src/utils/registry';
import { registry } from '../src/utils/registry';
const registry = new Registry(path.join(__dirname, '..'));
const ffmpeg = registry.executablePath('ffmpeg') || '';
export class VideoPlayer {