From 6d6ea9bf59daa3fb851b3da8baa27887e0aa2c28 Mon Sep 17 00:00:00 2001 From: Josh Bielick Date: Fri, 11 Feb 2022 08:07:31 -0500 Subject: [PATCH] fix: always use ENV executable path when present (#7985) Some recent changes to allow arm64 environments (including M1 macs) to launch a chromium installation successfully before arm-compatible builds were downloadable prevented the usage of PUPPETEER_EXECUTABLE_PATH in some environments. Currently, when the platform is not darwin and the arch is arm64, an executable cannot be specified using the environment variable. Generally speaking, environment variables have highest precedence for options such as this since they depend on system configuration. These change: 1. allow the ENV variable to always be used when defined and not specified in LaunchOptions (and when not puppeteer-core) 2. Retain the existing behavior of assuming /usr/bin/chromium-browser on platforms like Ubuntu (exact if-conditions preserved to avoid any breaking changes) 3. Add some tests for this particular portion of the code. --- src/node/Launcher.ts | 39 +++++++++++--------- test/launcher.spec.ts | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/src/node/Launcher.ts b/src/node/Launcher.ts index 4c87f0dbd43e9..e80c1690a6271 100644 --- a/src/node/Launcher.ts +++ b/src/node/Launcher.ts @@ -146,15 +146,9 @@ class ChromeLauncher implements ProductLauncher { chromeExecutable = executablePathForChannel(channel); } else if (!executablePath) { - // Use Intel x86 builds on Apple M1 until native macOS arm64 - // Chromium builds are available. - if (os.platform() !== 'darwin' && os.arch() === 'arm64') { - chromeExecutable = '/usr/bin/chromium-browser'; - } else { - const { missingText, executablePath } = resolveExecutablePath(this); - if (missingText) throw new Error(missingText); - chromeExecutable = executablePath; - } + const { missingText, executablePath } = resolveExecutablePath(this); + if (missingText) throw new Error(missingText); + chromeExecutable = executablePath; } if (!chromeExecutable) { @@ -799,9 +793,11 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { executablePath: string; missingText?: string; } { + const { product, _isPuppeteerCore, _projectRoot, _preferredRevision } = + launcher; let downloadPath: string | undefined; // puppeteer-core doesn't take into account PUPPETEER_* env variables. - if (!launcher._isPuppeteerCore) { + if (!_isPuppeteerCore) { const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.npm_config_puppeteer_executable_path || @@ -813,22 +809,31 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { : undefined; return { executablePath, missingText }; } + const ubuntuChromiumPath = '/usr/bin/chromium-browser'; + if ( + product === 'chrome' && + os.platform() !== 'darwin' && + os.arch() === 'arm64' && + fs.existsSync(ubuntuChromiumPath) + ) { + return { executablePath: ubuntuChromiumPath, missingText: undefined }; + } downloadPath = process.env.PUPPETEER_DOWNLOAD_PATH || process.env.npm_config_puppeteer_download_path || process.env.npm_package_config_puppeteer_download_path; } - if (!launcher._projectRoot) { + if (!_projectRoot) { throw new Error( '_projectRoot is undefined. Unable to create a BrowserFetcher.' ); } - const browserFetcher = new BrowserFetcher(launcher._projectRoot, { - product: launcher.product, + const browserFetcher = new BrowserFetcher(_projectRoot, { + product: product, path: downloadPath, }); - if (!launcher._isPuppeteerCore && launcher.product === 'chrome') { + if (!_isPuppeteerCore && product === 'chrome') { const revision = process.env['PUPPETEER_CHROMIUM_REVISION']; if (revision) { const revisionInfo = browserFetcher.revisionInfo(revision); @@ -839,13 +844,13 @@ function resolveExecutablePath(launcher: ChromeLauncher | FirefoxLauncher): { return { executablePath: revisionInfo.executablePath, missingText }; } } - const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision); + const revisionInfo = browserFetcher.revisionInfo(_preferredRevision); const firefoxHelp = `Run \`PUPPETEER_PRODUCT=firefox npm install\` to download a supported Firefox browser binary.`; const chromeHelp = `Run \`npm install\` to download the correct Chromium revision (${launcher._preferredRevision}).`; const missingText = !revisionInfo.local - ? `Could not find expected browser (${launcher.product}) locally. ${ - launcher.product === 'chrome' ? chromeHelp : firefoxHelp + ? `Could not find expected browser (${product}) locally. ${ + product === 'chrome' ? chromeHelp : firefoxHelp }` : undefined; return { executablePath: revisionInfo.executablePath, missingText }; diff --git a/test/launcher.spec.ts b/test/launcher.spec.ts index 1570870337ddd..7fa0744562cd6 100644 --- a/test/launcher.spec.ts +++ b/test/launcher.spec.ts @@ -733,6 +733,88 @@ describe('Launcher specs', function () { const executablePath = puppeteer.executablePath('chrome'); expect(executablePath).toBeTruthy(); }); + describe('when PUPPETEER_EXECUTABLE_PATH is set', () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + process.env.PUPPETEER_EXECUTABLE_PATH = ''; + sandbox + .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH') + .value('SOME_CUSTOM_EXECUTABLE'); + }); + + afterEach(() => sandbox.restore()); + + it('its value is returned', async () => { + const { puppeteer } = getTestState(); + + const executablePath = puppeteer.executablePath(); + + expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE'); + }); + }); + + describe('when the product is chrome, platform is not darwin, and arch is arm64', () => { + describe('and the executable exists', () => { + itChromeOnly('returns /usr/bin/chromium-browser', async () => { + const { puppeteer } = getTestState(); + const osPlatformStub = sinon.stub(os, 'platform').returns('linux'); + const osArchStub = sinon.stub(os, 'arch').returns('arm64'); + const fsExistsStub = sinon.stub(fs, 'existsSync'); + fsExistsStub.withArgs('/usr/bin/chromium-browser').returns(true); + + const executablePath = puppeteer.executablePath(); + + expect(executablePath).toEqual('/usr/bin/chromium-browser'); + + osPlatformStub.restore(); + osArchStub.restore(); + fsExistsStub.restore(); + }); + describe('and PUPPETEER_EXECUTABLE_PATH is set', () => { + const sandbox = sinon.createSandbox(); + + beforeEach(() => { + process.env.PUPPETEER_EXECUTABLE_PATH = ''; + sandbox + .stub(process.env, 'PUPPETEER_EXECUTABLE_PATH') + .value('SOME_CUSTOM_EXECUTABLE'); + }); + + afterEach(() => sandbox.restore()); + + it('its value is returned', async () => { + const { puppeteer } = getTestState(); + + const executablePath = puppeteer.executablePath(); + + expect(executablePath).toEqual('SOME_CUSTOM_EXECUTABLE'); + }); + }); + }); + describe('and the executable does not exist', () => { + itChromeOnly( + 'does not return /usr/bin/chromium-browser', + async () => { + const { puppeteer } = getTestState(); + const osPlatformStub = sinon + .stub(os, 'platform') + .returns('linux'); + const osArchStub = sinon.stub(os, 'arch').returns('arm64'); + const fsExistsStub = sinon.stub(fs, 'existsSync'); + fsExistsStub.withArgs('/usr/bin/chromium-browser').returns(false); + + const executablePath = puppeteer.executablePath(); + + expect(executablePath).not.toEqual('/usr/bin/chromium-browser'); + + osPlatformStub.restore(); + osArchStub.restore(); + fsExistsStub.restore(); + } + ); + }); + }); }); });