feat(browsers): support downloading chromedriver (#9990)

Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com>
This commit is contained in:
Alex Rudenko 2023-04-06 18:15:22 +02:00 committed by GitHub
parent c807fe7145
commit ef0fb5d872
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 374 additions and 6 deletions

View File

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

View File

@ -15,6 +15,7 @@
*/
import * as chrome from './chrome.js';
import * as chromedriver from './chromedriver.js';
import * as chromium from './chromium.js';
import * as firefox from './firefox.js';
import {
@ -28,18 +29,21 @@ import {
export {ProfileOptions};
export const downloadUrls = {
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadUrl,
[Browser.CHROME]: chrome.resolveDownloadUrl,
[Browser.CHROMIUM]: chromium.resolveDownloadUrl,
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
};
export const downloadPaths = {
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadPath,
[Browser.CHROME]: chrome.resolveDownloadPath,
[Browser.CHROMIUM]: chromium.resolveDownloadPath,
[Browser.FIREFOX]: firefox.resolveDownloadPath,
};
export const executablePathByBrowser = {
[Browser.CHROMEDRIVER]: chromedriver.relativeExecutablePath,
[Browser.CHROME]: chrome.relativeExecutablePath,
[Browser.CHROMIUM]: chromium.relativeExecutablePath,
[Browser.FIREFOX]: firefox.relativeExecutablePath,
@ -67,6 +71,11 @@ export async function resolveBuildId(
// In CfT beta is the latest version.
return await chrome.resolveBuildId(platform, 'beta');
}
case Browser.CHROMEDRIVER:
switch (tag as BrowserTag) {
case BrowserTag.LATEST:
return await chromedriver.resolveBuildId('latest');
}
case Browser.CHROMIUM:
switch (tag as BrowserTag) {
case BrowserTag.LATEST:
@ -102,9 +111,10 @@ export function resolveSystemExecutablePath(
channel: ChromeReleaseChannel
): string {
switch (browser) {
case Browser.CHROMEDRIVER:
case Browser.FIREFOX:
throw new Error(
'System browser detection is not supported for Firefox yet.'
`System browser detection is not supported for ${browser} yet.`
);
case Browser.CHROME:
return chromium.resolveSystemExecutablePath(platform, channel);

View File

@ -0,0 +1,93 @@
/**
* 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 {httpRequest} from '../httpUtil.js';
import {BrowserPlatform} from './types.js';
function archive(platform: BrowserPlatform): string {
switch (platform) {
case BrowserPlatform.LINUX:
return 'chromedriver_linux64';
case BrowserPlatform.MAC_ARM:
return 'chromedriver_mac_arm64';
case BrowserPlatform.MAC:
return 'chromedriver_mac64';
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return 'chromedriver_win32';
}
}
export function resolveDownloadUrl(
platform: BrowserPlatform,
buildId: string,
baseUrl = 'https://chromedriver.storage.googleapis.com'
): string {
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
}
export function resolveDownloadPath(
platform: BrowserPlatform,
buildId: string
): string[] {
return [buildId, `${archive(platform)}.zip`];
}
export function relativeExecutablePath(
platform: BrowserPlatform,
_buildId: string
): string {
switch (platform) {
case BrowserPlatform.MAC:
case BrowserPlatform.MAC_ARM:
case BrowserPlatform.LINUX:
return 'chromedriver';
case BrowserPlatform.WIN32:
case BrowserPlatform.WIN64:
return 'chromedriver.exe';
}
}
export async function resolveBuildId(
_channel: 'latest' = 'latest'
): Promise<string> {
return new Promise((resolve, reject) => {
const request = httpRequest(
new URL(`https://chromedriver.storage.googleapis.com/LATEST_RELEASE`),
'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);
});
});
}

View File

@ -26,6 +26,7 @@ export enum Browser {
CHROME = 'chrome',
CHROMIUM = 'chromium',
FIREFOX = 'firefox',
CHROMEDRIVER = 'chromedriver',
}
/**

View File

@ -0,0 +1,71 @@
/**
* 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 {BrowserPlatform} from '../../../lib/cjs/browser-data/browser-data.js';
import {
resolveDownloadUrl,
relativeExecutablePath,
} from '../../../lib/cjs/browser-data/chromedriver.js';
describe('ChromeDriver', () => {
it('should resolve download URLs', () => {
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.LINUX, '112.0.5615.49'),
'https://chromedriver.storage.googleapis.com/112.0.5615.49/chromedriver_linux64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC, '112.0.5615.49'),
'https://chromedriver.storage.googleapis.com/112.0.5615.49/chromedriver_mac64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.MAC_ARM, '112.0.5615.49'),
'https://chromedriver.storage.googleapis.com/112.0.5615.49/chromedriver_mac_arm64.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN32, '112.0.5615.49'),
'https://chromedriver.storage.googleapis.com/112.0.5615.49/chromedriver_win32.zip'
);
assert.strictEqual(
resolveDownloadUrl(BrowserPlatform.WIN64, '112.0.5615.49'),
'https://chromedriver.storage.googleapis.com/112.0.5615.49/chromedriver_win32.zip'
);
});
it('should resolve executable paths', () => {
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.LINUX, '12372323'),
'chromedriver'
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC, '12372323'),
'chromedriver'
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.MAC_ARM, '12372323'),
'chromedriver'
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN32, '12372323'),
'chromedriver.exe'
);
assert.strictEqual(
relativeExecutablePath(BrowserPlatform.WIN64, '12372323'),
'chromedriver.exe'
);
});
});

View File

@ -0,0 +1,89 @@
/**
* 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 {testChromeDriverBuildId} from '../versions.js';
describe('ChromeDriver 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 ChromeDriver binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`chromedriver@${testChromeDriverBuildId}`,
`--path=${tmpDir}`,
'--platform=linux',
`--base-url=${getServerUrl()}`,
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chromedriver',
`linux-${testChromeDriverBuildId}`,
'chromedriver'
)
)
);
await new CLI(tmpDir, createMockedReadlineInterface('no')).run([
'npx',
'@puppeteer/browsers',
'clear',
`--path=${tmpDir}`,
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chromedriver',
`linux-${testChromeDriverBuildId}`,
'chromedriver'
)
)
);
});
});

View File

@ -0,0 +1,102 @@
/**
* 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));
});
});

View File

@ -19,3 +19,4 @@ 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 = '113.0a1';
export const testChromeDriverBuildId = '112.0.5615.49';