@eyeo/get-browser-binary 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.gitlab-ci.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  default:
2
- image: registry.gitlab.com/eyeo/docker/get-browser-binary:node16
2
+ image: registry.gitlab.com/eyeo/docker/get-browser-binary:node18
3
3
  interruptible: true
4
4
 
5
5
  stages:
@@ -48,7 +48,7 @@ test:browsers:windows:
48
48
  -OutFile 'MicrosoftEdgeEnterpriseX64.msi'
49
49
  - Start-Process msiexec
50
50
  -ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
51
- - choco upgrade -y nodejs --version 16.10.0
51
+ - choco upgrade -y --no-progress nodejs --version 18.17.1
52
52
  - npm install
53
53
  script:
54
54
  # Running Edge tests only on the preinstalled version
package/README.md CHANGED
@@ -30,20 +30,25 @@ the right side.
30
30
 
31
31
  ### Supported browser versions
32
32
 
33
- - Chromium >= 75
34
- - Firefox >= 60
33
+ - Chromium >= 77 (Chromium ARM >= 92)
34
+ - Firefox >= 68
35
35
  - Edge >= 95
36
36
 
37
37
  Note: Installing Edge is not supported on Windows. It is assumed to be installed
38
38
  because it is the default browser on that platform. On macOS, only the latest
39
39
  Edge version is supported.
40
40
 
41
+ ### Verbose logging
42
+
43
+ Set the `VERBOSE` environment variable to `"true"` to get verbose logging on
44
+ download requests.
45
+
41
46
  ## Development
42
47
 
43
48
  ### Prerequisites
44
49
 
45
- - Node >= 16.10.0
46
- - npm >= 7
50
+ - Node >= 18
51
+ - npm >= 9
47
52
 
48
53
  ### Installing/Updating dependencies
49
54
 
@@ -70,6 +75,8 @@ Running all tests:
70
75
  npm test
71
76
  ```
72
77
 
78
+ ### Options
79
+
73
80
  The `grep` option filters the tests to run with a regular expression. Example:
74
81
 
75
82
  ```shell
@@ -91,6 +98,21 @@ variable to `true`. Example:
91
98
  TEST_KEEP_SNAPSHOTS=true npm test
92
99
  ```
93
100
 
101
+ ### Test server
102
+
103
+ Tests use a local http server, which is managed by the `npm test` command. If
104
+ needed, the test server can also run independently:
105
+
106
+ ```shell
107
+ npm run test-server
108
+ ```
109
+
110
+ Then tests may be executed on a separate session. Example:
111
+
112
+ ```shell
113
+ npm run test-suite -- --grep "chromium.*latest"
114
+ ```
115
+
94
116
  ### Running tests on Docker
95
117
 
96
118
  Useful to reproduce the CI environment of the `test:browsers:linux` job.
@@ -111,22 +133,22 @@ docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest --timeout 10000
111
133
 
112
134
  #### ARM architecture (M1/M2 Apple Silicon)
113
135
 
114
- Chromium (ARM native):
115
-
116
136
  ```shell
117
137
  docker build -f test/docker/arm64.Dockerfile -t browsers-arm .
118
- docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest" -it browsers-arm
138
+ docker run --shm-size=512m -e TEST_ARGS="--grep (chromium|firefox).*latest" -it browsers-arm
119
139
  ```
120
140
 
121
- Firefox (AMD emulation):
141
+ Only latest Chromium and latest Firefox ESR versions are supported. Edge is not
142
+ supported.
143
+
144
+ Regarding Firefox, it may be possible to run other versions using AMD emulation
145
+ (unstable results):
122
146
 
123
147
  ```shell
124
148
  docker build -f test/docker/Dockerfile -t browsers-amd .
