Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 10 additions & 62 deletions packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<string>();
for (const browser of browsers)
Expand Down Expand Up @@ -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`,
Expand All @@ -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)
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we check args.length===0 here at all? I'd just pass options.force.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what it was...

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);
Expand All @@ -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')
Expand All @@ -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);
Expand All @@ -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' },
Expand Down
54 changes: 51 additions & 3 deletions packages/playwright-core/src/server/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/runner/testRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class TestRunner extends EventEmitter<TestRunnerEventMap> {

async installBrowsers() {
const executables = registry.defaultExecutables();
await registry.install(executables, false);
await registry.install(executables);
}

async loadConfig() {
Expand Down
Loading