chore: move working with browser channels to Registry Executables (#7581)

This commit is contained in:
Dmitry Gozman 2021-07-13 19:03:49 -07:00 committed by GitHub
parent 34777853f7
commit 0742cb9076
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 273 additions and 291 deletions

View file

@ -32,46 +32,9 @@ 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 { registry, Executable } from '../utils/registry'; import { registry, Executable } from '../utils/registry';
import * as utils from '../utils/utils';
const SCRIPTS_DIRECTORY = path.join(__dirname, '..', '..', 'bin');
type BrowserChannel = 'chrome-beta'|'chrome'|'msedge'|'msedge-beta';
const allBrowserChannels: Set<BrowserChannel> = new Set(['chrome-beta', 'chrome', 'msedge', 'msedge-beta']);
const suggestedBrowsersToInstall = ['chromium', 'webkit', 'firefox', ...allBrowserChannels].map(name => `'${name}'`).join(', ');
const packageJSON = require('../../package.json'); const packageJSON = require('../../package.json');
const ChannelName = {
'chrome-beta': 'Google Chrome Beta',
'chrome': 'Google Chrome',
'msedge': 'Microsoft Edge',
'msedge-beta': 'Microsoft Edge Beta',
};
const InstallationScriptName = {
'chrome-beta': {
'linux': 'reinstall_chrome_beta_linux.sh',
'darwin': 'reinstall_chrome_beta_mac.sh',
'win32': 'reinstall_chrome_beta_win.ps1',
},
'chrome': {
'linux': 'reinstall_chrome_stable_linux.sh',
'darwin': 'reinstall_chrome_stable_mac.sh',
'win32': 'reinstall_chrome_stable_win.ps1',
},
'msedge': {
'darwin': 'reinstall_msedge_stable_mac.sh',
'win32': 'reinstall_msedge_stable_win.ps1',
},
'msedge-beta': {
'darwin': 'reinstall_msedge_beta_mac.sh',
'linux': 'reinstall_msedge_beta_linux.sh',
'win32': 'reinstall_msedge_beta_win.ps1',
},
};
program program
.version('Version ' + packageJSON.version) .version('Version ' + packageJSON.version)
.name(process.env.PW_CLI_NAME || 'npx playwright'); .name(process.env.PW_CLI_NAME || 'npx playwright');
@ -119,29 +82,36 @@ program
console.log(' $ debug npm run test'); console.log(' $ debug npm run test');
}); });
function suggestedBrowsersToInstall() {
return registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', ');
}
function checkBrowsersToInstall(args: string[]) {
const faultyArguments: string[] = [];
const executables: Executable[] = [];
for (const arg of args) {
const executable = registry.findExecutable(arg);
if (!executable || executable.installType === 'none')
faultyArguments.push(arg);
else
executables.push(executable);
}
if (faultyArguments.length) {
console.log(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`);
process.exit(1);
}
return executables;
}
program program
.command('install [browserType...]') .command('install [browser...]')
.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: any[]) { .action(async function(args: string[]) {
try { try {
// Install default browsers when invoked without arguments. if (!args.length)
if (!args.length) {
await registry.install(); await registry.install();
return; else
} await registry.install(checkBrowsersToInstall(args));
const binaries = args.map(arg => registry.findExecutable(arg)).filter(b => !!b) as Executable[];
const browserChannels: Set<BrowserChannel> = new Set(args.filter(browser => allBrowserChannels.has(browser)));
const faultyArguments: string[] = args.filter((browser: any) => !binaries.find(b => b.name === browser) && !browserChannels.has(browser));
if (faultyArguments.length) {
console.log(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall}`);
process.exit(1);
}
if (browserChannels.has('chrome-beta') || browserChannels.has('chrome') || browserChannels.has('msedge') || browserChannels.has('msedge-beta'))
binaries.push(registry.findExecutable('ffmpeg')!);
if (binaries.length)
await registry.install(binaries);
for (const browserChannel of browserChannels)
await installBrowserChannel(browserChannel);
} catch (e) { } catch (e) {
console.log(`Failed to install browsers\n${e}`); console.log(`Failed to install browsers\n${e}`);
process.exit(1); process.exit(1);
@ -153,51 +123,31 @@ program
console.log(` Install default browsers.`); console.log(` Install default browsers.`);
console.log(``); console.log(``);
console.log(` - $ install chrome firefox`); console.log(` - $ install chrome firefox`);
console.log(` Install custom browsers, supports ${suggestedBrowsersToInstall}.`); console.log(` Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
}); });
async function installBrowserChannel(channel: BrowserChannel) {
const platform = os.platform();
const scriptName: (string|undefined) = (InstallationScriptName[channel] as any)[platform];
if (!scriptName)
throw new Error(`Cannot install ${ChannelName[channel]} on ${platform}`);
const scriptArgs = [];
if ((channel === 'msedge' || channel === 'msedge-beta') && platform !== 'linux') {
const products = JSON.parse(await utils.fetchData('https://edgeupdates.microsoft.com/api/products'));
const productName = channel === 'msedge' ? 'Stable' : 'Beta';
const product = products.find((product: any) => product.Product === productName);
const searchConfig = ({
darwin: {platform: 'MacOS', arch: 'universal', artifact: 'pkg'},
win32: {platform: 'Windows', arch: os.arch() === 'x64' ? 'x64' : 'x86', artifact: 'msi'},
} as any)[platform];
const release = searchConfig ? product.Releases.find((release: any) => release.Platform === searchConfig.platform && release.Architecture === searchConfig.arch) : null;
const artifact = release ? release.Artifacts.find((artifact: any) => artifact.ArtifactName === searchConfig.artifact) : null;
if (artifact)
scriptArgs.push(artifact.Location /* url */);
else
throw new Error(`Cannot install ${ChannelName[channel]} on ${platform}`);
}
const shell = scriptName.endsWith('.ps1') ? 'powershell.exe' : 'bash';
const {code} = await utils.spawnAsync(shell, [path.join(SCRIPTS_DIRECTORY, scriptName), ...scriptArgs], { cwd: SCRIPTS_DIRECTORY, stdio: 'inherit' });
if (code !== 0)
throw new Error(`Failed to install ${ChannelName[channel]}`);
}
program program
.command('install-deps [browserType...]') .command('install-deps [browser...]')
.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: string[]) { .action(async function(args: string[]) {
try { try {
// TODO: verify the list and print supported browserTypes in the error message. if (!args.length)
const binaries = browserTypes.map(arg => registry.findExecutable(arg)).filter(b => !!b) as Executable[]; await registry.installDeps();
// When passed no arguments, assume default browsers. else
await registry.installDeps(browserTypes.length ? binaries : undefined); await registry.installDeps(checkBrowsersToInstall(args));
} 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);
} }
}).on('--help', function() {
console.log(``);
console.log(`Examples:`);
console.log(` - $ install-deps`);
console.log(` Install dependecies fro default browsers.`);
console.log(``);
console.log(` - $ install-deps chrome firefox`);
console.log(` Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
}); });
const browsers = [ const browsers = [

View file

@ -44,8 +44,8 @@ export abstract class BrowserType extends SdkObject {
this._name = browserName; this._name = browserName;
} }
executablePath(channel?: string): string { executablePath(): string {
return registry.findExecutable(this._name).maybeExecutablePath() || ''; return registry.findExecutable(this._name).executablePath() || '';
} }
name(): string { name(): string {
@ -156,21 +156,19 @@ export abstract class BrowserType extends SdkObject {
else else
browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir)); browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir));
const executable = executablePath || this.executablePath(options.channel); let executable: string;
if (!executable) if (executablePath) {
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); if (!(await existsAsync(executablePath)))
if (!(await existsAsync(executable))) { throw new Error(`Failed to launch ${this._name} because executable doesn't exist at ${executablePath}`);
const errorMessageLines = [`Failed to launch ${this._name} because executable doesn't exist at ${executable}`]; executable = executablePath;
// If we tried using stock downloaded browser, suggest re-installing playwright. } else {
if (!executablePath) const registryExecutable = registry.findExecutable(options.channel || this._name);
errorMessageLines.push(`Run "npx playwright install" to install browsers`); if (!registryExecutable || registryExecutable.browserName !== this._name)
throw new Error(errorMessageLines.join('\n')); throw new Error(`Unsupported ${this._name} channel "${options.channel}"`);
executable = registryExecutable.executablePathOrDie();
await registryExecutable.validateHostRequirements();
} }
// Do not validate dependencies for custom binaries.
if (!executablePath && !options.channel)
await registry.findExecutable(this._name).validateHostRequirements();
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'));
const waitForWSEndpoint = shouldWaitForWSListening ? new Promise<string>(f => wsEndpointCallback = f) : undefined; const waitForWSEndpoint = shouldWaitForWSListening ? new Promise<string>(f => wsEndpointCallback = f) : undefined;

