@eyeo/get-browser-binary 0.12.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.
@@ -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
+ }
package/src/edge.js ADDED
@@ -0,0 +1,308 @@
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 {exec} from "child_process";
20
+ import {promisify} from "util";
21
+ import fs from "fs";
22
+
23
+ import got from "got";
24
+ import webdriver from "selenium-webdriver";
25
+ import edge from "selenium-webdriver/edge.js";
26
+ import extractZip from "extract-zip";
27
+
28
+ import {Browser} from "./browser.js";
29
+ import {download, killDriverProcess, wait, getMajorVersion, checkVersion,
30
+ checkPlatform, errMsg, snapshotsBaseDir, platformArch}
31
+ from "./utils.js";
32
+
33
+ let {By} = webdriver;
34
+ let {platform} = process;
35
+
36
+ /**
37
+ * Browser and webdriver functionality for Edge.
38
+ * @hideconstructor
39
+ * @extends Browser
40
+ */
41
+ export class Edge extends Browser {
42
+ static #CHANNELS = ["latest", "beta", "dev"];
43
+
44
+ static async #getVersionForChannel(version) {
45
+ let channel = "stable";
46
+ if (Edge.#CHANNELS.includes(version) && version != "latest")
47
+ channel = version;
48
+
49
+ let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`;
50
+ let body;
51
+ try {
52
+ ({body} = await got(url));
53
+ }
54
+ catch (err) {
55
+ throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
56
+ }
57
+ let versionNumber;
58
+ if (Edge.#CHANNELS.includes(version)) {
59
+ let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
60
+ let matches;
61
+ let versionNumbers = [];
62
+ while ((matches = regex.exec(body)) !== null)
63
+ versionNumbers.push(matches[2]);
64
+
65
+ let compareVersions = (v1, v2) =>
66
+ getMajorVersion(v1) < getMajorVersion(v2) ? 1 : -1;
67
+ versionNumber = versionNumbers.sort(compareVersions)[0];
68
+ }
69
+ else {
70
+ let split = version.split(".");
71
+ let minorVersion = split.length == 4 ? parseInt(split.pop(), 10) : -1;
72
+ let majorVersion = split.join(".");
73
+ let found;
74
+ while (!found && minorVersion >= 0) {
75
+ versionNumber = `${majorVersion}.${minorVersion}`;
76
+ found = body.includes(versionNumber);
77
+ minorVersion--;
78
+ }
79
+ if (!found)
80
+ throw new Error(`${errMsg.unsupportedVersion}: ${version}`);
81
+ }
82
+
83
+ return {versionNumber, channel};
84
+ }
85
+
86
+ static #darwinApp = "Microsoft Edge";
87
+
88
+ static #getBinaryPath(channel = "stable") {
89
+ switch (platform) {
90
+ case "win32":
91
+ let programFiles = process.env["ProgramFiles(x86)"] ?
92
+ "${Env:ProgramFiles(x86)}" : "${Env:ProgramFiles}";
93
+ return `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
94
+ case "linux":
95
+ return channel == "stable" ?
96
+ "microsoft-edge" : `microsoft-edge-${channel}`;
97
+ case "darwin":
98
+ return `${process.env.HOME}/Applications/${Edge.#darwinApp}.app/Contents/MacOS/${Edge.#darwinApp}`;
99
+ default:
100
+ checkPlatform();
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Installs the browser. On Linux, Edge is installed as a system package,
106
+ * which requires root permissions. On MacOS, Edge is installed as a user
107
+ * app (not as a system app). Installing Edge on Windows is not supported.
108
+ * @param {string} [version=latest] Either "latest", "beta", "dev" or a full
109
+ * version number (i.e. "95.0.1020.40").
110
+ * @param {number} [downloadTimeout=0] Allowed time in ms for the download of
111
+ * install files to complete. When set to 0 there is no time limit.
112
+ * @return {BrowserBinary}
113
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
114
+ * download failed.
115
+ */
116
+ static async installBrowser(version = "latest", downloadTimeout = 0) {
117
+ if (platform == "win32")
118
+ // Edge is mandatory on Windows, can't be uninstalled or downgraded
119
+ // https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
120
+ throw new Error(`${errMsg.unsupportedPlatform}: ${platform}`);
121
+
122
+ if (platform == "darwin" && version != "latest")
123
+ // Only latest Edge is supported on macOS
124
+ throw new Error(`${errMsg.unsupportedVersion}: ${version}. Only "latest" is supported`);
125
+
126
+ const MIN_VERSION = 95;
127
+ checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
128
+ let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
129
+
130
+ let filename = {
131
+ linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
132
+ darwin: `MicrosoftEdge-${versionNumber}.pkg`
133
+ }[platform];
134
+
135
+ let snapshotsDir = path.join(snapshotsBaseDir, "edge");
136
+ let archive = path.join(snapshotsDir, "cache", filename);
137
+ let binary = Edge.#getBinaryPath(channel);
138
+ try {
139
+ if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
140
+ return {binary, versionNumber};
141
+ }
142
+ catch (e) {}
143
+
144
+ let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`;
145
+ try {
146
+ if (platform == "darwin") {
147
+ let caskUrl = "https://formulae.brew.sh/api/cask/microsoft-edge.json";
148
+ let caskJson;
149
+ try {
150
+ caskJson = await got(caskUrl).json();
151
+ }
152
+ catch (err) {
153
+ throw new Error(`${errMsg.browserVersionCheck}: ${caskUrl}\n${err}`);
154
+ }
155
+ ({url} = process.arch == "arm64" ?
156
+ caskJson.variations.arm64_ventura : caskJson);
157
+ }
158
+ await download(url, archive, downloadTimeout);
159
+ }
160
+ catch (err) {
161
+ throw new Error(`${errMsg.browserDownload}: ${url}\n${err}`);
162
+ }
163
+
164
+ if (platform == "linux") {
165
+ await promisify(exec)(`dpkg -i ${archive}`);
166
+ }
167
+ else if (platform == "darwin") {
168
+ await fs.promises.rm(`${process.env.HOME}/Applications/${Edge.#darwinApp}.app`, {force: true, recursive: true});
169
+ await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
170
+ }
171
+
172
+ return {binary, versionNumber};
173
+ }
174
+
175
+ static async #getInstalledVersionNumber(binary) {
176
+ let installedVersion = await Edge.getInstalledVersion(binary);
177
+ for (let word of ["beta", "dev", "Beta", "Dev"])
178
+ installedVersion = installedVersion.replace(word, "");
179
+ return installedVersion.trim().replace(/.*\s/, "");
180
+ }
181
+
182
+ static async #installDriver(binary) {
183
+ async function extractEdgeZip(archive, cacheDir, driverPath) {
184
+ await killDriverProcess("msedgedriver");
185
+ await fs.promises.rm(driverPath, {force: true});
186
+ await extractZip(archive, {dir: cacheDir});
187
+ }
188
+
189
+ let binaryPath = binary || Edge.#getBinaryPath();
190
+ let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
191
+ let [zip, driverBinary] = {
192
+ "win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
193
+ "win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
194
+ "linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
195
+ "darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
196
+ "darwin-arm64": ["edgedriver_mac64_m1.zip", "msedgedriver"]
197
+ }[platformArch];
198
+ let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
199
+ `edgedriver-${versionNumber}`);
200
+ let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
201
+ let driverPath = path.join(cacheDir, driverBinary);
202
+
203
+ try {
204
+ await fs.promises.access(archive);
205
+ await extractEdgeZip(archive, cacheDir, driverPath);
206
+ }
207
+ catch (e) { // zip file is either not cached or corrupted
208
+ let vSplit = versionNumber.split(".");
209
+ let lastNum = parseInt(vSplit[3], 10);
210
+ while (lastNum >= 0) {
211
+ try {
212
+ let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
213
+ await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
214
+ archive);
215
+ break;
216
+ }
217
+ catch (e2) {
218
+ lastNum--;
219
+ }
220
+ }
221
+ if (lastNum < 0)
222
+ throw new Error(`${errMsg.driverDownload}: Edge ${versionNumber}`);
223
+
224
+ await extractEdgeZip(archive, cacheDir, driverPath);
225
+ }
226
+ return driverPath;
227
+ }
228
+
229
+ /** @see Browser.getDriver */
230
+ static async getDriver(version = "latest", {
231
+ headless = true, extensionPaths = [], incognito = false, insecure = false,
232
+ extraArgs = [], customBrowserBinary
233
+ } = {}, downloadTimeout = 0) {
234
+ let binary;
235
+ let versionNumber;
236
+ if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
237
+ ({binary, versionNumber} =
238
+ await Edge.installBrowser(version, downloadTimeout));
239
+ }
240
+ else {
241
+ binary = customBrowserBinary || Edge.#getBinaryPath();
242
+ versionNumber =
243
+ await Edge.#getInstalledVersionNumber(binary);
244
+ }
245
+
246
+ let driverPath = await Edge.#installDriver(binary);
247
+ let serviceBuilder = new edge.ServiceBuilder(driverPath);
248
+
249
+ let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
250
+ if (headless) {
251
+ if (versionNumber && getMajorVersion(versionNumber) >= 114)
252
+ options.addArguments("headless=new");
253
+ else
254
+ options.headless();
255
+ }
256
+ if (extensionPaths.length > 0)
257
+ options.addArguments(`load-extension=${extensionPaths.join(",")}`);
258
+ if (incognito)
259
+ options.addArguments("inprivate");
260
+ if (insecure)
261
+ options.addArguments("ignore-certificate-errors");
262
+ if (platform == "linux")
263
+ options.setEdgeChromiumBinaryPath(`/usr/bin/${binary}`);
264
+
265
+ let builder = new webdriver.Builder();
266
+ builder.forBrowser("MicrosoftEdge");
267
+ builder.setEdgeOptions(options);
268
+ builder.setEdgeService(serviceBuilder);
269
+
270
+ let driver;
271
+ // On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
272
+ // due to low OS resources, that's why building the driver is retried
273
+ await wait(async() => {
274
+ try {
275
+ driver = await builder.build();
276
+ return true;
277
+ }
278
+ catch (err) {
279
+ if (err.name != "SessionNotCreatedError")
280
+ throw err;
281
+ await killDriverProcess("msedgedriver");
282
+ }
283
+ }, 30000, `${errMsg.driverStart}: msedgedriver`, 1000);
284
+
285
+ return driver;
286
+ }
287
+
288
+ /** @see Browser.enableExtensionInIncognito */
289
+ static async enableExtensionInIncognito(driver, extensionTitle) {
290
+ await driver.navigate().to("edge://extensions/");
291
+ for (let elem of await driver.findElements(By.css("[role=listitem]"))) {
292
+ let text = await elem.getAttribute("innerHTML");
293
+ if (!text.includes(extensionTitle))
294
+ continue;
295
+
296
+ for (let button of await elem.findElements(By.css("button"))) {
297
+ text = await elem.getAttribute("innerHTML");
298
+ if (!text.includes("Details"))
299
+ continue;
300
+
301
+ await button.click();
302
+ return await driver.findElement(By.id("itemAllowIncognito")).click();
303
+ }
304
+ throw new Error(`${errMsg.elemNotFound}: Details button`);
305
+ }
306
+ throw new Error(`${errMsg.extensionNotFound}: ${extensionTitle}`);
307
+ }
308
+ }