diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 9056896f862a9..379498f74821f 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -34,7 +34,7 @@ import type { BrowserContext } from '../client/browserContext'; import type { BrowserType } from '../client/browserType'; import type { Page } from '../client/page'; import type { BrowserContextOptions, LaunchOptions } from '../client/types'; -import type { Executable, BrowserInfo } from '../server'; +import type { BrowserInfo } from '../server'; import type { TraceViewerServerOptions } from '../server/trace/viewer/traceViewer'; import type { Command } from '../utilsBundle'; @@ -79,54 +79,6 @@ Examples: $ codegen --target=python $ codegen -b webkit https://example.com`); -function suggestedBrowsersToInstall() { - return registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', '); -} - -function defaultBrowsersToInstall(options: { noShell?: boolean, onlyShell?: boolean }): Executable[] { - let executables = registry.defaultExecutables(); - if (options.noShell) - executables = executables.filter(e => e.name !== 'chromium-headless-shell'); - if (options.onlyShell) - executables = executables.filter(e => e.name !== 'chromium'); - return executables; -} - -function checkBrowsersToInstall(args: string[], options: { noShell?: boolean, onlyShell?: boolean }): Executable[] { - if (options.noShell && options.onlyShell) - throw new Error(`Only one of --no-shell and --only-shell can be specified`); - - const faultyArguments: string[] = []; - const executables: Executable[] = []; - const handleArgument = (arg: string) => { - const executable = registry.findExecutable(arg); - if (!executable || executable.installType === 'none') - faultyArguments.push(arg); - else - executables.push(executable); - if (executable?.browserName === 'chromium') - executables.push(registry.findExecutable('ffmpeg')!); - }; - - for (const arg of args) { - if (arg === 'chromium') { - if (!options.onlyShell) - handleArgument('chromium'); - if (!options.noShell) - handleArgument('chromium-headless-shell'); - } else { - handleArgument(arg); - } - } - - if (process.platform === 'win32') - executables.push(registry.findExecutable('winldd')!); - - if (faultyArguments.length) - throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`); - return executables; -} - function printInstalledBrowsers(browsers: BrowserInfo[]) { const browserPaths = new Set(); for (const browser of browsers) @@ -194,9 +146,6 @@ program .option('--only-shell', 'only install headless shell when installing chromium') .option('--no-shell', 'do not install chromium headless shell') .action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean, list?: boolean, shell?: boolean, noShell?: boolean, onlyShell?: boolean }) { - // For '--no-shell' option, commander sets `shell: false` instead. - if (options.shell === false) - options.noShell = true; if (isLikelyNpxGlobal()) { console.error(wrapInASCIIBox([ `WARNING: It looks like you are running 'npx playwright install' without first`, @@ -218,8 +167,10 @@ program ].join('\n'), 1)); } try { - const hasNoArguments = !args.length; - const executables = hasNoArguments ? defaultBrowsersToInstall(options) : checkBrowsersToInstall(args, options); + if (options.shell === false && options.onlyShell) + throw new Error(`Only one of --no-shell and --only-shell can be specified`); + const shell = options.shell === false ? 'no' : options.onlyShell ? 'only' : undefined; + const executables = registry.resolveBrowsers(args, { shell }); if (options.withDeps) await registry.installDeps(executables, !!options.dryRun); if (options.dryRun && options.list) @@ -241,8 +192,8 @@ program const browsers = await registry.listInstalledBrowsers(); printGroupedByPlaywrightVersion(browsers); } else { - const forceReinstall = hasNoArguments ? false : !!options.force; - await registry.install(executables, forceReinstall); + const force = args.length === 0 ? false : !!options.force; + await registry.install(executables, { force }); await registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || 'javascript').catch((e: Error) => { e.name = 'Playwright Host validation warning'; console.error(e); @@ -259,7 +210,7 @@ Examples: Install default browsers. - $ install chrome firefox - Install custom browsers, supports ${suggestedBrowsersToInstall()}.`); + Install custom browsers, supports ${registry.suggestedBrowsersToInstall()}.`); program .command('uninstall') @@ -281,10 +232,7 @@ program .option('--dry-run', 'Do not execute installation commands, only print them') .action(async function(args: string[], options: { dryRun?: boolean }) { try { - if (!args.length) - await registry.installDeps(defaultBrowsersToInstall({}), !!options.dryRun); - else - await registry.installDeps(checkBrowsersToInstall(args, {}), !!options.dryRun); + await registry.installDeps(registry.resolveBrowsers(args, {}), !!options.dryRun); } catch (e) { console.log(`Failed to install browser dependencies\n${e}`); gracefullyProcessExitDoNotHang(1); @@ -295,7 +243,7 @@ Examples: Install dependencies for default browsers. - $ install-deps chrome firefox - Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`); + Install dependencies for specific browsers, supports ${registry.suggestedBrowsersToInstall()}.`); const browsers = [ { alias: 'cr', name: 'Chromium', type: 'chromium' }, diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index a591b93514045..a58844ce1d05a 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -1080,7 +1080,7 @@ export class Registry { return await installDependenciesLinux(targets, dryRun); } - async install(executablesToInstall: Executable[], forceReinstall: boolean) { + async install(executablesToInstall: Executable[], options?: { force?: boolean }) { const executables = this._dedupe(executablesToInstall); await fs.promises.mkdir(registryDirectory, { recursive: true }); const lockfilePath = path.join(registryDirectory, '__dirlock'); @@ -1115,7 +1115,7 @@ export class Registry { throw new Error(`ERROR: Playwright does not support installing ${executable.name}`); const { embedderName } = getEmbedderName(); - if (!getAsBooleanFromENV('CI') && !executable._isHermeticInstallation && !forceReinstall && executable.executablePath(embedderName)) { + if (!getAsBooleanFromENV('CI') && !executable._isHermeticInstallation && !options?.force && executable.executablePath(embedderName)) { const command = buildPlaywrightCLICommand(embedderName, 'install --force ' + executable.name); // eslint-disable-next-line no-restricted-properties process.stderr.write('\n' + wrapInASCIIBox([ @@ -1394,6 +1394,54 @@ export class Registry { for (const linkPath of brokenLinks) await fs.promises.unlink(linkPath).catch(e => {}); } + + private _defaultBrowsersToInstall(options: { shell?: 'no' | 'only' }): Executable[] { + let executables = this.defaultExecutables(); + if (options.shell === 'no') + executables = executables.filter(e => e.name !== 'chromium-headless-shell'); + if (options.shell === 'only') + executables = executables.filter(e => e.name !== 'chromium'); + return executables; + } + + suggestedBrowsersToInstall(): string { + return this.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', '); + } + + resolveBrowsers(aliases: string[], options: { shell?: 'no' | 'only' }): Executable[] { + if (aliases.length === 0) + return this._defaultBrowsersToInstall(options); + + const faultyArguments: string[] = []; + const executables: Executable[] = []; + const handleArgument = (arg: string) => { + const executable = this.findExecutable(arg); + if (!executable || executable.installType === 'none') + faultyArguments.push(arg); + else + executables.push(executable); + if (executable?.browserName === 'chromium') + executables.push(this.findExecutable('ffmpeg')!); + }; + + for (const alias of aliases) { + if (alias === 'chromium') { + if (options.shell !== 'only') + handleArgument('chromium'); + if (options.shell !== 'no') + handleArgument('chromium-headless-shell'); + } else { + handleArgument(alias); + } + } + + if (process.platform === 'win32') + executables.push(this.findExecutable('winldd')!); + + if (faultyArguments.length) + throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${this.suggestedBrowsersToInstall()}`); + return executables; + } } export function browserDirectoryToMarkerFilePath(browserDirectory: string): string { @@ -1431,7 +1479,7 @@ export async function installBrowsersForNpmInstall(browsers: string[]) { executables.push(executable); } - await registry.install(executables, false /* forceReinstall */); + await registry.install(executables); } // for launchApp -> UI Mode / Trace Viewer diff --git a/packages/playwright/src/runner/testRunner.ts b/packages/playwright/src/runner/testRunner.ts index 59b4ce7400de1..6c5fdf2c917d4 100644 --- a/packages/playwright/src/runner/testRunner.ts +++ b/packages/playwright/src/runner/testRunner.ts @@ -139,7 +139,7 @@ export class TestRunner extends EventEmitter { async installBrowsers() { const executables = registry.defaultExecutables(); - await registry.install(executables, false); + await registry.install(executables); } async loadConfig() {