From fcb233ce949f5f716aee39253e910104b04aa000 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Mon, 27 Mar 2023 12:01:06 +0200 Subject: [PATCH] feat: update Chrome browser binaries (#9917) --- .../browsers/src/browser-data/browser-data.ts | 13 +- packages/browsers/src/browser-data/chrome.ts | 66 +++++----- .../browsers/src/browser-data/chromium.ts | 121 ++++++++++++++++++ packages/browsers/src/httpUtil.ts | 2 +- .../test/src/chrome/chrome-data.spec.ts | 120 +++++++++++++++++ .../test/src/{ => chrome}/cli.spec.ts | 69 ++-------- .../test/src/{ => chrome}/fetch.spec.ts | 52 +------- .../test/src/{ => chrome}/launcher.spec.ts | 69 +--------- .../chromium-data.spec.ts} | 6 +- .../test/src/chromium/launcher.spec.ts | 105 +++++++++++++++ .../browsers/test/src/firefox/cli.spec.ts | 70 ++++++++++ .../browsers/test/src/firefox/fetch.spec.ts | 80 ++++++++++++ .../src/{ => firefox}/firefox-data.spec.ts | 4 +- .../test/src/firefox/launcher.spec.ts | 78 +++++++++++ packages/browsers/test/src/utils.ts | 35 +++++ packages/browsers/test/src/versions.ts | 5 +- 16 files changed, 684 insertions(+), 211 deletions(-) create mode 100644 packages/browsers/src/browser-data/chromium.ts create mode 100644 packages/browsers/test/src/chrome/chrome-data.spec.ts rename packages/browsers/test/src/{ => chrome}/cli.spec.ts (53%) rename packages/browsers/test/src/{ => chrome}/fetch.spec.ts (75%) rename packages/browsers/test/src/{ => chrome}/launcher.spec.ts (61%) rename packages/browsers/test/src/{chrome-data.spec.ts => chromium/chromium-data.spec.ts} (96%) create mode 100644 packages/browsers/test/src/chromium/launcher.spec.ts create mode 100644 packages/browsers/test/src/firefox/cli.spec.ts create mode 100644 packages/browsers/test/src/firefox/fetch.spec.ts rename packages/browsers/test/src/{ => firefox}/firefox-data.spec.ts (96%) create mode 100644 packages/browsers/test/src/firefox/launcher.spec.ts create mode 100644 packages/browsers/test/src/utils.ts diff --git a/packages/browsers/src/browser-data/browser-data.ts b/packages/browsers/src/browser-data/browser-data.ts index 6d9f6a98f96..9f522e6bbad 100644 --- a/packages/browsers/src/browser-data/browser-data.ts +++ b/packages/browsers/src/browser-data/browser-data.ts @@ -15,6 +15,7 @@ */ import * as chrome from './chrome.js'; +import * as chromium from './chromium.js'; import * as firefox from './firefox.js'; import { Browser, @@ -26,13 +27,13 @@ import { export const downloadUrls = { [Browser.CHROME]: chrome.resolveDownloadUrl, - [Browser.CHROMIUM]: chrome.resolveDownloadUrl, + [Browser.CHROMIUM]: chromium.resolveDownloadUrl, [Browser.FIREFOX]: firefox.resolveDownloadUrl, }; export const executablePathByBrowser = { [Browser.CHROME]: chrome.relativeExecutablePath, - [Browser.CHROMIUM]: chrome.relativeExecutablePath, + [Browser.CHROMIUM]: chromium.relativeExecutablePath, [Browser.FIREFOX]: firefox.relativeExecutablePath, }; @@ -50,10 +51,15 @@ export async function resolveBuildId( return await firefox.resolveBuildId('FIREFOX_NIGHTLY'); } case Browser.CHROME: + switch (tag as BrowserTag) { + case BrowserTag.LATEST: + // In CfT beta is the latest version. + return await chrome.resolveBuildId(platform, 'beta'); + } case Browser.CHROMIUM: switch (tag as BrowserTag) { case BrowserTag.LATEST: - return await chrome.resolveBuildId(platform, 'latest'); + return await chromium.resolveBuildId(platform, 'latest'); } } // We assume the tag is the buildId if it didn't match any keywords. @@ -84,6 +90,7 @@ export function resolveSystemExecutablePath( 'System browser detection is not supported for Firefox yet.' ); case Browser.CHROME: + return chromium.resolveSystemExecutablePath(platform, channel); case Browser.CHROMIUM: return chrome.resolveSystemExecutablePath(platform, channel); } diff --git a/packages/browsers/src/browser-data/chrome.ts b/packages/browsers/src/browser-data/chrome.ts index 4f000c79280..34e748b9e5a 100644 --- a/packages/browsers/src/browser-data/chrome.ts +++ b/packages/browsers/src/browser-data/chrome.ts @@ -20,43 +20,43 @@ import {httpRequest} from '../httpUtil.js'; import {BrowserPlatform, ChromeReleaseChannel} from './types.js'; -function archive(platform: BrowserPlatform, buildId: string): string { - switch (platform) { - case BrowserPlatform.LINUX: - return 'chrome-linux'; - case BrowserPlatform.MAC_ARM: - case BrowserPlatform.MAC: - return 'chrome-mac'; - case BrowserPlatform.WIN32: - case BrowserPlatform.WIN64: - // Windows archive name changed at r591479. - return parseInt(buildId, 10) > 591479 ? 'chrome-win' : 'chrome-win32'; - } -} - function folder(platform: BrowserPlatform): string { switch (platform) { case BrowserPlatform.LINUX: - return 'Linux_x64'; + return 'linux64'; case BrowserPlatform.MAC_ARM: - return 'Mac_Arm'; + return 'mac-arm64'; case BrowserPlatform.MAC: - return 'Mac'; + return 'mac-x64'; case BrowserPlatform.WIN32: - return 'Win'; + return 'win32'; case BrowserPlatform.WIN64: - return 'Win_x64'; + return 'win64'; + } +} + +function chromiumDashPlatform(platform: BrowserPlatform): string { + switch (platform) { + case BrowserPlatform.LINUX: + return 'linux'; + case BrowserPlatform.MAC_ARM: + return 'mac'; + case BrowserPlatform.MAC: + return 'mac'; + case BrowserPlatform.WIN32: + return 'win'; + case BrowserPlatform.WIN64: + return 'win64'; } } export function resolveDownloadUrl( platform: BrowserPlatform, buildId: string, - baseUrl = 'https://storage.googleapis.com/chromium-browser-snapshots' + baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing' ): string { - return `${baseUrl}/${folder(platform)}/${buildId}/${archive( - platform, - buildId + return `${baseUrl}/${buildId}/${folder(platform)}/chrome-${folder( + platform )}.zip`; } @@ -68,30 +68,29 @@ export function relativeExecutablePath( case BrowserPlatform.MAC: case BrowserPlatform.MAC_ARM: return path.join( - 'chrome-mac', - 'Chromium.app', + 'chrome-' + folder(platform), + 'Google Chrome for Testing.app', 'Contents', 'MacOS', - 'Chromium' + 'Google Chrome for Testing' ); case BrowserPlatform.LINUX: - return path.join('chrome-linux', 'chrome'); + return path.join('chrome-linux64', 'chrome'); case BrowserPlatform.WIN32: case BrowserPlatform.WIN64: - return path.join('chrome-win', 'chrome.exe'); + return path.join('chrome-' + folder(platform), 'chrome.exe'); } } export async function resolveBuildId( platform: BrowserPlatform, - // We will need it for other channels/keywords. - _channel: 'latest' = 'latest' + channel: 'beta' | 'stable' = 'beta' ): Promise { return new Promise((resolve, reject) => { const request = httpRequest( new URL( - `https://storage.googleapis.com/chromium-browser-snapshots/${folder( + `https://chromiumdash.appspot.com/fetch_releases?platform=${chromiumDashPlatform( platform - )}/LAST_CHANGE` + )}&channel=${channel}` ), 'GET', response => { @@ -104,7 +103,8 @@ export async function resolveBuildId( }); response.on('end', () => { try { - return resolve(String(data)); + const response = JSON.parse(String(data)); + return resolve(response[0].version); } catch { return reject(new Error('Chrome version not found')); } diff --git a/packages/browsers/src/browser-data/chromium.ts b/packages/browsers/src/browser-data/chromium.ts new file mode 100644 index 00000000000..40de87ae63c --- /dev/null +++ b/packages/browsers/src/browser-data/chromium.ts @@ -0,0 +1,121 @@ +/** + * Copyright 2023 Google Inc. 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 {httpRequest} from '../httpUtil.js'; + +import {BrowserPlatform} from './types.js'; + +export {resolveSystemExecutablePath} from './chrome.js'; + +function archive(platform: BrowserPlatform, buildId: string): string { + switch (platform) { + case BrowserPlatform.LINUX: + return 'chrome-linux'; + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return 'chrome-mac'; + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + // Windows archive name changed at r591479. + return parseInt(buildId, 10) > 591479 ? 'chrome-win' : 'chrome-win32'; + } +} + +function folder(platform: BrowserPlatform): string { + switch (platform) { + case BrowserPlatform.LINUX: + return 'Linux_x64'; + case BrowserPlatform.MAC_ARM: + return 'Mac_Arm'; + case BrowserPlatform.MAC: + return 'Mac'; + case BrowserPlatform.WIN32: + return 'Win'; + case BrowserPlatform.WIN64: + return 'Win_x64'; + } +} + +export function resolveDownloadUrl( + platform: BrowserPlatform, + buildId: string, + baseUrl = 'https://storage.googleapis.com/chromium-browser-snapshots' +): string { + return `${baseUrl}/${folder(platform)}/${buildId}/${archive( + platform, + buildId + )}.zip`; +} + +export function relativeExecutablePath( + platform: BrowserPlatform, + _buildId: string +): string { + switch (platform) { + case BrowserPlatform.MAC: + case BrowserPlatform.MAC_ARM: + return path.join( + 'chrome-mac', + 'Chromium.app', + 'Contents', + 'MacOS', + 'Chromium' + ); + case BrowserPlatform.LINUX: + return path.join('chrome-linux', 'chrome'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join('chrome-win', 'chrome.exe'); + } +} +export async function resolveBuildId( + platform: BrowserPlatform, + // We will need it for other channels/keywords. + _channel: 'latest' = 'latest' +): Promise { + return new Promise((resolve, reject) => { + const request = httpRequest( + new URL( + `https://storage.googleapis.com/chromium-browser-snapshots/${folder( + platform + )}/LAST_CHANGE` + ), + 'GET', + response => { + let data = ''; + if (response.statusCode && response.statusCode >= 400) { + return reject(new Error(`Got status code ${response.statusCode}`)); + } + response.on('data', chunk => { + data += chunk; + }); + response.on('end', () => { + try { + return resolve(String(data)); + } catch { + return reject(new Error('Chrome version not found')); + } + }); + }, + false + ); + request.on('error', err => { + reject(err); + }); + }); +} diff --git a/packages/browsers/src/httpUtil.ts b/packages/browsers/src/httpUtil.ts index ac07b93bbd9..ea256489be5 100644 --- a/packages/browsers/src/httpUtil.ts +++ b/packages/browsers/src/httpUtil.ts @@ -48,7 +48,7 @@ export function httpRequest( protocol: url.protocol, hostname: url.hostname, port: url.port, - path: url.pathname, + path: url.pathname + url.search, method, headers: keepAlive ? {Connection: 'keep-alive'} : undefined, }; diff --git a/packages/browsers/test/src/chrome/chrome-data.spec.ts b/packages/browsers/test/src/chrome/chrome-data.spec.ts new file mode 100644 index 00000000000..c0404baad81 --- /dev/null +++ b/packages/browsers/test/src/chrome/chrome-data.spec.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2023 Google Inc. 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 assert from 'assert'; +import path from 'path'; + +import { + BrowserPlatform, + ChromeReleaseChannel, +} from '../../../lib/cjs/browser-data/browser-data.js'; +import { + resolveDownloadUrl, + relativeExecutablePath, + resolveSystemExecutablePath, +} from '../../../lib/cjs/browser-data/chrome.js'; + +describe('Chrome', () => { + it('should resolve download URLs', () => { + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.LINUX, '113.0.5672.0'), + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/linux64/chrome-linux64.zip' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC, '113.0.5672.0'), + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/mac-x64/chrome-mac-x64.zip' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.MAC_ARM, '113.0.5672.0'), + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/mac-arm64/chrome-mac-arm64.zip' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN32, '113.0.5672.0'), + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/win32/chrome-win32.zip' + ); + assert.strictEqual( + resolveDownloadUrl(BrowserPlatform.WIN64, '113.0.5672.0'), + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/win64/chrome-win64.zip' + ); + }); + + it('should resolve executable paths', () => { + assert.strictEqual( + relativeExecutablePath(BrowserPlatform.LINUX, '12372323'), + path.join('chrome-linux64', 'chrome') + ); + assert.strictEqual( + relativeExecutablePath(BrowserPlatform.MAC, '12372323'), + path.join( + 'chrome-mac-x64', + 'Google Chrome for Testing.app', + 'Contents', + 'MacOS', + 'Google Chrome for Testing' + ) + ); + assert.strictEqual( + relativeExecutablePath(BrowserPlatform.MAC_ARM, '12372323'), + path.join( + 'chrome-mac-arm64', + 'Google Chrome for Testing.app', + 'Contents', + 'MacOS', + 'Google Chrome for Testing' + ) + ); + assert.strictEqual( + relativeExecutablePath(BrowserPlatform.WIN32, '12372323'), + path.join('chrome-win32', 'chrome.exe') + ); + assert.strictEqual( + relativeExecutablePath(BrowserPlatform.WIN64, '12372323'), + path.join('chrome-win64', 'chrome.exe') + ); + }); + + it('should resolve system executable path', () => { + process.env['PROGRAMFILES'] = 'C:\\ProgramFiles'; + try { + assert.strictEqual( + resolveSystemExecutablePath( + BrowserPlatform.WIN32, + ChromeReleaseChannel.DEV + ), + 'C:\\ProgramFiles\\Google\\Chrome Dev\\Application\\chrome.exe' + ); + } finally { + delete process.env['PROGRAMFILES']; + } + + assert.strictEqual( + resolveSystemExecutablePath( + BrowserPlatform.MAC, + ChromeReleaseChannel.BETA + ), + '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta' + ); + assert.throws(() => { + assert.strictEqual( + resolveSystemExecutablePath( + BrowserPlatform.LINUX, + ChromeReleaseChannel.CANARY + ), + path.join('chrome-linux', 'chrome') + ); + }, new Error(`Unable to detect browser executable path for 'canary' on linux.`)); + }); +}); diff --git a/packages/browsers/test/src/cli.spec.ts b/packages/browsers/test/src/chrome/cli.spec.ts similarity index 53% rename from packages/browsers/test/src/cli.spec.ts rename to packages/browsers/test/src/chrome/cli.spec.ts index 7482cd2523c..15e31814453 100644 --- a/packages/browsers/test/src/cli.spec.ts +++ b/packages/browsers/test/src/chrome/cli.spec.ts @@ -18,41 +18,22 @@ import assert from 'assert'; import fs from 'fs'; import os from 'os'; import path from 'path'; -import * as readline from 'readline'; -import {Writable, Readable} from 'stream'; -import {CLI} from '../../lib/cjs/CLI.js'; -import {Cache} from '../../lib/cjs/main.js'; +import {CLI} from '../../../lib/cjs/CLI.js'; +import {createMockedReadlineInterface} from '../utils.js'; +import {testChromeBuildId} from '../versions.js'; -import {testChromeBuildId, testFirefoxBuildId} from './versions.js'; - -describe('CLI', function () { +describe('Chrome CLI', function () { this.timeout(90000); let tmpDir = '/tmp/puppeteer-browsers-test'; - function createMockedInterface(input: string) { - const readable = Readable.from([input]); - const writable = new Writable({ - write(_chunk, _encoding, callback) { - // Suppress the output to keep the test clean - callback(); - }, - }); - - return readline.createInterface({ - input: readable, - output: writable, - }); - } - beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test')); }); afterEach(async () => { - new Cache(tmpDir).clear(); - await new CLI(tmpDir, createMockedInterface('yes')).run([ + await new CLI(tmpDir, createMockedReadlineInterface('yes')).run([ 'npx', '@puppeteer/browsers', 'clear', @@ -60,7 +41,7 @@ describe('CLI', function () { ]); }); - it('should download Chromium binaries', async () => { + it('should download Chrome binaries', async () => { await new CLI(tmpDir).run([ 'npx', '@puppeteer/browsers', @@ -75,12 +56,13 @@ describe('CLI', function () { tmpDir, 'chrome', `linux-${testChromeBuildId}`, - 'chrome-linux' + 'chrome-linux64', + 'chrome' ) ) ); - await new CLI(tmpDir, createMockedInterface('no')).run([ + await new CLI(tmpDir, createMockedReadlineInterface('no')).run([ 'npx', '@puppeteer/browsers', 'clear', @@ -92,40 +74,15 @@ describe('CLI', function () { tmpDir, 'chrome', `linux-${testChromeBuildId}`, - 'chrome-linux' + 'chrome-linux64', + 'chrome' ) ) ); }); - it('should download Firefox binaries', async () => { - await new CLI(tmpDir).run([ - 'npx', - '@puppeteer/browsers', - 'install', - `firefox@${testFirefoxBuildId}`, - `--path=${tmpDir}`, - '--platform=linux', - ]); - assert.ok( - fs.existsSync( - path.join(tmpDir, 'firefox', `linux-${testFirefoxBuildId}`, 'firefox') - ) - ); - }); - - it('should download latest Firefox binaries', async () => { - await new CLI(tmpDir).run([ - 'npx', - '@puppeteer/browsers', - 'install', - `firefox@latest`, - `--path=${tmpDir}`, - '--platform=linux', - ]); - }); - - it('should download latest Chrome binaries', async () => { + // Skipped because the current latest is not published yet. + it.skip('should download latest Chrome binaries', async () => { await new CLI(tmpDir).run([ 'npx', '@puppeteer/browsers', diff --git a/packages/browsers/test/src/fetch.spec.ts b/packages/browsers/test/src/chrome/fetch.spec.ts similarity index 75% rename from packages/browsers/test/src/fetch.spec.ts rename to packages/browsers/test/src/chrome/fetch.spec.ts index bab45e381e4..eac23c586f1 100644 --- a/packages/browsers/test/src/fetch.spec.ts +++ b/packages/browsers/test/src/chrome/fetch.spec.ts @@ -27,15 +27,14 @@ import { Browser, BrowserPlatform, Cache, -} from '../../lib/cjs/main.js'; - -import {testChromeBuildId, testFirefoxBuildId} from './versions.js'; +} from '../../../lib/cjs/main.js'; +import {testChromeBuildId} from '../versions.js'; /** * Tests in this spec use real download URLs and unpack live browser archives * so it requires the network access. */ -describe('fetch', () => { +describe('Chrome fetch', () => { let tmpDir = '/tmp/puppeteer-browsers-test'; beforeEach(() => { @@ -96,47 +95,6 @@ describe('fetch', () => { assert.ok(fs.existsSync(expectedOutputPath)); }); - it('should download a buildId that is a bzip2 archive', async function () { - this.timeout(90000); - const expectedOutputPath = path.join( - tmpDir, - 'firefox', - `${BrowserPlatform.LINUX}-${testFirefoxBuildId}` - ); - assert.strictEqual(fs.existsSync(expectedOutputPath), false); - const browser = await fetch({ - cacheDir: tmpDir, - browser: Browser.FIREFOX, - platform: BrowserPlatform.LINUX, - buildId: testFirefoxBuildId, - }); - assert.strictEqual(browser.path, expectedOutputPath); - assert.ok(fs.existsSync(expectedOutputPath)); - }); - - // Fetch relies on the `hdiutil` utility on MacOS. - // The utility is not available on other platforms. - (os.platform() === 'darwin' ? it : it.skip)( - 'should download a buildId that is a dmg archive', - async function () { - this.timeout(120000); - const expectedOutputPath = path.join( - tmpDir, - 'firefox', - `${BrowserPlatform.MAC}-${testFirefoxBuildId}` - ); - assert.strictEqual(fs.existsSync(expectedOutputPath), false); - const browser = await fetch({ - cacheDir: tmpDir, - browser: Browser.FIREFOX, - platform: BrowserPlatform.MAC, - buildId: testFirefoxBuildId, - }); - assert.strictEqual(browser.path, expectedOutputPath); - assert.ok(fs.existsSync(expectedOutputPath)); - } - ); - describe('with proxy', () => { const proxyUrl = new URL(`http://localhost:54321`); let proxyServer: http.Server; @@ -205,7 +163,7 @@ describe('fetch', () => { true ); assert.deepStrictEqual(proxiedRequestUrls, [ - 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip', + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/linux64/chrome-linux64.zip', ]); }); @@ -226,7 +184,7 @@ describe('fetch', () => { assert.strictEqual(browser.path, expectedOutputPath); assert.ok(fs.existsSync(expectedOutputPath)); assert.deepStrictEqual(proxiedRequestUrls, [ - 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/1083080/chrome-linux.zip', + 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/113.0.5672.0/linux64/chrome-linux64.zip', ]); }); }); diff --git a/packages/browsers/test/src/launcher.spec.ts b/packages/browsers/test/src/chrome/launcher.spec.ts similarity index 61% rename from packages/browsers/test/src/launcher.spec.ts rename to packages/browsers/test/src/chrome/launcher.spec.ts index 3ff797659e9..8c9fc3ee30b 100644 --- a/packages/browsers/test/src/launcher.spec.ts +++ b/packages/browsers/test/src/chrome/launcher.spec.ts @@ -27,11 +27,10 @@ import { Browser, BrowserPlatform, Cache, -} from '../../lib/cjs/main.js'; +} from '../../../lib/cjs/main.js'; +import {testChromeBuildId} from '../versions.js'; -import {testChromeBuildId, testFirefoxBuildId} from './versions.js'; - -describe('launcher', () => { +describe('Chrome', () => { it('should compute executable path for Chrome', () => { assert.strictEqual( computeExecutablePath({ @@ -40,35 +39,11 @@ describe('launcher', () => { buildId: '123', cacheDir: 'cache', }), - path.join('cache', 'chrome', 'linux-123', 'chrome-linux', 'chrome') + path.join('cache', 'chrome', 'linux-123', 'chrome-linux64', 'chrome') ); }); - it('should compute executable path for Chromium', () => { - assert.strictEqual( - computeExecutablePath({ - browser: Browser.CHROMIUM, - platform: BrowserPlatform.LINUX, - buildId: '123', - cacheDir: 'cache', - }), - path.join('cache', 'chromium', 'linux-123', 'chrome-linux', 'chrome') - ); - }); - - it('should compute executable path for Firefox', () => { - assert.strictEqual( - computeExecutablePath({ - browser: Browser.FIREFOX, - platform: BrowserPlatform.LINUX, - buildId: '123', - cacheDir: 'cache', - }), - path.join('cache', 'firefox', 'linux-123', 'firefox', 'firefox') - ); - }); - - describe('Chrome', function () { + describe('launcher', function () { this.timeout(60000); let tmpDir = '/tmp/puppeteer-browsers-test'; @@ -127,38 +102,4 @@ describe('launcher', () => { assert.ok(url.startsWith('ws://127.0.0.1:9222/devtools/browser')); }); }); - - describe('Firefox', function () { - this.timeout(60000); - - let tmpDir = '/tmp/puppeteer-browsers-test'; - - beforeEach(async () => { - tmpDir = fs.mkdtempSync( - path.join(os.tmpdir(), 'puppeteer-browsers-test') - ); - await fetch({ - cacheDir: tmpDir, - browser: Browser.FIREFOX, - buildId: testFirefoxBuildId, - }); - }); - - afterEach(() => { - new Cache(tmpDir).clear(); - }); - - it('should launch a Firefox browser', async () => { - const executablePath = computeExecutablePath({ - cacheDir: tmpDir, - browser: Browser.FIREFOX, - buildId: testFirefoxBuildId, - }); - const process = launch({ - executablePath, - args: [`--user-data-dir=${path.join(tmpDir, 'profile')}`], - }); - await process.close(); - }); - }); }); diff --git a/packages/browsers/test/src/chrome-data.spec.ts b/packages/browsers/test/src/chromium/chromium-data.spec.ts similarity index 96% rename from packages/browsers/test/src/chrome-data.spec.ts rename to packages/browsers/test/src/chromium/chromium-data.spec.ts index 491d2d48926..5dae0d0d293 100644 --- a/packages/browsers/test/src/chrome-data.spec.ts +++ b/packages/browsers/test/src/chromium/chromium-data.spec.ts @@ -20,14 +20,14 @@ import path from 'path'; import { BrowserPlatform, ChromeReleaseChannel, -} from '../../lib/cjs/browser-data/browser-data.js'; +} from '../../../lib/cjs/browser-data/browser-data.js'; import { resolveDownloadUrl, relativeExecutablePath, resolveSystemExecutablePath, -} from '../../lib/cjs/browser-data/chrome.js'; +} from '../../../lib/cjs/browser-data/chromium.js'; -describe('Chrome', () => { +describe('Chromium', () => { it('should resolve download URLs', () => { assert.strictEqual( resolveDownloadUrl(BrowserPlatform.LINUX, '1083080'), diff --git a/packages/browsers/test/src/chromium/launcher.spec.ts b/packages/browsers/test/src/chromium/launcher.spec.ts new file mode 100644 index 00000000000..2e7f388f099 --- /dev/null +++ b/packages/browsers/test/src/chromium/launcher.spec.ts @@ -0,0 +1,105 @@ +/** + * Copyright 2023 Google Inc. 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 assert from 'assert'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import { + CDP_WEBSOCKET_ENDPOINT_REGEX, + computeExecutablePath, + launch, + fetch, + Browser, + BrowserPlatform, + Cache, +} from '../../../lib/cjs/main.js'; +import {testChromiumBuildId} from '../versions.js'; + +describe('Chromium', () => { + it('should compute executable path for Chromium', () => { + assert.strictEqual( + computeExecutablePath({ + browser: Browser.CHROMIUM, + platform: BrowserPlatform.LINUX, + buildId: '123', + cacheDir: 'cache', + }), + path.join('cache', 'chromium', 'linux-123', 'chrome-linux', 'chrome') + ); + }); + + describe('launcher', function () { + this.timeout(60000); + + let tmpDir = '/tmp/puppeteer-browsers-test'; + + beforeEach(async () => { + tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'puppeteer-browsers-test') + ); + await fetch({ + cacheDir: tmpDir, + browser: Browser.CHROMIUM, + buildId: testChromiumBuildId, + }); + }); + + afterEach(() => { + new Cache(tmpDir).clear(); + }); + + it('should launch a Chrome browser', async () => { + const executablePath = computeExecutablePath({ + cacheDir: tmpDir, + browser: Browser.CHROMIUM, + buildId: testChromiumBuildId, + }); + const process = launch({ + executablePath, + args: [ + '--headless=new', + '--use-mock-keychain', + '--disable-features=DialMediaRouteProvider', + `--user-data-dir=${path.join(tmpDir, 'profile')}`, + ], + }); + await process.close(); + }); + + it('should allow parsing stderr output of the browser process', async () => { + const executablePath = computeExecutablePath({ + cacheDir: tmpDir, + browser: Browser.CHROMIUM, + buildId: testChromiumBuildId, + }); + const process = launch({ + executablePath, + args: [ + '--headless=new', + '--use-mock-keychain', + '--disable-features=DialMediaRouteProvider', + '--remote-debugging-port=9222', + `--user-data-dir=${path.join(tmpDir, 'profile')}`, + ], + }); + const url = await process.waitForLineOutput(CDP_WEBSOCKET_ENDPOINT_REGEX); + await process.close(); + assert.ok(url.startsWith('ws://127.0.0.1:9222/devtools/browser')); + }); + }); +}); diff --git a/packages/browsers/test/src/firefox/cli.spec.ts b/packages/browsers/test/src/firefox/cli.spec.ts new file mode 100644 index 00000000000..818bde535bd --- /dev/null +++ b/packages/browsers/test/src/firefox/cli.spec.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2023 Google Inc. 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 assert from 'assert'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import {CLI} from '../../../lib/cjs/CLI.js'; +import {createMockedReadlineInterface} from '../utils.js'; +import {testFirefoxBuildId} from '../versions.js'; + +describe('Firefox CLI', function () { + this.timeout(90000); + + let tmpDir = '/tmp/puppeteer-browsers-test'; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test')); + }); + + afterEach(async () => { + await new CLI(tmpDir, createMockedReadlineInterface('yes')).run([ + 'npx', + '@puppeteer/browsers', + 'clear', + `--path=${tmpDir}`, + ]); + }); + + it('should download Firefox binaries', async () => { + await new CLI(tmpDir).run([ + 'npx', + '@puppeteer/browsers', + 'install', + `firefox@${testFirefoxBuildId}`, + `--path=${tmpDir}`, + '--platform=linux', + ]); + assert.ok( + fs.existsSync( + path.join(tmpDir, 'firefox', `linux-${testFirefoxBuildId}`, 'firefox') + ) + ); + }); + + it('should download latest Firefox binaries', async () => { + await new CLI(tmpDir).run([ + 'npx', + '@puppeteer/browsers', + 'install', + `firefox@latest`, + `--path=${tmpDir}`, + '--platform=linux', + ]); + }); +}); diff --git a/packages/browsers/test/src/firefox/fetch.spec.ts b/packages/browsers/test/src/firefox/fetch.spec.ts new file mode 100644 index 00000000000..ebd3c233936 --- /dev/null +++ b/packages/browsers/test/src/firefox/fetch.spec.ts @@ -0,0 +1,80 @@ +/** + * Copyright 2023 Google Inc. 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 assert from 'assert'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import {fetch, Browser, BrowserPlatform, Cache} from '../../../lib/cjs/main.js'; +import {testFirefoxBuildId} from '../versions.js'; + +/** + * Tests in this spec use real download URLs and unpack live browser archives + * so it requires the network access. + */ +describe('Firefox fetch', () => { + let tmpDir = '/tmp/puppeteer-browsers-test'; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test')); + }); + + afterEach(() => { + new Cache(tmpDir).clear(); + }); + + it('should download a buildId that is a bzip2 archive', async function () { + this.timeout(90000); + const expectedOutputPath = path.join( + tmpDir, + 'firefox', + `${BrowserPlatform.LINUX}-${testFirefoxBuildId}` + ); + assert.strictEqual(fs.existsSync(expectedOutputPath), false); + const browser = await fetch({ + cacheDir: tmpDir, + browser: Browser.FIREFOX, + platform: BrowserPlatform.LINUX, + buildId: testFirefoxBuildId, + }); + assert.strictEqual(browser.path, expectedOutputPath); + assert.ok(fs.existsSync(expectedOutputPath)); + }); + + // Fetch relies on the `hdiutil` utility on MacOS. + // The utility is not available on other platforms. + (os.platform() === 'darwin' ? it : it.skip)( + 'should download a buildId that is a dmg archive', + async function () { + this.timeout(120000); + const expectedOutputPath = path.join( + tmpDir, + 'firefox', + `${BrowserPlatform.MAC}-${testFirefoxBuildId}` + ); + assert.strictEqual(fs.existsSync(expectedOutputPath), false); + const browser = await fetch({ + cacheDir: tmpDir, + browser: Browser.FIREFOX, + platform: BrowserPlatform.MAC, + buildId: testFirefoxBuildId, + }); + assert.strictEqual(browser.path, expectedOutputPath); + assert.ok(fs.existsSync(expectedOutputPath)); + } + ); +}); diff --git a/packages/browsers/test/src/firefox-data.spec.ts b/packages/browsers/test/src/firefox/firefox-data.spec.ts similarity index 96% rename from packages/browsers/test/src/firefox-data.spec.ts rename to packages/browsers/test/src/firefox/firefox-data.spec.ts index 8ff72993090..cad4ef5fa39 100644 --- a/packages/browsers/test/src/firefox-data.spec.ts +++ b/packages/browsers/test/src/firefox/firefox-data.spec.ts @@ -19,12 +19,12 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import {BrowserPlatform} from '../../lib/cjs/browser-data/browser-data.js'; +import {BrowserPlatform} from '../../../lib/cjs/browser-data/browser-data.js'; import { createProfile, relativeExecutablePath, resolveDownloadUrl, -} from '../../lib/cjs/browser-data/firefox.js'; +} from '../../../lib/cjs/browser-data/firefox.js'; describe('Firefox', () => { it('should resolve download URLs', () => { diff --git a/packages/browsers/test/src/firefox/launcher.spec.ts b/packages/browsers/test/src/firefox/launcher.spec.ts new file mode 100644 index 00000000000..34aabec1ff8 --- /dev/null +++ b/packages/browsers/test/src/firefox/launcher.spec.ts @@ -0,0 +1,78 @@ +/** + * Copyright 2023 Google Inc. 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 assert from 'assert'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import { + computeExecutablePath, + launch, + fetch, + Browser, + BrowserPlatform, + Cache, +} from '../../../lib/cjs/main.js'; +import {testFirefoxBuildId} from '../versions.js'; + +describe('Firefox', () => { + it('should compute executable path for Firefox', () => { + assert.strictEqual( + computeExecutablePath({ + browser: Browser.FIREFOX, + platform: BrowserPlatform.LINUX, + buildId: '123', + cacheDir: 'cache', + }), + path.join('cache', 'firefox', 'linux-123', 'firefox', 'firefox') + ); + }); + + describe('launcher', function () { + this.timeout(60000); + + let tmpDir = '/tmp/puppeteer-browsers-test'; + + beforeEach(async () => { + tmpDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'puppeteer-browsers-test') + ); + await fetch({ + cacheDir: tmpDir, + browser: Browser.FIREFOX, + buildId: testFirefoxBuildId, + }); + }); + + afterEach(() => { + new Cache(tmpDir).clear(); + }); + + it('should launch a Firefox browser', async () => { + const executablePath = computeExecutablePath({ + cacheDir: tmpDir, + browser: Browser.FIREFOX, + buildId: testFirefoxBuildId, + }); + const process = launch({ + executablePath, + args: [`--user-data-dir=${path.join(tmpDir, 'profile')}`], + }); + await process.close(); + }); + }); +}); diff --git a/packages/browsers/test/src/utils.ts b/packages/browsers/test/src/utils.ts new file mode 100644 index 00000000000..dff018ff6b5 --- /dev/null +++ b/packages/browsers/test/src/utils.ts @@ -0,0 +1,35 @@ +/** + * Copyright 2023 Google Inc. 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 * as readline from 'readline'; +import {Writable, Readable} from 'stream'; + +export function createMockedReadlineInterface( + input: string +): readline.Interface { + const readable = Readable.from([input]); + const writable = new Writable({ + write(_chunk, _encoding, callback) { + // Suppress the output to keep the test clean + callback(); + }, + }); + + return readline.createInterface({ + input: readable, + output: writable, + }); +} diff --git a/packages/browsers/test/src/versions.ts b/packages/browsers/test/src/versions.ts index 26e4caf4d46..b09b8a6de5a 100644 --- a/packages/browsers/test/src/versions.ts +++ b/packages/browsers/test/src/versions.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -export const testChromeBuildId = '1083080'; +export const testChromeBuildId = '113.0.5672.0'; +export const testChromiumBuildId = '1083080'; // TODO: We can add a Cron job to auto-update on change. // Firefox keeps only `latest` version of Nightly builds. -export const testFirefoxBuildId = '112.0a1'; +export const testFirefoxBuildId = '113.0a1';