125
- docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep firefox.*latest" -it browsers-amd
149
+ docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep firefox.*68.0" -it browsers-amd
126
150
  ```
127
151
 
128
- Edge: Not supported
129
-
130
152
  ## Building the documentation
131
153
 
132
154
  ```shell
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,29 @@
1
+ # 0.14.0
2
+
3
+ - Increases minimum supported browser versions (#64)
4
+ - Sets a minimum supported version for Chromium ARM (#54)
5
+ - Upgrades minimum node version to node 18 (#63)
6
+ - Fixes an occasional issue where the latest Edge on Windows could not find its
7
+ version on Chromium Dash (#61)
8
+ - Enables `dom.promise_rejection_events.enabled` for Firefox 68 (#49)
9
+ - Adds optional verbose logging on download requests (!79)
10
+ - Refactors `src/browsers.js` file (#65)
11
+
12
+ ### Testing
13
+
14
+ - Adds a local http server for tests (#50)
15
+ - Allows running Firefox ARM in the Docker ARM image (!84)
16
+ - Skip other browser tests when install fails (#58)
17
+ - Fixes an issue where Linux Edge tests would fail when not following a very
18
+ specific order (#57)
19
+ - Reduces the size of the Windows CI log (#62)
20
+
21
+ ### Notes for integrators
22
+
23
+ - The minimum broser versions are now Chromium >= 77 (Chromium ARM >= 92) and
24
+ Firefox >= 68. Edge keeps the same version (>= 95)
25
+ - The minimum required node version is now 18
26
+
1
27
  # 0.13.0
2
28
 
3
29
  - Fixes an issue that prevented the webdriver from locating the Edge binary on
package/index.js CHANGED
@@ -15,5 +15,22 @@
15
15
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- export * from "./src/browsers.js";
19
- export {download, takeFullPageScreenshot} from "./src/utils.js";
18
+ import {Chromium} from "./src/chromium.js";
19
+ import {Firefox} from "./src/firefox.js";
20
+ import {Edge} from "./src/edge.js";
21
+
22
+ export {download, takeFullPageScreenshot, snapshotsBaseDir}
23
+ from "./src/utils.js";
24
+
25
+ /**
26
+ * @type {Object}
27
+ * @property {Chromium} chromium Browser and webdriver functionality for
28
+ * Chromium.
29
+ * @property {Firefox} firefox Browser and webdriver functionality for Firefox.
30
+ * @property {Edge} edge Browser and webdriver functionality for Edge.
31
+ */
32
+ export const BROWSERS = {
33
+ chromium: Chromium,
34
+ firefox: Firefox,
35
+ edge: Edge
36
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Install browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,8 +9,8 @@
9
9
  "author": "eyeo GmbH",
10
10
  "license": "GPL-3.0",
11
11
  "engines": {
12
- "node": ">=16.10.0",
13
- "npm": ">=7"
12
+ "node": ">=18",
13
+ "npm": ">=9"
14
14
  },
15
15
  "type": "module",
16
16
  "main": "index.js",
@@ -39,12 +39,15 @@
39
39
  "eslint": "^8.33.0",
40
40
  "eslint-config-eyeo": "^3.2.0",
41
41
  "expect": "^29.4.2",
42
+ "express": "^4.18.2",
42
43
  "jsdoc": "^4.0.0",
43
44
  "mocha": "^10.2.0"
44
45
  },
45
46
  "scripts": {
46
47
  "docs": "jsdoc --readme README.md --destination docs src/*.js",
47
48
  "lint": "eslint --ext js .",
48
- "test": "mocha test/*.js --"
49
+ "test": "node test/runner.js --",
50
+ "test-suite": "mocha --exclude test/start-server.js --exclude test/test-server.js --exclude test/runner.js --",
51
+ "test-server": "node test/start-server.js"
49
52
  }
50
53
  }
package/src/browser.js ADDED
@@ -0,0 +1,134 @@
1
+ /*
2
+ * Copyright (c) 2006-present eyeo GmbH
3
+ *
4
+ * This module is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import {exec, execFile} from "child_process";
19
+ import {promisify} from "util";
20
+
21
+ import {errMsg} from "./utils.js";
22
+
23
+ /**
24
+ * Base class for browser and webdriver functionality. Please see subclasses for
25
+ * browser specific details. All classes can be used statically.
26
+ * @hideconstructor
27
+ */
28
+ export class Browser {
29
+ /**
30
+ * @typedef {Object} BrowserBinary
31
+ * @property {string} binary The path to the browser binary.
32
+ * @property {string} versionNumber The version number of the browser binary.
33
+ */
34
+
35
+ /**
36
+ * Installs the browser. The installation process is detailed on the
37
+ * subclasses.
38
+ * @param {string} [version=latest] Either full version number or
39
+ * channel/release. Please find examples on the subclasses.
40
+ * @param {number} [downloadTimeout=0] Allowed time in ms for the download of
41
+ * install files to complete. When set to 0 there is no time limit.
42
+ * @return {BrowserBinary}
43
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
44
+ * download failed.
45
+ */
46
+ static async installBrowser(version, downloadTimeout = 0) {
47
+ // to be implemented by the subclass
48
+ }
49
+
50
+ /**
51
+ * Gets the installed version returned by the browser binary.
52
+ * @param {string} binary The path to the browser binary.
53
+ * @return {string} Installed browser version.
54
+ * @throws {Error} Browser is not installed.
55
+ */
56
+ static async getInstalledVersion(binary) {
57
+ try {
58
+ let stdout;
59
+ let stderr;
60
+ if (process.platform == "win32") {
61
+ ({stdout, stderr} = await promisify(exec)(
62
+ `(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
63
+ {shell: "powershell.exe"})
64
+ );
65
+ }
66
+ else {
67
+ ({stdout, stderr} = await promisify(execFile)(binary, ["--version"]));
68
+ }
69
+ if (stderr)
70
+ throw new Error(stderr);
71
+
72
+ return stdout.trim();
73
+ }
74
+ catch (err) {
75
+ throw new Error(`${errMsg.browserNotInstalled}.\nBinary path: ${binary}\n${err}`);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @typedef {Object} webdriver
81
+ * @see https://www.selenium.dev/selenium/docs/api/javascript/index.html
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} driverOptions
86
+ * @property {boolean} [headless=true] Run the browser in headless mode,
87
+ * or not. In Chromium >= 111, the
88
+ * {@link https://developer.chrome.com/articles/new-headless/ new headless mode}
89
+ * is used.
90
+ * @property {Array.<string>} [extensionPaths=[]] Loads extensions to the
91
+ * browser.
92
+ * @property {boolean} [incognito=false] Runs the browser in incognito mode,
93
+ * or not.
94
+ * @property {boolean} [insecure=false] Forces the browser to accept insecure
95
+ * certificates, or not.
96
+ * @property {Array.<string>} [extraArgs=[]] Additional arguments to start
97
+ * the browser with.
98
+ * @property {string} [customBrowserBinary] Path to the browser binary to be
99
+ * used, instead of the browser installed by installBrowser(). This option
100
+ * overrides the version parameter in getDriver().
101
+ */
102
+
103
+ /**
104
+ * Installs the webdriver matching the browser version and runs the
105
+ * browser. If needed, the browser binary is also installed.
106
+ * @param {string} [version=latest] Either full version number or
107
+ * channel/release. Please find examples on the subclasses.
108
+ * @param {driverOptions} [options={}] Options to start the browser with.
109
+ * @param {number} [downloadTimeout=0] Allowed time in ms for the download of
110
+ * browser install files to complete. When set to 0 there is no time limit.
111
+ * @return {webdriver}
112
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
113
+ * download failed, Driver download failed, Unable to start driver.
114
+ */
115
+ static async getDriver(version, options = {}, downloadTimeout = 0) {
116
+ // to be implemented by the subclass
117
+ }
118
+
119
+ /**
120
+ * By default, extensions are disabled in incognito mode. This function
121
+ * enables the extension when loaded in incognito.
122
+ * @param {webdriver} driver The driver controlling the browser.
123
+ * @param {string} extensionTitle Title of the extension to be enabled.
124
+ * @return {webdriver}
125
+ * @throws {Error} Unsupported browser version, Extension not found, HTML
126
+ * element not found.
127
+ */
128
+ static async enableExtensionInIncognito(driver, extensionTitle) {
129
+ // Allowing the extension in incognito mode can't happen programmatically:
130
+ // https://stackoverflow.com/questions/57419654
131
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
132
+ // That is done through the UI, to be implemented by the subclass
133
+ }
134
+ }
@@ -0,0 +1,336 @@
1
+ /*
2
+ * Copyright (c) 2006-present eyeo GmbH
3
+ *
4
+ * This module is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import path from "path";
19
+ import fs from "fs";
20
+
21
+ import got from "got";
22
+ import webdriver from "selenium-webdriver";
23
+ import chrome from "selenium-webdriver/chrome.js";
24
+ import extractZip from "extract-zip";
25
+
26
+ import {Browser} from "./browser.js";
27
+ import {download, killDriverProcess, getMajorVersion, checkVersion,
28
+ checkPlatform, errMsg, snapshotsBaseDir, platformArch}
29
+ from "./utils.js";
30
+
31
+ /**
32
+ * Browser and webdriver functionality for Chromium.
33
+ * @hideconstructor
34
+ * @extends Browser
35
+ */
36
+ export class Chromium extends Browser {
37
+ static #CHANNELS = ["latest", "beta", "dev"];
38
+ static #MAX_VERSION_DECREMENTS = 200;
39
+
40
+ static async #getVersionForChannel(channel) {
41
+ if (!Chromium.#CHANNELS.includes(channel))
42
+ return channel;
43
+
44
+ if (channel == "latest")
45
+ channel = "stable";
46
+
47
+ let os = {
48
+ "win32-ia32": "win",
49
+ "win32-x64": "win64",
50
+ "linux-x64": "linux",
51
+ "darwin-x64": "mac",
52
+ "darwin-arm64": "mac_arm64"
53
+ }[platformArch];
54
+ let url = `https://versionhistory.googleapis.com/v1/chrome/platforms/${os}/channels/${channel}/versions/all/releases`;
55
+
56
+ let data;
57
+ try {
58
+ data = await got(url).json();
59
+ }
60
+ catch (err) {
61
+ throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
62
+ }
63
+ let version;
64
+ let fraction;
65
+ for ({version, fraction} of data.releases) {
66
+ // Versions having small fractions may not appear on Chromium Dash
67
+ if (fraction > 0.0025)
68
+ break;
69
+ }
70
+
71
+ return version;
72
+ }
73
+
74
+ static #getBinaryPath(dir) {
75
+ checkPlatform();
76
+ return {
77
+ win32: path.join(dir, "chrome-win", "chrome.exe"),
78
+ linux: path.join(dir, "chrome-linux", "chrome"),
79
+ darwin: path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
80
+ "Chromium")
81
+ }[process.platform];
82
+ }
83
+
84
+ static async #getBase(chromiumVersion) {
85
+ let url;
86
+ let chromiumBase;
87
+ try {
88
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/55
89
+ if (getMajorVersion(chromiumVersion) < 91) {
90
+ url = `https://omahaproxy.appspot.com/deps.json?version=${chromiumVersion}`;
91
+ ({chromium_base_position: chromiumBase} = await got(url).json());
92
+ }
93
+ else {
94
+ url = `https://chromiumdash.appspot.com/fetch_version?version=${chromiumVersion}`;
95
+ ({chromium_main_branch_position: chromiumBase} = await got(url).json());
96
+ }
97
+ }
98
+ catch (err) {
99
+ throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
100
+ }
101
+ return parseInt(chromiumBase, 10);
102
+ }
103
+
104
+ static async #getInstalledBrowserInfo(binary) {
105
+ let installedVersion = await Chromium.getInstalledVersion(binary);
106
+ // Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
107
+ // Windows example: "114.0.5735.0"
108
+ let versionNumber = installedVersion.split(" ")[1] || installedVersion;
109
+ let base = await Chromium.#getBase(versionNumber);
110
+ return {binary, versionNumber, base};
111
+ }
112
+
113
+ /**
114
+ * Installs the browser. The Chromium executable gets extracted in the
115
+ * {@link snapshotsBaseDir} folder, ready to go.
116
+ * @param {string} [version=latest] Either "latest", "beta", "dev" or a full
117
+ * version number (i.e. "77.0.3865.0").
118
+ * @param {number} [downloadTimeout=0] Allowed time in ms for the download of
119
+ * install files to complete. When set to 0 there is no time limit.
120
+ * @return {BrowserBinary}
121
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
122
+ * download failed.
123
+ */
124
+ static async installBrowser(version = "latest", downloadTimeout = 0) {
125
+ const MIN_VERSION = process.arch == "arm64" ? 92 : 77;
126
+
127
+ let binary;
128
+ let versionNumber;
129
+ let base;
130
+
131
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
132
+ if (platformArch == "linux-arm64")
133
+ return await Chromium.#getInstalledBrowserInfo("/usr/bin/chromium");
134
+
135
+ checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
136
+ versionNumber = await Chromium.#getVersionForChannel(version);
137
+
138
+ base = await Chromium.#getBase(versionNumber);
139
+ let startBase = base;
140
+ let [platformDir, fileName] = {
141
+ "win32-ia32": ["Win", "chrome-win.zip"],
142
+ "win32-x64": ["Win_x64", "chrome-win.zip"],
143
+ "linux-x64": ["Linux_x64", "chrome-linux.zip"],
144
+ "darwin-x64": ["Mac", "chrome-mac.zip"],
145
+ "darwin-arm64": ["Mac_Arm", "chrome-mac.zip"]
146
+ }[platformArch];
147
+ let archive;
148
+ let browserDir;
149
+ let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
150
+
151
+ while (true) {
152
+ browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
153
+ binary = Chromium.#getBinaryPath(browserDir);
154
+
155
+ try {
156
+ await fs.promises.access(browserDir);
157
+ return {binary, versionNumber, base};
158
+ }
159
+ catch (e) {}
160
+
161
+ await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
162
+
163
+ archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
164
+ let url = `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`;
165
+ try {
166
+ try {
167
+ await fs.promises.access(archive);
168
+ }
169
+ catch (e) {
170
+ await download(url, archive, downloadTimeout);
171
+ }
172
+ break;
173
+ }
174
+ catch (err) {
175
+ if (err.name == "HTTPError") {
176
+ // Chromium advises decrementing the branch_base_position when no
177
+ // matching build was found. See https://www.chromium.org/getting-involved/download-chromium
178
+ base--;
179
+ if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
180
+ throw new Error(`${errMsg.browserDownload}: Chromium base ${startBase}`);
181
+ }
182
+ else {
183
+ throw new Error(`${errMsg.browserDownload}: ${url}\n${err}`);
184
+ }
185
+ }
186
+ }
187
+ await extractZip(archive, {dir: browserDir});
188
+
189
+ return {binary, versionNumber, base};
190
+ }
191
+
192
+ static async #installDriver(startBase) {
193
+ let [dir, zip, driverBinary] = {
194
+ "win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
195
+ "win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
196
+ "linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
197
+ "linux-arm64": ["", "", "chromedriver"],
198
+ "darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
199
+ "darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
200
+ }[platformArch];
201
+
202
+ let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
203
+ "chromedriver");
204
+ let archive = path.join(cacheDir, `${startBase}-${zip}`);
205
+ try {
206
+ await fs.promises.access(archive);
207
+ await extractZip(archive, {dir: cacheDir});
208
+ }
209
+ catch (e) { // zip file is either not cached or corrupted
210
+ if (platformArch == "linux-arm64") {
211
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
212
+ // It's unclear how electron releases match Chromium versions. Once that
213
+ // is figured out, the link below will depend on the Chromium version.
214
+ // https://stackoverflow.com/questions/38732822/compile-chromedriver-on-arm
215
+ let url = "https://github.com/electron/electron/releases/download/v26.2.0/chromedriver-v26.2.0-linux-arm64.zip";
216
+ try {
217
+ await download(url, archive);
218
+ }
219
+ catch (err) {
220
+ throw new Error(`${errMsg.driverDownload}: ${url}\n${err}`);
221
+ }
222
+ }
223
+ else {
224
+ let base = startBase;
225
+ while (true) {
226
+ let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
227
+ try {
228
+ await download(url, archive);
229
+ break;
230
+ }
231
+ catch (err) {
232
+ if (err.name == "HTTPError") {
233
+ base--;
234
+ archive = path.join(cacheDir, `${base}-${zip}`);
235
+ if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
236
+ throw new Error(`${errMsg.driverDownload}: Chromium base ${startBase}`);
237
+ }
238
+ else {
239
+ throw new Error(`${errMsg.driverDownload}: ${url}\n${err}`);
240
+ }
241
+ }
242
+ }
243
+ }
244
+ await extractZip(archive, {dir: cacheDir});
245
+ }
246
+
247
+ await killDriverProcess("chromedriver");
248
+ let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
249
+ if (platformArch == "linux-arm64")
250
+ driverPath = path.join(cacheDir, driverBinary);
251
+ await fs.promises.rm(driverPath, {force: true});
252
+ await extractZip(archive, {dir: cacheDir});
253
+
254
+ return driverPath;
255
+ }
256
+
257
+ /** @see Browser.getDriver */
258
+ static async getDriver(version = "latest", {
259
+ headless = true, extensionPaths = [], incognito = false, insecure = false,
260
+ extraArgs = [], customBrowserBinary
261
+ } = {}, downloadTimeout = 0) {
262
+ let {binary, versionNumber, base} = customBrowserBinary ?
263
+ await Chromium.#getInstalledBrowserInfo(customBrowserBinary) :
264
+ await Chromium.installBrowser(version, downloadTimeout);
265
+ let driverPath = await Chromium.#installDriver(base);
266
+ let serviceBuilder = new chrome.ServiceBuilder(driverPath);
267
+ let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
268
+ if (extensionPaths.length > 0)
269
+ options.addArguments(`load-extension=${extensionPaths.join(",")}`);
270
+ if (headless) {
271
+ // New headless mode introduced in Chrome 111
272
+ // https://developer.chrome.com/articles/new-headless/
273
+ if (getMajorVersion(versionNumber) >= 111)
274
+ options.addArguments("headless=new");
275
+ else
276
+ options.headless();
277
+ }
278
+ if (insecure)
279
+ options.addArguments("ignore-certificate-errors");
280
+ if (incognito)
281
+ options.addArguments("incognito");
282
+ options.setChromeBinaryPath(binary);
283
+
284
+ let builder = new webdriver.Builder();
285
+ builder.forBrowser("chrome");
286
+ builder.setChromeOptions(options);
287
+ builder.setChromeService(serviceBuilder);
288
+
289
+ return builder.build();
290
+ }
291
+
292
+ /** @see Browser.enableExtensionInIncognito */
293
+ static async enableExtensionInIncognito(driver, extensionTitle) {
294
+ let handle = await driver.getWindowHandle();
295
+
296
+ let version = getMajorVersion(
297
+ (await driver.getCapabilities()).getBrowserVersion());
298
+ if (version >= 115)
299
+ // On Chromium 115 opening chrome://extensions on the default tab causes
300
+ // WebDriverError: disconnected. Switching to a new window as a workaround
301
+ await driver.switchTo().newWindow("window");
302
+
303
+ await driver.navigate().to("chrome://extensions");
304
+ await driver.executeScript((...args) => {
305
+ let enable = () => document.querySelector("extensions-manager").shadowRoot
306
+ .querySelector("extensions-detail-view").shadowRoot
307
+ .getElementById("allow-incognito").shadowRoot
308
+ .getElementById("crToggle").click();
309
+
310
+ let extensions = document.querySelector("extensions-manager").shadowRoot
311
+ .getElementById("items-list").shadowRoot
312
+ .querySelectorAll("extensions-item");
313
+
314
+ return new Promise((resolve, reject) => {
315
+ let extensionDetails;
316
+ for (let {shadowRoot} of extensions) {
317
+ if (shadowRoot.getElementById("name").innerHTML != args[0])
318
+ continue;
319
+
320
+ extensionDetails = shadowRoot.getElementById("detailsButton");
321
+ break;
322
+ }
323
+ if (!extensionDetails)
324
+ reject(`${args[1]}: ${args[0]}`);
325
+
326
+ extensionDetails.click();
327
+ setTimeout(() => resolve(enable()), 100);
328
+ });
329
+ }, extensionTitle, errMsg.extensionNotFound);
330
+ if (version >= 115)
331
+ // Closing the previously opened new window
332
+ await driver.close();
333
+
334
+ await driver.switchTo().window(handle);
335
+ }
336
+ }