diff --git a/packages/browsers/src/browsers/browsers.ts b/packages/browsers/src/browsers/browsers.ts index 0faa826d60c..daad7f80fcc 100644 --- a/packages/browsers/src/browsers/browsers.ts +++ b/packages/browsers/src/browsers/browsers.ts @@ -23,4 +23,9 @@ export const downloadUrls = { [Browser.FIREFOX]: firefox.resolveDownloadUrl, }; +export const executablePathByBrowser = { + [Browser.CHROME]: chrome.executablePath, + [Browser.FIREFOX]: firefox.executablePath, +}; + export {Browser, BrowserPlatform}; diff --git a/packages/browsers/src/browsers/chrome.ts b/packages/browsers/src/browsers/chrome.ts index c750620ae6e..a8ea4af390c 100644 --- a/packages/browsers/src/browsers/chrome.ts +++ b/packages/browsers/src/browsers/chrome.ts @@ -15,6 +15,7 @@ */ import {BrowserPlatform} from './types.js'; +import path from 'path'; function archive(platform: BrowserPlatform, revision: string): string { switch (platform) { @@ -55,3 +56,27 @@ export function resolveDownloadUrl( revision )}.zip`; } + +export function executablePath( + platform: BrowserPlatform, + revision: string, + basePath = '' +): string { + const browserPath = path.join(basePath, `${platform}-${revision}`); + switch (platform) { + case BrowserPlatform.MAC: + case BrowserPlatform.MAC_ARM: + return path.join( + browserPath, + 'Chromium.app', + 'Contents', + 'MacOS', + 'Chromium' + ); + case BrowserPlatform.LINUX: + return path.join(browserPath, 'chrome'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join(browserPath, 'chrome.exe'); + } +} diff --git a/packages/browsers/src/browsers/firefox.ts b/packages/browsers/src/browsers/firefox.ts index b63a5013223..cb3c0419121 100644 --- a/packages/browsers/src/browsers/firefox.ts +++ b/packages/browsers/src/browsers/firefox.ts @@ -15,6 +15,7 @@ */ import {BrowserPlatform} from './types.js'; +import path from 'path'; function archive(platform: BrowserPlatform, revision: string): string { switch (platform) { @@ -24,7 +25,6 @@ function archive(platform: BrowserPlatform, revision: string): string { case BrowserPlatform.MAC: return `firefox-${revision}.en-US.mac.dmg`; case BrowserPlatform.WIN32: - return `firefox-${revision}.en-US.${platform}.zip`; case BrowserPlatform.WIN64: return `firefox-${revision}.en-US.${platform}.zip`; } @@ -37,3 +37,27 @@ export function resolveDownloadUrl( ): string { return `${baseUrl}/${archive(platform, revision)}`; } + +export function executablePath( + platform: BrowserPlatform, + revision: string, + basePath = '' +): string { + const browserPath = path.join(basePath, `${platform}-${revision}`); + switch (platform) { + case BrowserPlatform.MAC_ARM: + case BrowserPlatform.MAC: + return path.join( + browserPath, + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ); + case BrowserPlatform.LINUX: + return path.join(browserPath, 'firefox', 'firefox'); + case BrowserPlatform.WIN32: + case BrowserPlatform.WIN64: + return path.join(browserPath, 'firefox', 'firefox.exe'); + } +} diff --git a/packages/browsers/src/detectPlatform.ts b/packages/browsers/src/detectPlatform.ts new file mode 100644 index 00000000000..23b4efa709b --- /dev/null +++ b/packages/browsers/src/detectPlatform.ts @@ -0,0 +1,57 @@ +/** + * 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 os from 'os'; +import {BrowserPlatform} from './browsers/browsers.js'; + +export function detectPlatform(): BrowserPlatform | undefined { + const platform = os.platform(); + switch (platform) { + case 'darwin': + return os.arch() === 'arm64' + ? BrowserPlatform.MAC_ARM + : BrowserPlatform.MAC; + case 'linux': + return BrowserPlatform.LINUX; + case 'win32': + return os.arch() === 'x64' || + // Windows 11 for ARM supports x64 emulation + (os.arch() === 'arm64' && isWindows11(os.release())) + ? BrowserPlatform.WIN64 + : BrowserPlatform.WIN32; + default: + return undefined; + } +} + +/** + * Windows 11 is identified by the version 10.0.22000 or greater + * @internal + */ +function isWindows11(version: string): boolean { + const parts = version.split('.'); + if (parts.length > 2) { + const major = parseInt(parts[0] as string, 10); + const minor = parseInt(parts[1] as string, 10); + const patch = parseInt(parts[2] as string, 10); + return ( + major > 10 || + (major === 10 && minor > 0) || + (major === 10 && minor === 0 && patch >= 22000) + ); + } + return false; +} diff --git a/packages/browsers/src/fetch.ts b/packages/browsers/src/fetch.ts index 870b8711463..e0f136822ab 100644 --- a/packages/browsers/src/fetch.ts +++ b/packages/browsers/src/fetch.ts @@ -24,6 +24,7 @@ import {Browser, BrowserPlatform, downloadUrls} from './browsers/browsers.js'; import {downloadFile, headHttpRequest} from './httpUtil.js'; import assert from 'assert'; import {unpackArchive} from './fileUtil.js'; +import {detectPlatform} from './detectPlatform.js'; const debugFetch = debug('puppeteer:browsers:fetcher'); @@ -123,45 +124,6 @@ export async function canFetch(options: Options): Promise { ); } -/** - * Windows 11 is identified by the version 10.0.22000 or greater - * @internal - */ -function isWindows11(version: string): boolean { - const parts = version.split('.'); - if (parts.length > 2) { - const major = parseInt(parts[0] as string, 10); - const minor = parseInt(parts[1] as string, 10); - const patch = parseInt(parts[2] as string, 10); - return ( - major > 10 || - (major === 10 && minor > 0) || - (major === 10 && minor === 0 && patch >= 22000) - ); - } - return false; -} - -function detectPlatform(): BrowserPlatform | undefined { - const platform = os.platform(); - switch (platform) { - case 'darwin': - return os.arch() === 'arm64' - ? BrowserPlatform.MAC_ARM - : BrowserPlatform.MAC; - case 'linux': - return BrowserPlatform.LINUX; - case 'win32': - return os.arch() === 'x64' || - // Windows 11 for ARM supports x64 emulation - (os.arch() === 'arm64' && isWindows11(os.release())) - ? BrowserPlatform.WIN64 - : BrowserPlatform.WIN32; - default: - return undefined; - } -} - function getDownloadUrl( browser: Browser, platform: BrowserPlatform, diff --git a/packages/browsers/src/launcher.ts b/packages/browsers/src/launcher.ts new file mode 100644 index 00000000000..dbdff12585c --- /dev/null +++ b/packages/browsers/src/launcher.ts @@ -0,0 +1,62 @@ +/** + * 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 { + Browser, + BrowserPlatform, + executablePathByBrowser, +} from './browsers/browsers.js'; +import {detectPlatform} from './detectPlatform.js'; +import os from 'os'; + +/** + * @public + */ +export interface Options { + /** + * Root path to the storage directory. + */ + path: string; + /** + * Determines which platform the browser will be suited for. + * + * @defaultValue Auto-detected. + */ + platform?: BrowserPlatform; + /** + * Determines which browser to fetch. + */ + browser: Browser; + /** + * Determines which revision to dowloand. Revision should uniquely identify + * binaries and they are used for caching. + */ + revision: string; +} + +export function computeExecutablePath(options: Options): string { + options.platform ??= detectPlatform(); + if (!options.platform) { + throw new Error( + `Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})` + ); + } + return executablePathByBrowser[options.browser]( + options.platform, + options.revision, + options.path + ); +} diff --git a/packages/browsers/test/src/chrome-data.spec.ts b/packages/browsers/test/src/chrome-data.spec.ts index b18dedd659d..e86272129d8 100644 --- a/packages/browsers/test/src/chrome-data.spec.ts +++ b/packages/browsers/test/src/chrome-data.spec.ts @@ -14,9 +14,13 @@ * limitations under the License. */ -import {resolveDownloadUrl} from '../../lib/cjs/browsers/chrome.js'; +import { + resolveDownloadUrl, + executablePath, +} from '../../lib/cjs/browsers/chrome.js'; import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js'; import assert from 'assert'; +import path from 'path'; describe('Chrome', () => { it('should resolve download URLs', () => { @@ -41,4 +45,33 @@ describe('Chrome', () => { 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1083080/chrome-win.zip' ); }); + + it('should resolve executable paths', () => { + assert.strictEqual( + executablePath(BrowserPlatform.LINUX, '12372323'), + path.join('linux-12372323', 'chrome') + ); + assert.strictEqual( + executablePath(BrowserPlatform.MAC, '12372323'), + path.join('mac-12372323', 'Chromium.app', 'Contents', 'MacOS', 'Chromium') + ); + assert.strictEqual( + executablePath(BrowserPlatform.MAC_ARM, '12372323'), + path.join( + 'mac_arm-12372323', + 'Chromium.app', + 'Contents', + 'MacOS', + 'Chromium' + ) + ); + assert.strictEqual( + executablePath(BrowserPlatform.WIN32, '12372323'), + path.join('win32-12372323', 'chrome.exe') + ); + assert.strictEqual( + executablePath(BrowserPlatform.WIN64, '12372323'), + path.join('win64-12372323', 'chrome.exe') + ); + }); }); diff --git a/packages/browsers/test/src/fetch.spec.ts b/packages/browsers/test/src/fetch.spec.ts index 3ee0e417169..4788f4fb7ba 100644 --- a/packages/browsers/test/src/fetch.spec.ts +++ b/packages/browsers/test/src/fetch.spec.ts @@ -110,7 +110,7 @@ describe('fetch', () => { (os.platform() === 'darwin' ? it : it.skip)( 'should download a revision that is a dmg archive', async function () { - this.timeout(60000); + this.timeout(120000); const expectedOutputPath = path.join( tmpDir, `${BrowserPlatform.MAC}-${testFirefoxRevision}` diff --git a/packages/browsers/test/src/firefox-data.spec.ts b/packages/browsers/test/src/firefox-data.spec.ts index 8822aece487..d4043bca2ba 100644 --- a/packages/browsers/test/src/firefox-data.spec.ts +++ b/packages/browsers/test/src/firefox-data.spec.ts @@ -14,9 +14,13 @@ * limitations under the License. */ -import {resolveDownloadUrl} from '../../lib/cjs/browsers/firefox.js'; +import { + executablePath, + resolveDownloadUrl, +} from '../../lib/cjs/browsers/firefox.js'; import {BrowserPlatform} from '../../lib/cjs/browsers/browsers.js'; import assert from 'assert'; +import path from 'path'; describe('Firefox', () => { it('should resolve download URLs', () => { @@ -41,4 +45,39 @@ describe('Firefox', () => { 'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win64.zip' ); }); + + it('should resolve executable paths', () => { + assert.strictEqual( + executablePath(BrowserPlatform.LINUX, '111.0a1'), + path.join('linux-111.0a1', 'firefox', 'firefox') + ); + assert.strictEqual( + executablePath(BrowserPlatform.MAC, '111.0a1'), + path.join( + 'mac-111.0a1', + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ) + ); + assert.strictEqual( + executablePath(BrowserPlatform.MAC_ARM, '111.0a1'), + path.join( + 'mac_arm-111.0a1', + 'Firefox Nightly.app', + 'Contents', + 'MacOS', + 'firefox' + ) + ); + assert.strictEqual( + executablePath(BrowserPlatform.WIN32, '111.0a1'), + path.join('win32-111.0a1', 'firefox', 'firefox.exe') + ); + assert.strictEqual( + executablePath(BrowserPlatform.WIN64, '111.0a1'), + path.join('win64-111.0a1', 'firefox', 'firefox.exe') + ); + }); }); diff --git a/packages/browsers/test/src/launcher.spec.ts b/packages/browsers/test/src/launcher.spec.ts new file mode 100644 index 00000000000..2ec6c37a516 --- /dev/null +++ b/packages/browsers/test/src/launcher.spec.ts @@ -0,0 +1,46 @@ +/** + * 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 {computeExecutablePath} from '../../lib/cjs/launcher.js'; +import {Browser, BrowserPlatform} from '../../lib/cjs/browsers/browsers.js'; + +import assert from 'assert'; +import path from 'path'; + +describe('launcher', () => { + it('should compute executable path for Chrome', () => { + assert.strictEqual( + computeExecutablePath({ + browser: Browser.CHROME, + platform: BrowserPlatform.LINUX, + revision: '123', + path: 'cache', + }), + path.join('cache', 'linux-123', 'chrome') + ); + }); + it('should compute executable path for Firefox', () => { + assert.strictEqual( + computeExecutablePath({ + browser: Browser.FIREFOX, + platform: BrowserPlatform.LINUX, + revision: '123', + path: 'cache', + }), + path.join('cache', 'linux-123', 'firefox', 'firefox') + ); + }); +});