chore: move working with browser channels to Registry Executables (#7581)
This commit is contained in:
		
							parent
							
								
									34777853f7
								
							
						
					
					
						commit
						0742cb9076
					
				
							
								
								
									
										132
									
								
								src/cli/cli.ts
								
								
								
								
							
							
						
						
									
										132
									
								
								src/cli/cli.ts
								
								
								
								
							|  | @ -32,46 +32,9 @@ import { BrowserType } from '../client/browserType'; | |||
| import { BrowserContextOptions, LaunchOptions } from '../client/types'; | ||||
| import { spawn } from 'child_process'; | ||||
| 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 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 | ||||
|     .version('Version ' + packageJSON.version) | ||||
|     .name(process.env.PW_CLI_NAME || 'npx playwright'); | ||||
|  | @ -119,29 +82,36 @@ program | |||
|       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 | ||||
|     .command('install [browserType...]') | ||||
|     .command('install [browser...]') | ||||
|     .description('ensure browsers necessary for this version of Playwright are installed') | ||||
|     .action(async function(args: any[]) { | ||||
|     .action(async function(args: string[]) { | ||||
|       try { | ||||
|         // Install default browsers when invoked without arguments.
 | ||||
|         if (!args.length) { | ||||
|         if (!args.length) | ||||
|           await registry.install(); | ||||
|           return; | ||||
|         } | ||||
|         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); | ||||
|         else | ||||
|           await registry.install(checkBrowsersToInstall(args)); | ||||
|       } catch (e) { | ||||
|         console.log(`Failed to install browsers\n${e}`); | ||||
|         process.exit(1); | ||||
|  | @ -153,51 +123,31 @@ program | |||
|       console.log(`    Install default browsers.`); | ||||
|       console.log(``); | ||||
|       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 | ||||
|     .command('install-deps [browserType...]') | ||||
|     .command('install-deps [browser...]') | ||||
|     .description('install dependencies necessary to run browsers (will ask for sudo permissions)') | ||||
|     .action(async function(browserTypes: string[]) { | ||||
|     .action(async function(args: string[]) { | ||||
|       try { | ||||
|         // TODO: verify the list and print supported browserTypes in the error message.
 | ||||
|         const binaries = browserTypes.map(arg => registry.findExecutable(arg)).filter(b => !!b) as Executable[]; | ||||
|         // When passed no arguments, assume default browsers.
 | ||||
|         await registry.installDeps(browserTypes.length ? binaries : undefined); | ||||
|         if (!args.length) | ||||
|           await registry.installDeps(); | ||||
|         else | ||||
|           await registry.installDeps(checkBrowsersToInstall(args)); | ||||
|       } catch (e) { | ||||
|         console.log(`Failed to install browser dependencies\n${e}`); | ||||
|         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 = [ | ||||
|  |  | |||
|  | @ -44,8 +44,8 @@ export abstract class BrowserType extends SdkObject { | |||
|     this._name = browserName; | ||||
|   } | ||||
| 
 | ||||
|   executablePath(channel?: string): string { | ||||
|     return registry.findExecutable(this._name).maybeExecutablePath() || ''; | ||||
|   executablePath(): string { | ||||
|     return registry.findExecutable(this._name).executablePath() || ''; | ||||
|   } | ||||
| 
 | ||||
|   name(): string { | ||||
|  | @ -156,21 +156,19 @@ export abstract class BrowserType extends SdkObject { | |||
|     else | ||||
|       browserArguments.push(...this._defaultArgs(options, isPersistent, userDataDir)); | ||||
| 
 | ||||
|     const executable = executablePath || this.executablePath(options.channel); | ||||
|     if (!executable) | ||||
|       throw new Error(`No executable path is specified. Pass "executablePath" option directly.`); | ||||
|     if (!(await existsAsync(executable))) { | ||||
|       const errorMessageLines = [`Failed to launch ${this._name} because executable doesn't exist at ${executable}`]; | ||||
|       // If we tried using stock downloaded browser, suggest re-installing playwright.
 | ||||
|       if (!executablePath) | ||||
|         errorMessageLines.push(`Run "npx playwright install" to install browsers`); | ||||
|       throw new Error(errorMessageLines.join('\n')); | ||||
|     let executable: string; | ||||
|     if (executablePath) { | ||||
|       if (!(await existsAsync(executablePath))) | ||||
|         throw new Error(`Failed to launch ${this._name} because executable doesn't exist at ${executablePath}`); | ||||
|       executable = executablePath; | ||||
|     } else { | ||||
|       const registryExecutable = registry.findExecutable(options.channel || this._name); | ||||
|       if (!registryExecutable || registryExecutable.browserName !== this._name) | ||||
|         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; | ||||
|     const shouldWaitForWSListening = options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port')); | ||||
|     const waitForWSEndpoint = shouldWaitForWSListening ? new Promise<string>(f => wsEndpointCallback = f) : undefined; | ||||
|  |  | |||
|  | @ -27,13 +27,12 @@ import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../tra | |||
| import { CRDevTools } from './crDevTools'; | ||||
| import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; | ||||
| 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 { ProgressController } from '../progress'; | ||||
| import { TimeoutSettings } from '../../utils/timeoutSettings'; | ||||
| import { helper } from '../helper'; | ||||
| import { CallMetadata } from '../instrumentation'; | ||||
| import { findChromiumChannel } from './findChromiumChannel'; | ||||
| import http from 'http'; | ||||
| import { registry } from '../../utils/registry'; | ||||
| 
 | ||||
|  | @ -49,20 +48,6 @@ export class Chromium extends BrowserType { | |||
|       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) { | ||||
|     const controller = new ProgressController(metadata, this); | ||||
|     controller.setLogName('browser'); | ||||
|  | @ -104,7 +89,7 @@ export class Chromium extends BrowserType { | |||
| 
 | ||||
|   private _createDevTools() { | ||||
|     // 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; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -845,10 +845,9 @@ class FrameSession { | |||
| 
 | ||||
|   async _createVideoRecorder(screencastId: string, options: types.PageScreencastOptions): Promise<void> { | ||||
|     assert(!this._screencastId); | ||||
|     const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePathIfExists(); | ||||
|     if (!ffmpegPath) | ||||
|       throw new Error('ffmpeg executable was not found'); | ||||
|     if (!canAccessFile(ffmpegPath)) { | ||||
|     const ffmpegPath = registry.findExecutable('ffmpeg')!.executablePath(); | ||||
|     // TODO: use default error message once it's ready.
 | ||||
|     if (!ffmpegPath || !canAccessFile(ffmpegPath)) { | ||||
|       let message: string = ''; | ||||
|       switch (this._page._browserContext._options.sdkLanguage) { | ||||
|         case 'python': message = 'playwright install ffmpeg'; break; | ||||
|  |  | |||
|  | @ -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}`); | ||||
| } | ||||
|  | @ -18,7 +18,6 @@ | |||
| import * as os from 'os'; | ||||
| import fs from 'fs'; | ||||
| import path from 'path'; | ||||
| import { assert } from '../../utils/utils'; | ||||
| import { FFBrowser } from './ffBrowser'; | ||||
| import { kBrowserCloseMessageId } from './ffConnection'; | ||||
| import { BrowserType } from '../browserType'; | ||||
|  | @ -26,25 +25,12 @@ import { Env } from '../../utils/processLauncher'; | |||
| import { ConnectionTransport } from '../transport'; | ||||
| import { BrowserOptions, PlaywrightOptions } from '../browser'; | ||||
| import * as types from '../types'; | ||||
| import { registry } from '../../utils/registry'; | ||||
| 
 | ||||
| export class Firefox extends BrowserType { | ||||
|   constructor(playwrightOptions: 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> { | ||||
|     return FFBrowser.connect(transport, options); | ||||
|   } | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ import { EventEmitter } from 'events'; | |||
| import { internalCallMetadata } from '../../instrumentation'; | ||||
| import type { CallLog, EventData, Mode, Source } from './recorderTypes'; | ||||
| import { BrowserContext } from '../../browserContext'; | ||||
| import { existsAsync, isUnderTest } from '../../../utils/utils'; | ||||
| import { isUnderTest } from '../../../utils/utils'; | ||||
| import { installAppIcon } from '../../chromium/crApp'; | ||||
| 
 | ||||
| declare global { | ||||
|  | @ -97,8 +97,7 @@ export class RecorderApp extends EventEmitter { | |||
|     let executablePath: string | undefined; | ||||
|     if (inspectedContext._browser.options.isChromium) { | ||||
|       channel = inspectedContext._browser.options.channel; | ||||
|       const defaultExecutablePath = recorderPlaywright.chromium.executablePath(channel); | ||||
|       if (!(await existsAsync(defaultExecutablePath))) | ||||
|       if (!channel) | ||||
|         executablePath = inspectedContext._browser.options.customExecutablePath; | ||||
|     } | ||||
|     const context = await recorderPlaywright.chromium.launchPersistentContext(internalCallMetadata(), '', { | ||||
|  |  | |||
|  | @ -30,7 +30,6 @@ import { internalCallMetadata } from '../../instrumentation'; | |||
| import { ProgressController } from '../../progress'; | ||||
| import { BrowserContext } from '../../browserContext'; | ||||
| import { registry } from '../../../utils/registry'; | ||||
| import { findChromiumChannel } from '../../chromium/findChromiumChannel'; | ||||
| import { installAppIcon } from '../../chromium/crApp'; | ||||
| 
 | ||||
| export class TraceViewer { | ||||
|  | @ -140,21 +139,17 @@ export class TraceViewer { | |||
|     // Null means no installation and no channels found.
 | ||||
|     let channel = null; | ||||
|     if (traceViewerBrowser === 'chromium') { | ||||
|       if (registry.findExecutable('chromium').executablePathIfExists()) { | ||||
|         // This means we have a browser downloaded.
 | ||||
|         channel = undefined; | ||||
|       } else { | ||||
|         for (const c of ['chrome', 'msedge']) { | ||||
|           try { | ||||
|             findChromiumChannel(c); | ||||
|             channel = c; | ||||
|             break; | ||||
|           } catch (e) { | ||||
|           } | ||||
|       for (const name of ['chromium', 'chrome', 'msedge']) { | ||||
|         try { | ||||
|           registry.findExecutable(name)!.executablePathOrDie(); | ||||
|           channel = name === 'chromium' ? undefined : name; | ||||
|           break; | ||||
|         } catch (e) { | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (channel === null) { | ||||
|         // TODO: language-specific error message, or fallback to default error.
 | ||||
|         throw new Error(` | ||||
| ================================================================== | ||||
| Please run 'npx playwright install' to install Playwright browsers | ||||
|  |  | |||
|  | @ -35,7 +35,9 @@ function isSupportedWindowsVersion(): boolean { | |||
|   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')) { | ||||
|     const {code} = await utils.spawnAsync('powershell.exe', [path.join(BIN_DIRECTORY, 'install_media_pack.ps1')], { cwd: BIN_DIRECTORY, stdio: 'inherit' }); | ||||
|     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(); | ||||
|   if (ubuntuVersion !== '18.04' && ubuntuVersion !== '20.04' && ubuntuVersion !== '21.04') { | ||||
|     console.warn('Cannot install dependencies for this linux distribution!');  // eslint-disable-line no-console
 | ||||
|  |  | |||
|  | @ -21,11 +21,12 @@ 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 } from './utils'; | ||||
| import { installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; | ||||
| import { getFromENV, getAsBooleanFromENV, calculateSha1, removeFolders, existsAsync, hostPlatform, canAccessFile, spawnAsync, fetchData } from './utils'; | ||||
| import { DependencyGroup, installDependenciesLinux, installDependenciesWindows, validateDependenciesLinux, validateDependenciesWindows } from './dependencies'; | ||||
| import { downloadBrowserWithProgressBar, logPolitely } from './browserFetcher'; | ||||
| 
 | ||||
| const PACKAGE_PATH = path.join(__dirname, '..', '..'); | ||||
| const BIN_PATH = path.join(__dirname, '..', '..', 'bin'); | ||||
| 
 | ||||
| const EXECUTABLE_PATHS = { | ||||
|   'chromium': { | ||||
|  | @ -215,21 +216,23 @@ function readDescriptors(packagePath: string) { | |||
| 
 | ||||
| export type BrowserName = 'chromium' | 'firefox' | 'webkit'; | ||||
| 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']; | ||||
| 
 | ||||
| export interface Executable { | ||||
|   type: 'browser' | 'tool'; | ||||
|   name: BrowserName | InternalTool; | ||||
|   type: 'browser' | 'tool' | 'channel'; | ||||
|   name: BrowserName | InternalTool | ChromiumChannel; | ||||
|   browserName: BrowserName | undefined; | ||||
|   installType: 'download-by-default' | 'download-on-demand'; | ||||
|   maybeExecutablePath(): string | undefined; | ||||
|   executablePathIfExists(): string | undefined; | ||||
|   directoryIfExists(): string | undefined; | ||||
|   installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none'; | ||||
|   directory: string | undefined; | ||||
|   executablePathOrDie(): string; | ||||
|   executablePath(): string | undefined; | ||||
|   validateHostRequirements(): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| interface ExecutableImpl extends Executable { | ||||
|   _download?: () => Promise<void>; | ||||
|   _install?: () => Promise<void>; | ||||
|   _dependencyGroup?: DependencyGroup; | ||||
| } | ||||
| 
 | ||||
| export class Registry { | ||||
|  | @ -237,72 +240,146 @@ export class Registry { | |||
| 
 | ||||
|   constructor(packagePath: string) { | ||||
|     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]; | ||||
|       return tokens ? path.join(dir, ...tokens) : undefined; | ||||
|     }; | ||||
|     const directoryIfExists = (d: string) => fs.existsSync(d) ? d : undefined; | ||||
|     const executablePathIfExists = (e: string | undefined) => e && canAccessFile(e) ? e : undefined; | ||||
|     const executablePathOrDie = (name: string, e: string | 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 = []; | ||||
| 
 | ||||
|     const chromium = descriptors.find(d => d.name === 'chromium')!; | ||||
|     const chromiumExecutable = executablePath(chromium.dir, 'chromium'); | ||||
|     const chromiumExecutable = findExecutablePath(chromium.dir, 'chromium'); | ||||
|     this._executables.push({ | ||||
|       type: 'browser', | ||||
|       name: 'chromium', | ||||
|       browserName: 'chromium', | ||||
|       directoryIfExists: () => directoryIfExists(chromium.dir), | ||||
|       maybeExecutablePath: () => chromiumExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(chromiumExecutable), | ||||
|       directory: chromium.dir, | ||||
|       executablePath: () => chromiumExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('chromium', chromiumExecutable), | ||||
|       installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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 chromiumWithSymbolsExecutable = executablePath(chromiumWithSymbols.dir, 'chromium'); | ||||
|     const chromiumWithSymbolsExecutable = findExecutablePath(chromiumWithSymbols.dir, 'chromium'); | ||||
|     this._executables.push({ | ||||
|       type: 'tool', | ||||
|       name: 'chromium-with-symbols', | ||||
|       browserName: 'chromium', | ||||
|       directoryIfExists: () => directoryIfExists(chromiumWithSymbols.dir), | ||||
|       maybeExecutablePath: () => chromiumWithSymbolsExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(chromiumWithSymbolsExecutable), | ||||
|       directory: chromiumWithSymbols.dir, | ||||
|       executablePath: () => chromiumWithSymbolsExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('chromium-with-symbols', chromiumWithSymbolsExecutable), | ||||
|       installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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 firefoxExecutable = executablePath(firefox.dir, 'firefox'); | ||||
|     const firefoxExecutable = findExecutablePath(firefox.dir, 'firefox'); | ||||
|     this._executables.push({ | ||||
|       type: 'browser', | ||||
|       name: 'firefox', | ||||
|       browserName: 'firefox', | ||||
|       directoryIfExists: () => directoryIfExists(firefox.dir), | ||||
|       maybeExecutablePath: () => firefoxExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(firefoxExecutable), | ||||
|       directory: firefox.dir, | ||||
|       executablePath: () => firefoxExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('firefox', firefoxExecutable), | ||||
|       installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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 firefoxBetaExecutable = executablePath(firefoxBeta.dir, 'firefox'); | ||||
|     const firefoxBetaExecutable = findExecutablePath(firefoxBeta.dir, 'firefox'); | ||||
|     this._executables.push({ | ||||
|       type: 'tool', | ||||
|       name: 'firefox-beta', | ||||
|       browserName: 'firefox', | ||||
|       directoryIfExists: () => directoryIfExists(firefoxBeta.dir), | ||||
|       maybeExecutablePath: () => firefoxBetaExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(firefoxBetaExecutable), | ||||
|       directory: firefoxBeta.dir, | ||||
|       executablePath: () => firefoxBetaExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('firefox-beta', firefoxBetaExecutable), | ||||
|       installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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 webkitExecutable = executablePath(webkit.dir, 'webkit'); | ||||
|     const webkitExecutable = findExecutablePath(webkit.dir, 'webkit'); | ||||
|     const webkitLinuxLddDirectories = [ | ||||
|       path.join('minibrowser-gtk'), | ||||
|       path.join('minibrowser-gtk', 'bin'), | ||||
|  | @ -315,29 +392,73 @@ export class Registry { | |||
|       type: 'browser', | ||||
|       name: 'webkit', | ||||
|       browserName: 'webkit', | ||||
|       directoryIfExists: () => directoryIfExists(webkit.dir), | ||||
|       maybeExecutablePath: () => webkitExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(webkitExecutable), | ||||
|       directory: webkit.dir, | ||||
|       executablePath: () => webkitExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('webkit', webkitExecutable), | ||||
|       installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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 ffmpegExecutable = executablePath(ffmpeg.dir, 'ffmpeg'); | ||||
|     const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg'); | ||||
|     this._executables.push({ | ||||
|       type: 'tool', | ||||
|       name: 'ffmpeg', | ||||
|       browserName: undefined, | ||||
|       directoryIfExists: () => directoryIfExists(ffmpeg.dir), | ||||
|       maybeExecutablePath: () => ffmpegExecutable, | ||||
|       executablePathIfExists: () => executablePathIfExists(ffmpegExecutable), | ||||
|       directory: ffmpeg.dir, | ||||
|       executablePath: () => ffmpegExecutable, | ||||
|       executablePathOrDie: () => executablePathOrDie('ffmpeg', ffmpegExecutable), | ||||
|       installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand', | ||||
|       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: string): Executable | undefined; | ||||
|   findExecutable(name: string): Executable | undefined { | ||||
|  | @ -373,10 +494,10 @@ export class Registry { | |||
| 
 | ||||
|   async installDeps(executablesToInstallDeps?: Executable[]) { | ||||
|     const executables = this._addRequirementsAndDedupe(executablesToInstallDeps); | ||||
|     const targets = new Set<'chromium' | 'firefox' | 'webkit' | 'tools'>(); | ||||
|     const targets = new Set<DependencyGroup>(); | ||||
|     for (const executable of executables) { | ||||
|       if (executable.browserName) | ||||
|         targets.add(executable.browserName); | ||||
|       if (executable._dependencyGroup) | ||||
|         targets.add(executable._dependencyGroup); | ||||
|     } | ||||
|     targets.add('tools'); | ||||
|     if (os.platform() === 'win32') | ||||
|  | @ -414,8 +535,8 @@ export class Registry { | |||
| 
 | ||||
|       // Install browsers for this package.
 | ||||
|       for (const executable of executables) { | ||||
|         if (executable._download) | ||||
|           await executable._download(); | ||||
|         if (executable._install) | ||||
|           await executable._install(); | ||||
|         else | ||||
|           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), ''); | ||||
|   } | ||||
| 
 | ||||
|   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) { | ||||
|     // 1. Collect used downloads and package descriptors.
 | ||||
|     const usedBrowserPaths: Set<string> = new Set(); | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ import path from 'path'; | |||
| import { spawnSync } from 'child_process'; | ||||
| import { registry } from '../../src/utils/registry'; | ||||
| 
 | ||||
| const ffmpeg = registry.findExecutable('ffmpeg')!.executablePathIfExists() || ''; | ||||
| const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath(); | ||||
| 
 | ||||
| export class VideoPlayer { | ||||
|   videoWidth: number; | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ import { spawnSync } from 'child_process'; | |||
| import { PNG } from 'pngjs'; | ||||
| import { registry } from '../src/utils/registry'; | ||||
| 
 | ||||
| const ffmpeg = registry.findExecutable('ffmpeg')!.executablePathIfExists() || ''; | ||||
| const ffmpeg = registry.findExecutable('ffmpeg')!.executablePath(); | ||||
| 
 | ||||
| export class VideoPlayer { | ||||
|   fileName: string; | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ Example: | |||
| 
 | ||||
|     // 4. Generate 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); | ||||
| 
 | ||||
|     // 5. Update docs.
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue