diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 4607cf4926..d02170cd47 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -234,7 +234,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome + - run: npx playwright install --with-deps --force chrome - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run ctest env: PWTEST_CHANNEL: chrome @@ -259,7 +259,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome + - run: npx playwright install --with-deps --force chrome - run: npm run ctest shell: bash env: @@ -286,7 +286,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome + - run: npx playwright install --with-deps --force chrome - run: npm run ctest env: PWTEST_CHANNEL: chrome @@ -388,7 +388,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge + - run: npx playwright install --with-deps --force msedge - run: npm run ctest env: PWTEST_CHANNEL: msedge @@ -414,7 +414,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge + - run: npx playwright install --with-deps --force msedge - run: npm run ctest shell: bash env: @@ -438,7 +438,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge + - run: npx playwright install --with-deps --force msedge - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run ctest env: PWTEST_CHANNEL: msedge @@ -463,7 +463,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-beta + - run: npx playwright install --with-deps --force msedge-beta - run: npm run ctest env: PWTEST_CHANNEL: msedge-beta @@ -488,7 +488,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-beta + - run: npx playwright install --with-deps --force msedge-beta - run: npm run ctest shell: bash env: @@ -512,7 +512,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-beta + - run: npx playwright install --with-deps --force msedge-beta - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run ctest env: PWTEST_CHANNEL: msedge-beta @@ -537,7 +537,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-dev + - run: npx playwright install --with-deps --force msedge-dev - run: npm run ctest env: PWTEST_CHANNEL: msedge-dev @@ -562,7 +562,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-dev + - run: npx playwright install --with-deps --force msedge-dev - run: npm run ctest shell: bash env: @@ -586,7 +586,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps msedge-dev + - run: npx playwright install --with-deps --force msedge-dev - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run ctest env: PWTEST_CHANNEL: msedge-dev @@ -611,7 +611,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome-beta + - run: npx playwright install --with-deps --force chrome-beta - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run ctest env: PWTEST_CHANNEL: chrome-beta @@ -636,7 +636,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome-beta + - run: npx playwright install --with-deps --force chrome-beta - run: npm run ctest shell: bash env: @@ -663,7 +663,7 @@ jobs: env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - run: npm run build - - run: npx playwright install --with-deps chrome-beta + - run: npx playwright install --with-deps --force chrome-beta - run: npm run ctest env: PWTEST_CHANNEL: chrome-beta diff --git a/packages/playwright-core/src/cli/cli.ts b/packages/playwright-core/src/cli/cli.ts index 7287ee8fcd..552bd8fe98 100755 --- a/packages/playwright-core/src/cli/cli.ts +++ b/packages/playwright-core/src/cli/cli.ts @@ -113,13 +113,14 @@ program .command('install [browser...]') .description('ensure browsers necessary for this version of Playwright are installed') .option('--with-deps', 'install system dependencies for browsers') - .action(async function(args: string[], options: { withDeps?: boolean }) { + .option('--force', 'force reinstall of stable browser channels') + .action(async function(args: string[], options: { withDeps?: boolean, force?: boolean }) { try { if (!args.length) { const executables = registry.defaultExecutables(); if (options.withDeps) await registry.installDeps(executables, false); - await registry.install(executables); + await registry.install(executables, false /* forceReinstall */); } else { const installDockerImage = args.some(arg => arg === 'docker-image'); args = args.filter(arg => arg !== 'docker-image'); @@ -135,7 +136,7 @@ program const executables = checkBrowsersToInstall(args); if (options.withDeps) await registry.installDeps(executables, false); - await registry.install(executables); + await registry.install(executables, !!options.force /* forceReinstall */); } } catch (e) { console.log(`Failed to install browsers\n${e}`); diff --git a/packages/playwright-core/src/utils/registry.ts b/packages/playwright-core/src/utils/registry.ts index 7d4e42dce4..409a032ea4 100644 --- a/packages/playwright-core/src/utils/registry.ts +++ b/packages/playwright-core/src/utils/registry.ts @@ -21,7 +21,7 @@ import * as util from 'util'; import * as fs from 'fs'; import lockfile from 'proper-lockfile'; import { getUbuntuVersion } from './ubuntuVersion'; -import { getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile, spawnAsync, fetchData, wrapInASCIIBox, transformCommandsForRoot } from './utils'; +import { getFromENV, getAsBooleanFromENV, getClientLanguage, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile, spawnAsync, fetchData, wrapInASCIIBox, transformCommandsForRoot } from './utils'; import { DependencyGroup, installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher'; @@ -254,6 +254,7 @@ export interface Executable { interface ExecutableImpl extends Executable { _install?: () => Promise; _dependencyGroup?: DependencyGroup; + _isHermeticInstallation?: boolean; } export class Registry { @@ -303,6 +304,7 @@ export class Registry { 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'), _dependencyGroup: 'chromium', + _isHermeticInstallation: true, }); const chromiumWithSymbols = descriptors.find(d => d.name === 'chromium-with-symbols')!; @@ -318,6 +320,7 @@ export class Registry { 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'), _dependencyGroup: 'chromium', + _isHermeticInstallation: true, }); this._executables.push(this._createChromiumChannel('chrome', { @@ -401,6 +404,7 @@ export class Registry { validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, 'firefox', firefox.dir, ['firefox'], [], ['firefox']), _install: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_PATHS['firefox'][hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'), _dependencyGroup: 'firefox', + _isHermeticInstallation: true, }); const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta')!; @@ -416,6 +420,7 @@ export class Registry { 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'), _dependencyGroup: 'firefox', + _isHermeticInstallation: true, }); const webkit = descriptors.find(d => d.name === 'webkit')!; @@ -439,6 +444,7 @@ export class Registry { 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'), _dependencyGroup: 'webkit', + _isHermeticInstallation: true, }); const ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!; @@ -454,6 +460,7 @@ export class Registry { validateHostRequirements: () => Promise.resolve(), _install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_PATHS['ffmpeg'][hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'), _dependencyGroup: 'tools', + _isHermeticInstallation: true, }); } @@ -491,6 +498,7 @@ export class Registry { executablePathOrDie: (sdkLanguage: string) => executablePath(sdkLanguage, true)!, installType: install ? 'install-script' : 'none', validateHostRequirements: () => Promise.resolve(), + _isHermeticInstallation: false, _install: install, }; } @@ -548,7 +556,7 @@ export class Registry { return await installDependenciesLinux(targets, dryRun); } - async install(executablesToInstall: Executable[]) { + async install(executablesToInstall: Executable[], forceReinstall: boolean) { const executables = this._addRequirementsAndDedupe(executablesToInstall); await fs.promises.mkdir(registryDirectory, { recursive: true }); const lockfilePath = path.join(registryDirectory, '__dirlock'); @@ -578,10 +586,29 @@ export class Registry { // Install browsers for this package. for (const executable of executables) { - if (executable._install) - await executable._install(); - else + if (!executable._install) throw new Error(`ERROR: Playwright does not support installing ${executable.name}`); + + const { langName } = getClientLanguage(); + if (!executable._isHermeticInstallation && !forceReinstall && executable.executablePath(langName)) { + const command = buildPlaywrightCLICommand(langName, 'install --force ' + executable.name); + throw new Error('\n' + wrapInASCIIBox([ + `ATTENTION: "${executable.name}" is already installed on the system!`, + ``, + `"${executable.name}" installation is not hermetic; installing newer version`, + `requires *removal* of a current installation first.`, + ``, + `To *uninstall* current version and re-install latest "${executable.name}":`, + ``, + `- Close all running instances of "${executable.name}", if any`, + `- Use "--force" to install browser:`, + ``, + ` ${command}`, + ``, + `<3 Playwright Team`, + ].join('\n'), 1)); + } + await executable._install(); } } catch (e) { if (e.code === 'ELOCKED') { @@ -761,7 +788,7 @@ export async function installBrowsersForNpmInstall(browsers: string[]) { executables.push(executable); } - await registry.install(executables); + await registry.install(executables, false /* forceReinstall */); } export function findChromiumChannel(sdkLanguage: string): string | undefined { diff --git a/packages/playwright-core/src/utils/utils.ts b/packages/playwright-core/src/utils/utils.ts index ecce954d7f..6d7e4ad9e1 100644 --- a/packages/playwright-core/src/utils/utils.ts +++ b/packages/playwright-core/src/utils/utils.ts @@ -479,6 +479,11 @@ function determineUserAgent(): string { } } + const { langName, langVersion } = getClientLanguage(); + return `Playwright/${getPlaywrightVersion()} (${os.arch()}; ${osIdentifier} ${osVersion}) ${langName}/${langVersion}`; +} + +export function getClientLanguage(): { langName: string, langVersion: string } { let langName = 'unknown'; let langVersion = 'unknown'; if (!process.env.PW_LANG_NAME) { @@ -488,8 +493,7 @@ function determineUserAgent(): string { langName = process.env.PW_LANG_NAME; langVersion = process.env.PW_LANG_NAME_VERSION ?? 'unknown'; } - - return `Playwright/${getPlaywrightVersion()} (${os.arch()}; ${osIdentifier} ${osVersion}) ${langName}/${langVersion}`; + return { langName, langVersion }; } export function getPlaywrightVersion(majorMinorOnly = false) {