diff --git a/packages/playwright-core/src/server/registry/browserFetcher.ts b/packages/playwright-core/src/server/registry/browserFetcher.ts index a78361d5e7..f312a5eb13 100644 --- a/packages/playwright-core/src/server/registry/browserFetcher.ts +++ b/packages/playwright-core/src/server/registry/browserFetcher.ts @@ -24,17 +24,16 @@ import { debugLogger } from '../../common/debugLogger'; import { download } from './download'; import { extract } from '../../zipBundle'; -export async function downloadBrowserWithProgressBar(title: string, browserDirectory: string, executablePath: string, downloadURL: string, downloadFileName: string): Promise { +export async function downloadBrowserWithProgressBar(title: string, browserDirectory: string, executablePath: string, downloadURLs: string[], downloadFileName: string): Promise { if (await existsAsync(browserDirectory)) { // Already downloaded. debugLogger.log('install', `${title} is already downloaded.`); return false; } - const url = downloadURL; const zipPath = path.join(os.tmpdir(), downloadFileName); try { - await download(url, zipPath, { + await download(downloadURLs, zipPath, { progressBarName: title, log: debugLogger.log.bind(debugLogger, 'install'), userAgent: getUserAgent(), diff --git a/packages/playwright-core/src/server/registry/download.ts b/packages/playwright-core/src/server/registry/download.ts index 12f14301ff..0aba083619 100644 --- a/packages/playwright-core/src/server/registry/download.ts +++ b/packages/playwright-core/src/server/registry/download.ts @@ -79,7 +79,7 @@ type DownloadOptions = { }; export async function download( - url: string, + urls: string | string[], destination: string, options: DownloadOptions = {} ) { @@ -88,6 +88,9 @@ export async function download( log( `downloading ${progressBarName} - attempt #${attempt}` ); + if (!Array.isArray(urls)) + urls = [urls]; + const url = urls[(attempt - 1) % urls.length]; const { error } = await downloadFile(url, destination, { progressCallback: getDownloadProgress(progressBarName), log, @@ -99,18 +102,8 @@ export async function download( } const errorMessage = error?.message || ''; log(`attempt #${attempt} - ERROR: ${errorMessage}`); - if ( - attempt < retryCount && - (errorMessage.includes('ECONNRESET') || - errorMessage.includes('ETIMEDOUT')) - ) { - // Maximum default delay is 3rd retry: 1337.5ms - const millis = Math.random() * 200 + 250 * Math.pow(1.5, attempt); - log(`sleeping ${millis}ms before retry...`); - await new Promise(c => setTimeout(c, millis)); - } else { + if (attempt >= retryCount) throw error; - } } } diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 2a6cf77b90..3d4912f238 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -36,6 +36,12 @@ export { writeDockerVersion } from './dependencies'; const PACKAGE_PATH = path.join(__dirname, '..', '..', '..'); const BIN_PATH = path.join(__dirname, '..', '..', '..', 'bin'); +const PLAYWRIGHT_CDN_MIRRORS = [ + 'https://playwright.azureedge.net', + 'https://playwright-akamai.azureedge.net', + 'https://playwright-verizon.azureedge.net', +]; + const EXECUTABLE_PATHS = { 'chromium': { 'linux': ['chrome-linux', 'chrome'], @@ -701,12 +707,12 @@ export class Registry { 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 Ubuntu build as a fallback.'); - const downloadHost = - (downloadHostEnv && getFromENV(downloadHostEnv)) || - getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') || - 'https://playwright.azureedge.net'; const downloadPath = util.format(downloadPathTemplate, descriptor.revision); - const downloadURL = `${downloadHost}/${downloadPath}`; + + let downloadURLs = PLAYWRIGHT_CDN_MIRRORS.map(mirror => `${mirror}/${downloadPath}`) ; + const customHostOverride = (downloadHostEnv && getFromENV(downloadHostEnv)) || getFromENV('PLAYWRIGHT_DOWNLOAD_HOST'); + if (customHostOverride) + downloadURLs = [`${customHostOverride}/${downloadPath}`]; const displayName = descriptor.name.split('-').map(word => { return word === 'ffmpeg' ? 'FFMPEG' : word.charAt(0).toUpperCase() + word.slice(1); @@ -716,7 +722,7 @@ export class Registry { : `${displayName} playwright build v${descriptor.revision}`; const downloadFileName = `playwright-download-${descriptor.name}-${hostPlatform}-${descriptor.revision}.zip`; - await downloadBrowserWithProgressBar(title, descriptor.dir, executablePath, downloadURL, downloadFileName).catch(e => { + await downloadBrowserWithProgressBar(title, descriptor.dir, executablePath, downloadURLs, downloadFileName).catch(e => { throw new Error(`Failed to download ${title}, caused by\n${e.stack}`); }); await fs.promises.writeFile(markerFilePath(descriptor.dir), '');