feat: implement --dry-run for npx playwright install (#17158)

* feat: implement `--dry-run` for `npx playwright install`

The `--dry-run` command prints URLs for browsers to be installed.

Demo output:

```
browser: chromium version 106.0.5249.21
  Install location:    /Users/andreylushnikov/Library/Caches/ms-playwright/chromium-1023
  Download url:        https://playwright.azureedge.net/builds/chromium/1023/chromium-mac-arm64.zip
  Download fallback 1: https://playwright-akamai.azureedge.net/builds/chromium/1023/chromium-mac-arm64.zip
  Download fallback 2: https://playwright-verizon.azureedge.net/builds/chromium/1023/chromium-mac-arm64.zip

browser: firefox version 104.0
  Install location:    /Users/andreylushnikov/Library/Caches/ms-playwright/firefox-1350
  Download url:        https://playwright.azureedge.net/builds/firefox/1350/firefox-mac-11-arm64.zip
  Download fallback 1: https://playwright-akamai.azureedge.net/builds/firefox/1350/firefox-mac-11-arm64.zip
  Download fallback 2: https://playwright-verizon.azureedge.net/builds/firefox/1350/firefox-mac-11-arm64.zip

browser: webkit version 16.0
  Install location:    /Users/andreylushnikov/Library/Caches/ms-playwright/webkit-1714
  Download url:        https://playwright.azureedge.net/builds/webkit/1714/webkit-mac-12-arm64.zip
  Download fallback 1: https://playwright-akamai.azureedge.net/builds/webkit/1714/webkit-mac-12-arm64.zip
  Download fallback 2: https://playwright-verizon.azureedge.net/builds/webkit/1714/webkit-mac-12-arm64.zip

browser: ffmpeg
  Install location:    /Users/andreylushnikov/Library/Caches/ms-playwright/ffmpeg-1007
  Download url:        https://playwright.azureedge.net/builds/ffmpeg/1007/ffmpeg-mac-arm64.zip
  Download fallback 1: https://playwright-akamai.azureedge.net/builds/ffmpeg/1007/ffmpeg-mac-arm64.zip
  Download fallback 2: https://playwright-verizon.azureedge.net/builds/ffmpeg/1007/ffmpeg-mac-arm64.zip
```

Fixes #16926
This commit is contained in:
Andrey Lushnikov 2022-09-08 09:05:09 -07:00 committed by GitHub
parent 3708ba7a1f
commit b0ff4f58ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 34 deletions

View File

@ -32,9 +32,7 @@ import type { Page } from '../client/page';
import type { BrowserType } from '../client/browserType';
import type { BrowserContextOptions, LaunchOptions } from '../client/types';
import { spawn } from 'child_process';
import { getPlaywrightVersion } from '../common/userAgent';
import { wrapInASCIIBox, isLikelyNpxGlobal, assert } from '../utils';
import { spawnAsync } from '../utils/spawnAsync';
import { launchGridAgent } from '../grid/gridAgent';
import type { GridFactory } from '../grid/gridServer';
import { GridServer } from '../grid/gridServer';
@ -120,8 +118,9 @@ program
.command('install [browser...]')
.description('ensure browsers necessary for this version of Playwright are installed')
.option('--with-deps', 'install system dependencies for browsers')
.option('--dry-run', 'do not execute installation, only print information')
.option('--force', 'force reinstall of stable browser channels')
.action(async function(args: string[], options: { withDeps?: boolean, force?: boolean }) {
.action(async function(args: string[], options: { withDeps?: boolean, force?: boolean, dryRun?: boolean }) {
if (isLikelyNpxGlobal()) {
console.error(wrapInASCIIBox([
`WARNING: It looks like you are running 'npx playwright install' without first`,
@ -143,27 +142,26 @@ program
].join('\n'), 1));
}
try {
if (!args.length) {
const executables = registry.defaultExecutables();
if (options.withDeps)
await registry.installDeps(executables, false);
await registry.install(executables, false /* forceReinstall */);
} else {
const installDockerImage = args.some(arg => arg === 'docker-image');
args = args.filter(arg => arg !== 'docker-image');
if (installDockerImage) {
const imageName = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-focal`;
const { code } = await spawnAsync('docker', ['pull', imageName], { stdio: 'inherit' });
if (code !== 0) {
console.log('Failed to pull docker image');
process.exit(1);
const hasNoArguments = !args.length;
const executables = hasNoArguments ? registry.defaultExecutables() : checkBrowsersToInstall(args);
if (options.withDeps)
await registry.installDeps(executables, !!options.dryRun);
if (options.dryRun) {
for (const executable of executables) {
const version = executable.browserVersion ? `version ` + executable.browserVersion : '';
console.log(`browser: ${executable.name}${version ? ' ' + version : ''}`);
console.log(` Install location: ${executable.directory ?? '<system>'}`);
if (executable.downloadURLs?.length) {
const [url, ...fallbacks] = executable.downloadURLs;
console.log(` Download url: ${url}`);
for (let i = 0; i < fallbacks.length; ++i)
console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
}
console.log(``);
}
const executables = checkBrowsersToInstall(args);
if (options.withDeps)
await registry.installDeps(executables, false);
await registry.install(executables, !!options.force /* forceReinstall */);
} else {
const forceReinstall = hasNoArguments ? false : !!options.force;
await registry.install(executables, forceReinstall);
}
} catch (e) {
console.log(`Failed to install browsers\n${e}`);

View File

@ -310,6 +310,8 @@ export interface Executable {
browserName: BrowserName | undefined;
installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none';
directory: string | undefined;
downloadURLs?: string[],
browserVersion?: string,
executablePathOrDie(sdkLanguage: string): string;
executablePath(sdkLanguage: string): string | undefined;
validateHostRequirements(sdkLanguage: string): Promise<void>;
@ -376,7 +378,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
_install: () => this._downloadExecutable(chromium, chromiumExecutable, DOWNLOAD_PATHS['chromium'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(chromium),
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
_dependencyGroup: 'chromium',
_isHermeticInstallation: true,
});
@ -392,7 +396,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-with-symbols', chromiumWithSymbolsExecutable, chromiumWithSymbols.installByDefault, sdkLanguage),
installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromiumWithSymbols.dir, ['chrome-linux'], [], ['chrome-win']),
_install: () => this._downloadExecutable(chromiumWithSymbols, chromiumWithSymbolsExecutable, DOWNLOAD_PATHS['chromium-with-symbols'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(chromiumWithSymbols),
browserVersion: chromiumWithSymbols.browserVersion,
_install: () => this._downloadExecutable(chromiumWithSymbols, chromiumWithSymbolsExecutable),
_dependencyGroup: 'chromium',
_isHermeticInstallation: true,
});
@ -408,7 +414,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-tip-of-tree', chromiumTipOfTreeExecutable, chromiumTipOfTree.installByDefault, sdkLanguage),
installType: chromiumTipOfTree.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'chromium', chromiumTipOfTree.dir, ['chrome-linux'], [], ['chrome-win']),
_install: () => this._downloadExecutable(chromiumTipOfTree, chromiumTipOfTreeExecutable, DOWNLOAD_PATHS['chromium-tip-of-tree'][hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(chromiumTipOfTree),
browserVersion: chromiumTipOfTree.browserVersion,
_install: () => this._downloadExecutable(chromiumTipOfTree, chromiumTipOfTreeExecutable),
_dependencyGroup: 'chromium',
_isHermeticInstallation: true,
});
@ -492,7 +500,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox', firefoxExecutable, firefox.installByDefault, sdkLanguage),
installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'firefox', firefox.dir, ['firefox'], [], ['firefox']),
_install: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_PATHS['firefox'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(firefox),
browserVersion: firefox.browserVersion,
_install: () => this._downloadExecutable(firefox, firefoxExecutable),
_dependencyGroup: 'firefox',
_isHermeticInstallation: true,
});
@ -508,7 +518,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('firefox-beta', firefoxBetaExecutable, firefoxBeta.installByDefault, sdkLanguage),
installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']),
_install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable, DOWNLOAD_PATHS['firefox-beta'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(firefoxBeta),
browserVersion: firefoxBeta.browserVersion,
_install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable),
_dependencyGroup: 'firefox',
_isHermeticInstallation: true,
});
@ -534,7 +546,9 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'webkit', webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
_install: () => this._downloadExecutable(webkit, webkitExecutable, DOWNLOAD_PATHS['webkit'][hostPlatform], 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(webkit),
browserVersion: webkit.browserVersion,
_install: () => this._downloadExecutable(webkit, webkitExecutable),
_dependencyGroup: 'webkit',
_isHermeticInstallation: true,
});
@ -550,7 +564,8 @@ export class Registry {
executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('ffmpeg', ffmpegExecutable, ffmpeg.installByDefault, sdkLanguage),
installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand',
validateHostRequirements: () => Promise.resolve(),
_install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_PATHS['ffmpeg'][hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'),
downloadURLs: this._downloadURLs(ffmpeg),
_install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable),
_dependencyGroup: 'tools',
_isHermeticInstallation: true,
});
@ -727,17 +742,33 @@ export class Registry {
}
}
private async _downloadExecutable(descriptor: BrowsersJSONDescriptor, executablePath: string | undefined, downloadPathTemplate: string | undefined, downloadHostEnv: string) {
if (!downloadPathTemplate || !executablePath)
throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${hostPlatform}`);
if (hostPlatform === 'generic-linux' || hostPlatform === 'generic-linux-arm64')
logPolitely('BEWARE: your OS is not officially supported by Playwright; downloading fallback build.');
private _downloadURLs(descriptor: BrowsersJSONDescriptor): string[] {
const downloadPathTemplate: string|undefined = (DOWNLOAD_PATHS as any)[descriptor.name][hostPlatform];
if (!downloadPathTemplate)
return [];
const downloadPath = util.format(downloadPathTemplate, descriptor.revision);
let downloadURLs = PLAYWRIGHT_CDN_MIRRORS.map(mirror => `${mirror}/${downloadPath}`) ;
let downloadHostEnv;
if (descriptor.name.startsWith('chromium'))
downloadHostEnv = 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST';
else if (descriptor.name.startsWith('firefox'))
downloadHostEnv = 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST';
else if (descriptor.name.startsWith('webkit'))
downloadHostEnv = 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST';
const customHostOverride = (downloadHostEnv && getFromENV(downloadHostEnv)) || getFromENV('PLAYWRIGHT_DOWNLOAD_HOST');
if (customHostOverride)
downloadURLs = [`${customHostOverride}/${downloadPath}`];
return downloadURLs;
}
private async _downloadExecutable(descriptor: BrowsersJSONDescriptor, executablePath: string | undefined) {
const downloadURLs = this._downloadURLs(descriptor);
if (!downloadURLs.length || !executablePath)
throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${hostPlatform}`);
if (hostPlatform === 'generic-linux' || hostPlatform === 'generic-linux-arm64')
logPolitely('BEWARE: your OS is not officially supported by Playwright; downloading fallback build.');
const displayName = descriptor.name.split('-').map(word => {
return word === 'ffmpeg' ? 'FFMPEG' : word.charAt(0).toUpperCase() + word.slice(1);