View file

@ -27,13 +27,12 @@ import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../tra
import { CRDevTools } from './crDevTools'; import { CRDevTools } from './crDevTools';
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
import * as types from '../types'; import * as types from '../types';
import { assert, debugMode, headersArrayToObject, removeFolders } from '../../utils/utils'; import { debugMode, headersArrayToObject, removeFolders } from '../../utils/utils';
import { RecentLogsCollector } from '../../utils/debugLogger'; import { RecentLogsCollector } from '../../utils/debugLogger';
import { ProgressController } from '../progress'; import { ProgressController } from '../progress';
import { TimeoutSettings } from '../../utils/timeoutSettings'; import { TimeoutSettings } from '../../utils/timeoutSettings';
import { helper } from '../helper'; import { helper } from '../helper';
import { CallMetadata } from '../instrumentation'; import { CallMetadata } from '../instrumentation';
import { findChromiumChannel } from './findChromiumChannel';
import http from 'http'; import http from 'http';
import { registry } from '../../utils/registry'; import { registry } from '../../utils/registry';
@ -49,20 +48,6 @@ export class Chromium extends BrowserType {
this._devtools = this._createDevTools(); this._devtools = this._createDevTools();
} }
executablePath(channel?: string): string {
if (channel) {
let executablePath = undefined;
if ((channel as any) === 'chromium-with-symbols')
executablePath = registry.findExecutable('chromium-with-symbols')!.executablePathIfExists();
else
executablePath = findChromiumChannel(channel);
assert(executablePath, `unsupported chromium channel "${channel}"`);
assert(fs.existsSync(executablePath), `"${channel}" channel is not installed. Try running 'npx playwright install ${channel}'`);
return executablePath;
}
return super.executablePath(channel);
}
async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string, headers?: types.HeadersArray }, timeout?: number) { async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string, headers?: types.HeadersArray }, timeout?: number) {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
controller.setLogName('browser'); controller.setLogName('browser');
@ -104,7 +89,7 @@ export class Chromium extends BrowserType {
private _createDevTools() { private _createDevTools() {
// TODO: this is totally wrong when using channels. // TODO: this is totally wrong when using channels.
const directory = registry.findExecutable('chromium').directoryIfExists(); const directory = registry.findExecutable('chromium').directory;
return directory ? new CRDevTools(path.join(directory, 'devtools-preferences.json')) : undefined; return directory ? new CRDevTools(path.join(directory, 'devtools-preferences.json')) : undefined;
} }

View file

@ -845,10 +845,9 @@ 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.findExecutable('ffmpeg')!.executablePathIfExists(); const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePath();
if (!ffmpegPath) // TODO: use default error message once it's ready.
throw new Error('ffmpeg executable was not found'); if (!ffmpegPath || !canAccessFile(ffmpegPath)) {
if (!canAccessFile(ffmpegPath)) {
let message: string = ''; let message: string = '';
switch (this._page._browserContext._options.sdkLanguage) { switch (this._page._browserContext._options.sdkLanguage) {
case 'python': message = 'playwright install ffmpeg'; break; case 'python': message = 'playwright install ffmpeg'; break;

View file

@ -1,83 +0,0 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* 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 path from 'path';
import { canAccessFile } from '../../utils/utils';
function darwin(channel: string): string[] | undefined {
switch (channel) {
case 'chrome': return ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'];
case 'chrome-beta': return ['/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta'];
case 'chrome-dev': return ['/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev'];
case 'chrome-canary': return ['/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'];
case 'msedge': return ['/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'];
case 'msedge-beta': return ['/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta'];
case 'msedge-dev': return ['/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev'];
case 'msedge-canary': return ['/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary'];
}
}
function linux(channel: string): string[] | undefined {
switch (channel) {
case 'chrome': return ['/opt/google/chrome/chrome'];
case 'chrome-beta': return ['/opt/google/chrome-beta/chrome'];
case 'chrome-dev': return ['/opt/google/chrome-unstable/chrome'];
case 'msedge-dev': return ['/opt/microsoft/msedge-dev/msedge'];
case 'msedge-beta': return ['/opt/microsoft/msedge-beta/msedge'];
}
}
function win32(channel: string): string[] | undefined {
let suffix: string | undefined;
switch (channel) {
case 'chrome': suffix = `\\Google\\Chrome\\Application\\chrome.exe`; break;
case 'chrome-beta': suffix = `\\Google\\Chrome Beta\\Application\\chrome.exe`; break;
case 'chrome-dev': suffix = `\\Google\\Chrome Dev\\Application\\chrome.exe`; break;
case 'chrome-canary': suffix = `\\Google\\Chrome SxS\\Application\\chrome.exe`; break;
case 'msedge': suffix = `\\Microsoft\\Edge\\Application\\msedge.exe`; break;
case 'msedge-beta': suffix = `\\Microsoft\\Edge Beta\\Application\\msedge.exe`; break;
case 'msedge-dev': suffix = `\\Microsoft\\Edge Dev\\Application\\msedge.exe`; break;
case 'msedge-canary': suffix = `\\Microsoft\\Edge SxS\\Application\\msedge.exe`; break;
}
if (!suffix)
return;
const prefixes = [
process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']
].filter(Boolean) as string[];
return prefixes.map(prefix => path.join(prefix, suffix!));
}
export function findChromiumChannel(channel: string): string {
let installationPaths: string[] | undefined;
if (process.platform === 'linux')
installationPaths = linux(channel);
else if (process.platform === 'win32')
installationPaths = win32(channel);
else if (process.platform === 'darwin')
installationPaths = darwin(channel);
if (!installationPaths)
throw new Error(`Chromium distribution '${channel}' is not supported on ${process.platform}`);
let result: string | undefined;
installationPaths.forEach(chromePath => {
if (canAccessFile(chromePath))
result = chromePath;
});
if (result)
return result;
throw new Error(`Chromium distribution is not installed on the system: ${channel}`);
}

View file

@ -18,7 +18,6 @@
import * as os from 'os'; import * as os from 'os';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { assert } from '../../utils/utils';
import { FFBrowser } from './ffBrowser'; import { FFBrowser } from './ffBrowser';
import { kBrowserCloseMessageId } from './ffConnection'; import { kBrowserCloseMessageId } from './ffConnection';
import { BrowserType } from '../browserType'; import { BrowserType } from '../browserType';
@ -26,25 +25,12 @@ import { Env } from '../../utils/processLauncher';
import { ConnectionTransport } from '../transport'; import { ConnectionTransport } from '../transport';
import { BrowserOptions, PlaywrightOptions } from '../browser'; import { BrowserOptions, PlaywrightOptions } from '../browser';
import * as types from '../types'; import * as types from '../types';
import { registry } from '../../utils/registry';
export class Firefox extends BrowserType { export class Firefox extends BrowserType {
constructor(playwrightOptions: PlaywrightOptions) { constructor(playwrightOptions: PlaywrightOptions) {
super('firefox', playwrightOptions); super('firefox', playwrightOptions);
} }
executablePath(channel?: string): string {
if (channel) {
let executablePath = undefined;
if ((channel as any) === 'firefox-beta')
executablePath = registry.findExecutable('firefox-beta')!.executablePathIfExists();
assert(executablePath, `unsupported firefox channel "${channel}"`);
assert(fs.existsSync(executablePath), `"${channel}" channel is not installed. Try running 'npx playwright install ${channel}'`);
return executablePath;
}
return super.executablePath(channel);
}
_connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<FFBrowser> { _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<FFBrowser> {
return FFBrowser.connect(transport, options); return FFBrowser.connect(transport, options);
} }

View file

@ -22,7 +22,7 @@ import { EventEmitter } from 'events';
import { internalCallMetadata } from '../../instrumentation'; import { internalCallMetadata } from '../../instrumentation';
import type { CallLog, EventData, Mode, Source } from './recorderTypes'; import type { CallLog, EventData, Mode, Source } from './recorderTypes';
import { BrowserContext } from '../../browserContext'; import { BrowserContext } from '../../browserContext';
import { existsAsync, isUnderTest } from '../../../utils/utils'; import { isUnderTest } from '../../../utils/utils';
import { installAppIcon } from '../../chromium/crApp'; import { installAppIcon } from '../../chromium/crApp';
declare global { declare global {
@ -97,8 +97,7 @@ export class RecorderApp extends EventEmitter {
let executablePath: string | undefined; let executablePath: string | undefined;
if (inspectedContext._browser.options.isChromium) { if (inspectedContext._browser.options.isChromium) {
channel = inspectedContext._browser.options.channel; channel = inspectedContext._browser.options.channel;
const defaultExecutablePath = recorderPlaywright.chromium.executablePath(channel); if (!channel)
if (!(await existsAsync(defaultExecutablePath)))
executablePath = inspectedContext._browser.options.customExecutablePath; executablePath = inspectedContext._browser.options.customExecutablePath;
} }
const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', { const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', {

View file

@ -30,7 +30,6 @@ import { internalCallMetadata } from '../../instrumentation';
import { ProgressController } from '../../progress'; import { ProgressController } from '../../progress';
import { BrowserContext } from '../../browserContext'; import { BrowserContext } from '../../browserContext';
import { registry } from '../../../utils/registry'; import { registry } from '../../../utils/registry';
import { findChromiumChannel } from '../../chromium/findChromiumChannel';
import { installAppIcon } from '../../chromium/crApp'; import { installAppIcon } from '../../chromium/crApp';
export class TraceViewer { export class TraceViewer {
@ -140,21 +139,17 @@ 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 (registry.findExecutable('chromium').executablePathIfExists()) { for (const name of ['chromium', 'chrome', 'msedge']) {
// This means we have a browser downloaded. try {
channel = undefined; registry.findExecutable(name)!.executablePathOrDie();
} else { channel = name === 'chromium' ? undefined : name;
for (const c of ['chrome', 'msedge']) { break;
try { } catch (e) {
findChromiumChannel(c);
channel = c;
break;
} catch (e) {
}
} }
} }
if (channel === null) { if (channel === null) {
// TODO: language-specific error message, or fallback to default error.
throw new Error(` throw new Error(`
================================================================== ==================================================================
Please run 'npx playwright install' to install Playwright browsers Please run 'npx playwright install' to install Playwright browsers

View file

@ -35,7 +35,9 @@ function isSupportedWindowsVersion(): boolean {
return major > 6 || (major === 6 && minor > 1); return major > 6 || (major === 6 && minor > 1);
} }
export async function installDependenciesWindows(targets: Set<'chromium' | 'firefox' | 'webkit' | 'tools'>) { export type DependencyGroup = 'chromium' | 'firefox' | 'webkit' | 'tools';
export async function installDependenciesWindows(targets: Set<DependencyGroup>) {
if (targets.has('chromium')) { if (targets.has('chromium')) {
const {code} = await utils.spawnAsync('powershell.exe', [path.join(BIN_DIRECTORY, 'install_media_pack.ps1')], { cwd: BIN_DIRECTORY, stdio: 'inherit' }); const {code} = await utils.spawnAsync('powershell.exe', [path.join(BIN_DIRECTORY, 'install_media_pack.ps1')], { cwd: BIN_DIRECTORY, stdio: 'inherit' });
if (code !== 0) if (code !== 0)
@ -43,7 +45,7 @@ export async function installDependenciesWindows(targets: Set<'chromium' | 'fire
} }
} }
export async function installDependenciesLinux(targets: Set<'chromium' | 'firefox' | 'webkit' | 'tools'>) { export async function installDependenciesLinux(targets: Set<DependencyGroup>) {
const ubuntuVersion = await getUbuntuVersion(); const ubuntuVersion = await getUbuntuVersion();
if (ubuntuVersion !== '18.04' && ubuntuVersion !== '20.04' && ubuntuVersion !== '21.04') { if (ubuntuVersion !== '18.04' && ubuntuVersion !== '20.04' && ubuntuVersion !== '21.04') {
console.warn('Cannot install dependencies for this linux distribution!'); // eslint-disable-line no-console console.warn('Cannot install dependencies for this linux distribution!'); // eslint-disable-line no-console

View file

@ -21,11 +21,12 @@ 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 { getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile } from './utils'; import { getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile, spawnAsync, fetchData } from './utils';
import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; import { DependencyGroup, installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies';
import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher'; import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher';
const PACKAGE_PATH = path.join(__dirname, '..', '..'); const PACKAGE_PATH = path.join(__dirname, '..', '..');
const BIN_PATH = path.join(__dirname, '..', '..', 'bin');
const EXECUTABLE_PATHS = { const EXECUTABLE_PATHS = {
'chromium': { 'chromium': {
@ -215,21 +216,23 @@ function readDescriptors(packagePath: string) {
export type BrowserName = 'chromium' | 'firefox' | 'webkit'; export type BrowserName = 'chromium' | 'firefox' | 'webkit';
type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-with-symbols'; type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-with-symbols';
type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary';
const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-with-symbols']; const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-with-symbols'];
export interface Executable { export interface Executable {
type: 'browser' | 'tool'; type: 'browser' | 'tool' | 'channel';
name: BrowserName | InternalTool; name: BrowserName | InternalTool | ChromiumChannel;
browserName: BrowserName | undefined; browserName: BrowserName | undefined;
installType: 'download-by-default' | 'download-on-demand'; installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none';
maybeExecutablePath(): string | undefined; directory: string | undefined;
executablePathIfExists(): string | undefined; executablePathOrDie(): string;
directoryIfExists(): string | undefined; executablePath(): string | undefined;
validateHostRequirements(): Promise<void>; validateHostRequirements(): Promise<void>;
} }
interface ExecutableImpl extends Executable { interface ExecutableImpl extends Executable {
_download?: () => Promise<void>; _install?: () => Promise<void>;
_dependencyGroup?: DependencyGroup;
} }
export class Registry { export class Registry {
@ -237,72 +240,146 @@ export class Registry {
constructor(packagePath: string) { constructor(packagePath: string) {
const descriptors = readDescriptors(packagePath); const descriptors = readDescriptors(packagePath);
const executablePath = (dir: string, name: keyof typeof EXECUTABLE_PATHS) => { const findExecutablePath = (dir: string, name: keyof typeof EXECUTABLE_PATHS) => {
const tokens = EXECUTABLE_PATHS[name][hostPlatform]; const tokens = EXECUTABLE_PATHS[name][hostPlatform];
return tokens ? path.join(dir, ...tokens) : undefined; return tokens ? path.join(dir, ...tokens) : undefined;
}; };
const directoryIfExists = (d: string) => fs.existsSync(d) ? d : undefined; const executablePathOrDie = (name: string, e: string | undefined) => {
const executablePathIfExists = (e: string | undefined) => e && canAccessFile(e) ? e : undefined; if (!e)
throw new Error(`${name} is not supported on ${hostPlatform}`);
// TODO: language-specific error message
if (!canAccessFile(e))
throw new Error(`Executable doesn't exist at ${e}\nRun "npx playwright install ${name}"`);
return e;
};
this._executables = []; this._executables = [];
const chromium = descriptors.find(d => d.name === 'chromium')!; const chromium = descriptors.find(d => d.name === 'chromium')!;
const chromiumExecutable = executablePath(chromium.dir, 'chromium'); const chromiumExecutable = findExecutablePath(chromium.dir, 'chromium');
this._executables.push({ this._executables.push({
type: 'browser', type: 'browser',
name: 'chromium', name: 'chromium',
browserName: 'chromium', browserName: 'chromium',
directoryIfExists: () => directoryIfExists(chromium.dir), directory: chromium.dir,
maybeExecutablePath: () => chromiumExecutable, executablePath: () => chromiumExecutable,
executablePathIfExists: () => executablePathIfExists(chromiumExecutable), executablePathOrDie: () => executablePathOrDie('chromium', chromiumExecutable),
installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand', installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => this._validateHostRequirements('chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']), validateHostRequirements: () => this._validateHostRequirements('chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
_download: () => this._downloadExecutable(chromium, chromiumExecutable, DOWNLOAD_URLS['chromium'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'), _install: () => this._downloadExecutable(chromium, chromiumExecutable, DOWNLOAD_URLS['chromium'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
_dependencyGroup: 'chromium',
}); });
const chromiumWithSymbols = descriptors.find(d => d.name === 'chromium-with-symbols')!; const chromiumWithSymbols = descriptors.find(d => d.name === 'chromium-with-symbols')!;
const chromiumWithSymbolsExecutable = executablePath(chromiumWithSymbols.dir, 'chromium'); const chromiumWithSymbolsExecutable = findExecutablePath(chromiumWithSymbols.dir, 'chromium');
this._executables.push({ this._executables.push({
type: 'tool', type: 'tool',
name: 'chromium-with-symbols', name: 'chromium-with-symbols',
browserName: 'chromium', browserName: 'chromium',
directoryIfExists: () => directoryIfExists(chromiumWithSymbols.dir), directory: chromiumWithSymbols.dir,
maybeExecutablePath: () => chromiumWithSymbolsExecutable, executablePath: () => chromiumWithSymbolsExecutable,
executablePathIfExists: () => executablePathIfExists(chromiumWithSymbolsExecutable), executablePathOrDie: () => executablePathOrDie('chromium-with-symbols', chromiumWithSymbolsExecutable),
installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand', installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => this._validateHostRequirements('chromium', chromiumWithSymbols.dir, ['chrome-linux'], [], ['chrome-win']), 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'), _install: () => this._downloadExecutable(chromiumWithSymbols, chromiumWithSymbolsExecutable, DOWNLOAD_URLS['chromium-with-symbols'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
_dependencyGroup: 'chromium',
}); });
this._executables.push(this._createChromiumChannel('chrome', {
'linux': '/opt/google/chrome/chrome',
'darwin': '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'win32': `\\Google\\Chrome\\Application\\chrome.exe`,
}, () => this._installChromiumChannel('chrome', {
'linux': 'reinstall_chrome_stable_linux.sh',
'darwin': 'reinstall_chrome_stable_mac.sh',
'win32': 'reinstall_chrome_stable_win.ps1',
})));
this._executables.push(this._createChromiumChannel('chrome-beta', {
'linux': '/opt/google/chrome-beta/chrome',
'darwin': '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
'win32': `\\Google\\Chrome Beta\\Application\\chrome.exe`,
}, () => this._installChromiumChannel('chrome-beta', {
'linux': 'reinstall_chrome_beta_linux.sh',
'darwin': 'reinstall_chrome_beta_mac.sh',
'win32': 'reinstall_chrome_beta_win.ps1',
})));
this._executables.push(this._createChromiumChannel('chrome-dev', {
'linux': '/opt/google/chrome-unstable/chrome',
'darwin': '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
'win32': `\\Google\\Chrome Dev\\Application\\chrome.exe`,
}));
this._executables.push(this._createChromiumChannel('chrome-canary', {
'linux': '',
'darwin': '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
'win32': `\\Google\\Chrome SxS\\Application\\chrome.exe`,
}));
this._executables.push(this._createChromiumChannel('msedge', {
'linux': '',
'darwin': '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
'win32': `\\Microsoft\\Edge\\Application\\msedge.exe`,
}, () => this._installMSEdgeChannel('msedge', {
'linux': '',
'darwin': 'reinstall_msedge_stable_mac.sh',
'win32': 'reinstall_msedge_stable_win.ps1',
})));
this._executables.push(this._createChromiumChannel('msedge-beta', {
'linux': '/opt/microsoft/msedge-beta/msedge',
'darwin': '/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta',
'win32': `\\Microsoft\\Edge Beta\\Application\\msedge.exe`,
}, () => this._installMSEdgeChannel('msedge-beta', {
'darwin': 'reinstall_msedge_beta_mac.sh',
'linux': 'reinstall_msedge_beta_linux.sh',
'win32': 'reinstall_msedge_beta_win.ps1',
})));
this._executables.push(this._createChromiumChannel('msedge-dev', {
'linux': '/opt/microsoft/msedge-dev/msedge',
'darwin': '/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev',
'win32': `\\Microsoft\\Edge Dev\\Application\\msedge.exe`,
}));
this._executables.push(this._createChromiumChannel('msedge-canary', {
'linux': '',
'darwin': '/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary',
'win32': `\\Microsoft\\Edge SxS\\Application\\msedge.exe`,
}));
const firefox = descriptors.find(d => d.name === 'firefox')!; const firefox = descriptors.find(d => d.name === 'firefox')!;
const firefoxExecutable = executablePath(firefox.dir, 'firefox'); const firefoxExecutable = findExecutablePath(firefox.dir, 'firefox');
this._executables.push({ this._executables.push({
type: 'browser', type: 'browser',
name: 'firefox', name: 'firefox',
browserName: 'firefox', browserName: 'firefox',
directoryIfExists: () => directoryIfExists(firefox.dir), directory: firefox.dir,
maybeExecutablePath: () => firefoxExecutable, executablePath: () => firefoxExecutable,
executablePathIfExists: () => executablePathIfExists(firefoxExecutable), executablePathOrDie: () => executablePathOrDie('firefox', firefoxExecutable),
installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand', installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => this._validateHostRequirements('firefox', firefox.dir, ['firefox'], [], ['firefox']), validateHostRequirements: () => this._validateHostRequirements('firefox', firefox.dir, ['firefox'], [], ['firefox']),
_download: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_URLS['firefox'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'), _install: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_URLS['firefox'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
_dependencyGroup: 'firefox',
}); });
const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta')!; const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta')!;
const firefoxBetaExecutable = executablePath(firefoxBeta.dir, 'firefox'); const firefoxBetaExecutable = findExecutablePath(firefoxBeta.dir, 'firefox');
this._executables.push({ this._executables.push({
type: 'tool', type: 'tool',
name: 'firefox-beta', name: 'firefox-beta',
browserName: 'firefox', browserName: 'firefox',
directoryIfExists: () => directoryIfExists(firefoxBeta.dir), directory: firefoxBeta.dir,
maybeExecutablePath: () => firefoxBetaExecutable, executablePath: () => firefoxBetaExecutable,
executablePathIfExists: () => executablePathIfExists(firefoxBetaExecutable), executablePathOrDie: () => executablePathOrDie('firefox-beta', firefoxBetaExecutable),
installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand', installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => this._validateHostRequirements('firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']), validateHostRequirements: () => this._validateHostRequirements('firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']),
_download: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable, DOWNLOAD_URLS['firefox-beta'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'), _install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable, DOWNLOAD_URLS['firefox-beta'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
_dependencyGroup: 'firefox',
}); });
const webkit = descriptors.find(d => d.name === 'webkit')!; const webkit = descriptors.find(d => d.name === 'webkit')!;
const webkitExecutable = executablePath(webkit.dir, 'webkit'); const webkitExecutable = findExecutablePath(webkit.dir, 'webkit');
const webkitLinuxLddDirectories = [ const webkitLinuxLddDirectories = [
path.join('minibrowser-gtk'), path.join('minibrowser-gtk'),
path.join('minibrowser-gtk', 'bin'), path.join('minibrowser-gtk', 'bin'),
@ -315,29 +392,73 @@ export class Registry {
type: 'browser', type: 'browser',
name: 'webkit', name: 'webkit',
browserName: 'webkit', browserName: 'webkit',
directoryIfExists: () => directoryIfExists(webkit.dir), directory: webkit.dir,
maybeExecutablePath: () => webkitExecutable, executablePath: () => webkitExecutable,
executablePathIfExists: () => executablePathIfExists(webkitExecutable), executablePathOrDie: () => executablePathOrDie('webkit', webkitExecutable),
installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand', installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => this._validateHostRequirements('webkit', webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']), 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'), _install: () => this._downloadExecutable(webkit, webkitExecutable, DOWNLOAD_URLS['webkit'][hostPlatform], 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST'),
_dependencyGroup: 'webkit',
}); });
const ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!; const ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!;
const ffmpegExecutable = executablePath(ffmpeg.dir, 'ffmpeg'); const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg');
this._executables.push({ this._executables.push({
type: 'tool', type: 'tool',
name: 'ffmpeg', name: 'ffmpeg',
browserName: undefined, browserName: undefined,
directoryIfExists: () => directoryIfExists(ffmpeg.dir), directory: ffmpeg.dir,
maybeExecutablePath: () => ffmpegExecutable, executablePath: () => ffmpegExecutable,
executablePathIfExists: () => executablePathIfExists(ffmpegExecutable), executablePathOrDie: () => executablePathOrDie('ffmpeg', ffmpegExecutable),
installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand', installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => Promise.resolve(), validateHostRequirements: () => Promise.resolve(),
_download: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_URLS['ffmpeg'][hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'), _install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_URLS['ffmpeg'][hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'),
_dependencyGroup: 'tools',
}); });
} }
private _createChromiumChannel(name: ChromiumChannel, lookAt: Record<'linux' | 'darwin' | 'win32', string>, install?: () => Promise<void>): ExecutableImpl {
const executablePath = (shouldThrow: boolean) => {
const suffix = lookAt[process.platform as 'linux' | 'darwin' | 'win32'];
if (!suffix) {
if (shouldThrow)
throw new Error(`Chromium distribution '${name}' is not supported on ${process.platform}`);
return undefined;
}
const prefixes = (process.platform === 'win32' ? [
process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']
].filter(Boolean) : ['']) as string[];
for (const prefix of prefixes) {
const executablePath = path.join(prefix, suffix);
if (canAccessFile(executablePath))
return executablePath;
}
if (!shouldThrow)
return undefined;
const location = prefixes.length ? ` at ${path.join(prefixes[0], suffix)}` : ``;
// TODO: language-specific error message
const installation = install ? `\nRun "npx playwright install ${name}"` : '';
throw new Error(`Chromium distribution '${name}' is not found${location}${installation}`);
};
return {
type: 'channel',
name,
browserName: 'chromium',
directory: undefined,
executablePath: () => executablePath(false),
executablePathOrDie: () => executablePath(true)!,
installType: install ? 'install-script' : 'none',
validateHostRequirements: () => Promise.resolve(),
_install: install,
};
}
executables(): Executable[] {
return this._executables;
}
findExecutable(name: BrowserName): Executable; findExecutable(name: BrowserName): Executable;
findExecutable(name: string): Executable | undefined; findExecutable(name: string): Executable | undefined;
findExecutable(name: string): Executable | undefined { findExecutable(name: string): Executable | undefined {
@ -373,10 +494,10 @@ export class Registry {
async installDeps(executablesToInstallDeps?: Executable[]) { async installDeps(executablesToInstallDeps?: Executable[]) {
const executables = this._addRequirementsAndDedupe(executablesToInstallDeps); const executables = this._addRequirementsAndDedupe(executablesToInstallDeps);
const targets = new Set<'chromium' | 'firefox' | 'webkit' | 'tools'>(); const targets = new Set<DependencyGroup>();
for (const executable of executables) { for (const executable of executables) {
if (executable.browserName) if (executable._dependencyGroup)
targets.add(executable.browserName); targets.add(executable._dependencyGroup);
} }
targets.add('tools'); targets.add('tools');
if (os.platform() === 'win32') if (os.platform() === 'win32')
@ -414,8 +535,8 @@ export class Registry {
// Install browsers for this package. // Install browsers for this package.
for (const executable of executables) { for (const executable of executables) {
if (executable._download) if (executable._install)
await executable._download(); await executable._install();
else else
throw new Error(`ERROR: Playwright does not support installing ${executable.name}`); throw new Error(`ERROR: Playwright does not support installing ${executable.name}`);
} }
@ -440,6 +561,36 @@ export class Registry {
await fs.promises.writeFile(markerFilePath(descriptor.dir), ''); await fs.promises.writeFile(markerFilePath(descriptor.dir), '');
} }
private async _installMSEdgeChannel(channel: string, scripts: Record<'linux' | 'darwin' | 'win32', string>) {
const scriptArgs: string[] = [];
if (process.platform !== 'linux') {
const products = JSON.parse(await fetchData('https://edgeupdates.microsoft.com/api/products'));
const productName = channel === 'msedge' ? 'Stable' : 'Beta';
const product = products.find((product: any) => product.Product === productName);
const searchConfig = ({
darwin: {platform: 'MacOS', arch: 'universal', artifact: 'pkg'},
win32: {platform: 'Windows', arch: os.arch() === 'x64' ? 'x64' : 'x86', artifact: 'msi'},
} as any)[process.platform];
const release = searchConfig ? product.Releases.find((release: any) => release.Platform === searchConfig.platform && release.Architecture === searchConfig.arch) : null;
const artifact = release ? release.Artifacts.find((artifact: any) => artifact.ArtifactName === searchConfig.artifact) : null;
if (artifact)
scriptArgs.push(artifact.Location /* url */);
else
throw new Error(`Cannot install ${channel} on ${process.platform}`);
}
await this._installChromiumChannel(channel, scripts, scriptArgs);
}
private async _installChromiumChannel(channel: string, scripts: Record<'linux' | 'darwin' | 'win32', string>, scriptArgs: string[] = []) {
const scriptName = scripts[process.platform as 'linux' | 'darwin' | 'win32'];
if (!scriptName)
throw new Error(`Cannot install ${channel} on ${process.platform}`);
const shell = scriptName.endsWith('.ps1') ? 'powershell.exe' : 'bash';
const { code } = await spawnAsync(shell, [path.join(BIN_PATH, scriptName), ...scriptArgs], { cwd: BIN_PATH, stdio: 'inherit' });
if (code !== 0)
throw new Error(`Failed to install ${channel}`);
}
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();

View file

@ -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.findExecutable('ffmpeg')!.executablePathIfExists() || ''; const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath();
export class VideoPlayer { export class VideoPlayer {
videoWidth: number; videoWidth: number;

View file

@ -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.findExecutable('ffmpeg')!.executablePathIfExists() || ''; const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath();
export class VideoPlayer { export class VideoPlayer {
fileName: string; fileName: string;

View file

@ -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).findBinary(binaryName).executablePathIfExists(); const executablePath = new Registry(ROOT_PATH).findBinary(binaryName).executablePathOrDie();
await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn); await protocolGenerator.generateProtocol(browserName, executablePath).catch(console.warn);
// 5. Update docs. // 5. Update docs.