diff --git a/docs/browsers-api/browsers.browser.md b/docs/browsers-api/browsers.browser.md
index 53b649aac38..9020e963314 100644
--- a/docs/browsers-api/browsers.browser.md
+++ b/docs/browsers-api/browsers.browser.md
@@ -14,8 +14,9 @@ export declare enum Browser
## Enumeration Members
-| Member | Value | Description |
-| -------- | --------------------------------- | ----------- |
-| CHROME | "chrome"
| |
-| CHROMIUM | "chromium"
| |
-| FIREFOX | "firefox"
| |
+| Member | Value | Description |
+| ------------ | ------------------------------------- | ----------- |
+| CHROME | "chrome"
| |
+| CHROMEDRIVER | "chromedriver"
| |
+| CHROMIUM | "chromium"
| |
+| FIREFOX | "firefox"
| |
diff --git a/packages/browsers/src/browser-data/browser-data.ts b/packages/browsers/src/browser-data/browser-data.ts
index 03f95f5979e..413435453a8 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 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);
diff --git a/packages/browsers/src/browser-data/chromedriver.ts b/packages/browsers/src/browser-data/chromedriver.ts
new file mode 100644
index 00000000000..39894d2e866
--- /dev/null
+++ b/packages/browsers/src/browser-data/chromedriver.ts
@@ -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 {
+ 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);
+ });
+ });
+}
diff --git a/packages/browsers/src/browser-data/types.ts b/packages/browsers/src/browser-data/types.ts
index 5b2f84d8ab9..f88d2ca0982 100644
--- a/packages/browsers/src/browser-data/types.ts
+++ b/packages/browsers/src/browser-data/types.ts
@@ -26,6 +26,7 @@ export enum Browser {
CHROME = 'chrome',
CHROMIUM = 'chromium',
FIREFOX = 'firefox',
+ CHROMEDRIVER = 'chromedriver',
}
/**
diff --git a/packages/browsers/test/src/chromedriver/chromedriver-data.spec.ts b/packages/browsers/test/src/chromedriver/chromedriver-data.spec.ts
new file mode 100644
index 00000000000..fb4134a6631
--- /dev/null
+++ b/packages/browsers/test/src/chromedriver/chromedriver-data.spec.ts
@@ -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'
+ );
+ });
+});
diff --git a/packages/browsers/test/src/chromedriver/cli.spec.ts b/packages/browsers/test/src/chromedriver/cli.spec.ts
new file mode 100644
index 00000000000..52c23d22c23
--- /dev/null
+++ b/packages/browsers/test/src/chromedriver/cli.spec.ts
@@ -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'
+ )
+ )
+ );
+ });
+});
diff --git a/packages/browsers/test/src/chromedriver/install.spec.ts b/packages/browsers/test/src/chromedriver/install.spec.ts
new file mode 100644
index 00000000000..fb725de010e
--- /dev/null
+++ b/packages/browsers/test/src/chromedriver/install.spec.ts
@@ -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));
+ });
+});
diff --git a/packages/browsers/test/src/versions.ts b/packages/browsers/test/src/versions.ts
index b09b8a6de5a..56440c8b187 100644
--- a/packages/browsers/test/src/versions.ts
+++ b/packages/browsers/test/src/versions.ts
@@ -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';