feat: support chrome-headless-shell (#10739)

This commit is contained in:
Mathias Bynens 2023-08-16 13:34:54 +02:00 committed by GitHub
parent e12b558f50
commit 416843ba68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 413 additions and 9 deletions

View File

@ -15,8 +15,9 @@ export declare enum Browser
## Enumeration Members ## Enumeration Members
| Member | Value | Description | | Member | Value | Description |
| ------------ | ------------------------------------- | ----------- | | ------------------- | ---------------------------------------------- | ----------- |
| CHROME | <code>&quot;chrome&quot;</code> | | | CHROME | <code>&quot;chrome&quot;</code> | |
| CHROMEDRIVER | <code>&quot;chromedriver&quot;</code> | | | CHROMEDRIVER | <code>&quot;chromedriver&quot;</code> | |
| CHROMEHEADLESSSHELL | <code>&quot;chrome-headless-shell&quot;</code> | |
| CHROMIUM | <code>&quot;chromium&quot;</code> | | | CHROMIUM | <code>&quot;chromium&quot;</code> | |
| FIREFOX | <code>&quot;firefox&quot;</code> | | | FIREFOX | <code>&quot;firefox&quot;</code> | |

View File

@ -152,6 +152,18 @@ export class CLI {
'$0 install chromedriver@115.0.5790', '$0 install chromedriver@115.0.5790',
'Install the latest available patch (115.0.5790.X) build for ChromeDriver.' 'Install the latest available patch (115.0.5790.X) build for ChromeDriver.'
); );
yargs.example(
'$0 install chrome-headless-shell',
'Install the latest available chrome-headless-shell build.'
);
yargs.example(
'$0 install chrome-headless-shell@beta',
'Install the latest available chrome-headless-shell build corresponding to the Beta channel.'
);
yargs.example(
'$0 install chrome-headless-shell@118',
'Install the latest available chrome-headless-shell 118 build.'
);
yargs.example( yargs.example(
'$0 install chromium@1083080', '$0 install chromium@1083080',
'Install the revision 1083080 of the Chromium browser.' 'Install the revision 1083080 of the Chromium browser.'

View File

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import * as chromeHeadlessShell from './chrome-headless-shell.js';
import * as chrome from './chrome.js'; import * as chrome from './chrome.js';
import * as chromedriver from './chromedriver.js'; import * as chromedriver from './chromedriver.js';
import * as chromium from './chromium.js'; import * as chromium from './chromium.js';
@ -30,6 +31,7 @@ export {ProfileOptions};
export const downloadUrls = { export const downloadUrls = {
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadUrl, [Browser.CHROMEDRIVER]: chromedriver.resolveDownloadUrl,
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.resolveDownloadUrl,
[Browser.CHROME]: chrome.resolveDownloadUrl, [Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.CHROMIUM]: chromium.resolveDownloadUrl, [Browser.CHROMIUM]: chromium.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl, [Browser.FIREFOX]: firefox.resolveDownloadUrl,
@ -37,6 +39,7 @@ export const downloadUrls = {
export const downloadPaths = { export const downloadPaths = {
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadPath, [Browser.CHROMEDRIVER]: chromedriver.resolveDownloadPath,
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.resolveDownloadPath,
[Browser.CHROME]: chrome.resolveDownloadPath, [Browser.CHROME]: chrome.resolveDownloadPath,
[Browser.CHROMIUM]: chromium.resolveDownloadPath, [Browser.CHROMIUM]: chromium.resolveDownloadPath,
[Browser.FIREFOX]: firefox.resolveDownloadPath, [Browser.FIREFOX]: firefox.resolveDownloadPath,
@ -44,6 +47,7 @@ export const downloadPaths = {
export const executablePathByBrowser = { export const executablePathByBrowser = {
[Browser.CHROMEDRIVER]: chromedriver.relativeExecutablePath, [Browser.CHROMEDRIVER]: chromedriver.relativeExecutablePath,
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.relativeExecutablePath,
[Browser.CHROME]: chrome.relativeExecutablePath, [Browser.CHROME]: chrome.relativeExecutablePath,
[Browser.CHROMIUM]: chromium.relativeExecutablePath, [Browser.CHROMIUM]: chromium.relativeExecutablePath,
[Browser.FIREFOX]: firefox.relativeExecutablePath, [Browser.FIREFOX]: firefox.relativeExecutablePath,
@ -111,6 +115,33 @@ export async function resolveBuildId(
} }
return tag; return tag;
} }
case Browser.CHROMEHEADLESSSHELL: {
switch (tag) {
case BrowserTag.LATEST:
case BrowserTag.CANARY:
return await chromeHeadlessShell.resolveBuildId(
ChromeReleaseChannel.CANARY
);
case BrowserTag.BETA:
return await chromeHeadlessShell.resolveBuildId(
ChromeReleaseChannel.BETA
);
case BrowserTag.DEV:
return await chromeHeadlessShell.resolveBuildId(
ChromeReleaseChannel.DEV
);
case BrowserTag.STABLE:
return await chromeHeadlessShell.resolveBuildId(
ChromeReleaseChannel.STABLE
);
default:
const result = await chromeHeadlessShell.resolveBuildId(tag);
if (result) {
return result;
}
}
return tag;
}
case Browser.CHROMIUM: case Browser.CHROMIUM:
switch (tag as BrowserTag) { switch (tag as BrowserTag) {
case BrowserTag.LATEST: case BrowserTag.LATEST:
@ -154,6 +185,7 @@ export function resolveSystemExecutablePath(
): string { ): string {
switch (browser) { switch (browser) {
case Browser.CHROMEDRIVER: case Browser.CHROMEDRIVER:
case Browser.CHROMEHEADLESSSHELL:
case Browser.FIREFOX: case Browser.FIREFOX:
case Browser.CHROMIUM: case Browser.CHROMIUM:
throw new Error( throw new Error(

View File

@ -0,0 +1,79 @@
/**
* 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 {BrowserPlatform} from './types.js';
function folder(platform: BrowserPlatform): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'linux64';
case BrowserPlatform.MAC_ARM:
return 'mac-arm64';
case BrowserPlatform.MAC:
return 'mac-x64';
case BrowserPlatform.WIN32:
return 'win32';
case BrowserPlatform.WIN64:
return 'win64';
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
buildId: string,
baseUrl = 'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing'
): string {
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
}
export function resolveDownloadPath(
platform: BrowserPlatform,
buildId: string
): string[] {
return [
buildId,
folder(platform),
`chrome-headless-shell-${folder(platform)}.zip`,
];
}
export function relativeExecutablePath(
platform: BrowserPlatform,
_buildId: string
): string {
switch (platform) {
case BrowserPlatform.MAC:
case BrowserPlatform.MAC_ARM:
return path.join(
'chrome-headless-shell-' + folder(platform),
'chrome-headless-shell'
);
case BrowserPlatform.LINUX:
return path.join(
'chrome-headless-shell-linux64',
'chrome-headless-shell'
);
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return path.join(
'chrome-headless-shell-' + folder(platform),
'chrome-headless-shell.exe'
);
}
}
export {resolveBuildId} from './chrome.js';

View File

@ -24,6 +24,7 @@ import * as firefox from './firefox.js';
*/ */
export enum Browser { export enum Browser {
CHROME = 'chrome', CHROME = 'chrome',
CHROMEHEADLESSSHELL = 'chrome-headless-shell',
CHROMIUM = 'chromium', CHROMIUM = 'chromium',
FIREFOX = 'firefox', FIREFOX = 'firefox',
CHROMEDRIVER = 'chromedriver', CHROMEDRIVER = 'chromedriver',

View File

@ -0,0 +1,81 @@
/**
* 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} from '../../../lib/cjs/browser-data/browser-data.js';
import {
resolveDownloadUrl,
relativeExecutablePath,
resolveBuildId,
} from '../../../lib/cjs/browser-data/chrome-headless-shell.js';
describe('chrome-headless-shell', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '118.0.5950.0'),
'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/118.0.5950.0/linux64/chrome-headless-shell-linux64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '118.0.5950.0'),
'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/118.0.5950.0/mac-x64/chrome-headless-shell-mac-x64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '118.0.5950.0'),
'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/118.0.5950.0/mac-arm64/chrome-headless-shell-mac-arm64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '118.0.5950.0'),
'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/118.0.5950.0/win32/chrome-headless-shell-win32.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '118.0.5950.0'),
'https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/118.0.5950.0/win64/chrome-headless-shell-win64.zip'
);
});
it('should resolve milestones', async () => {
assert.strictEqual(await resolveBuildId('118'), '118.0.5950.0');
});
it('should resolve build prefix', async () => {
assert.strictEqual(await resolveBuildId('118.0.5950'), '118.0.5950.0');
});
it('should resolve executable paths', () => {
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.LINUX, '12372323'),
path.join('chrome-headless-shell-linux64', 'chrome-headless-shell')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC, '12372323'),
path.join('chrome-headless-shell-mac-x64/', 'chrome-headless-shell')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC_ARM, '12372323'),
path.join('chrome-headless-shell-mac-arm64', 'chrome-headless-shell')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN32, '12372323'),
path.join('chrome-headless-shell-win32', 'chrome-headless-shell.exe')
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN64, '12372323'),
path.join('chrome-headless-shell-win64', 'chrome-headless-shell.exe')
);
});
});

View File

@ -0,0 +1,91 @@
/**
* 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,
setupTestServer,
getServerUrl,
} from '../utils.js';
import {testChromeHeadlessShellBuildId} from '../versions.js';
describe('chrome-headless-shell CLI', function () {
this.timeout(90000);
setupTestServer();
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}`,
`--base-url=${getServerUrl()}`,
]);
});
it('should download chrome-headless-shell binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`chrome-headless-shell@${testChromeHeadlessShellBuildId}`,
`--path=${tmpDir}`,
'--platform=linux',
`--base-url=${getServerUrl()}`,
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chrome-headless-shell',
`linux-${testChromeHeadlessShellBuildId}`,
'chrome-headless-shell-linux64',
'chrome-headless-shell'
)
)
);
await new CLI(tmpDir, createMockedReadlineInterface('no')).run([
'npx',
'@puppeteer/browsers',
'clear',
`--path=${tmpDir}`,
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chrome-headless-shell',
`linux-${testChromeHeadlessShellBuildId}`,
'chrome-headless-shell-linux64',
'chrome-headless-shell'
)
)
);
});
});

View File

@ -0,0 +1,103 @@
/**
* 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 {
install,
canDownload,
Browser,
BrowserPlatform,
Cache,
} from '../../../lib/cjs/main.js';
import {getServerUrl, setupTestServer} from '../utils.js';
import {testChromeDriverBuildId} from '../versions.js';
/**
* Tests in this spec use real download URLs and unpack live browser archives
* so it requires the network access.
*/
describe('ChromeDriver install', () => {
setupTestServer();
let tmpDir = '/tmp/puppeteer-browsers-test';
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
new Cache(tmpDir).clear();
});
it('should check if a buildId can be downloaded', async () => {
assert.ok(
await canDownload({
cacheDir: tmpDir,
browser: Browser.CHROMEDRIVER,
platform: BrowserPlatform.LINUX,
buildId: testChromeDriverBuildId,
baseUrl: getServerUrl(),
})
);
});
it('should report if a buildId is not downloadable', async () => {
assert.strictEqual(
await canDownload({
cacheDir: tmpDir,
browser: Browser.CHROMEDRIVER,
platform: BrowserPlatform.LINUX,
buildId: 'unknown',
baseUrl: getServerUrl(),
}),
false
);
});
it('should download and unpack the binary', async function () {
this.timeout(60000);
const expectedOutputPath = path.join(
tmpDir,
'chromedriver',
`${BrowserPlatform.LINUX}-${testChromeDriverBuildId}`
);
assert.strictEqual(fs.existsSync(expectedOutputPath), false);
let browser = await install({
cacheDir: tmpDir,
browser: Browser.CHROMEDRIVER,
platform: BrowserPlatform.LINUX,
buildId: testChromeDriverBuildId,
baseUrl: getServerUrl(),
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
// Second iteration should be no-op.
browser = await install({
cacheDir: tmpDir,
browser: Browser.CHROMEDRIVER,
platform: BrowserPlatform.LINUX,
buildId: testChromeDriverBuildId,
baseUrl: getServerUrl(),
});
assert.strictEqual(browser.path, expectedOutputPath);
assert.ok(fs.existsSync(expectedOutputPath));
assert.ok(fs.existsSync(browser.executablePath));
});
});

View File

@ -18,5 +18,6 @@ export const testChromeBuildId = '113.0.5672.0';
export const testChromiumBuildId = '1083080'; export const testChromiumBuildId = '1083080';
// TODO: We can add a Cron job to auto-update on change. // TODO: We can add a Cron job to auto-update on change.
// Firefox keeps only `latest` version of Nightly builds. // Firefox keeps only `latest` version of Nightly builds.
export const testFirefoxBuildId = '117.0a1'; export const testFirefoxBuildId = '118.0a1';
export const testChromeDriverBuildId = '115.0.5763.0'; export const testChromeDriverBuildId = '115.0.5763.0';
export const testChromeHeadlessShellBuildId = '118.0.5950.0';

View File

@ -32,7 +32,11 @@ function getBrowser(str) {
const match = str.match(regex); const match = str.match(regex);
if (match && match[1]) { if (match && match[1]) {
return match[1].toLowerCase(); const lowercased = match[1].toLowerCase();
if (lowercased === 'chromeheadlessshell') {
return 'chrome-headless-shell';
}
return lowercased;
} else { } else {
return null; return null;
} }
@ -42,7 +46,6 @@ const cacheDir = normalize(join('.', 'test', 'cache'));
for (const version of Object.keys(versions)) { for (const version of Object.keys(versions)) {
const browser = getBrowser(version); const browser = getBrowser(version);
if (!browser) { if (!browser) {
continue; continue;
} }