@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 +2 -2
- package/README.md +33 -11
- package/RELEASE_NOTES.md +26 -0
- package/index.js +19 -2
- package/package.json +7 -4
- package/src/browser.js +134 -0
- package/src/chromium.js +336 -0
- package/src/edge.js +308 -0
- package/src/firefox.js +216 -0
- package/src/utils.js +43 -0
- package/test/browsers.js +63 -41
- package/test/docker/Dockerfile +1 -1
- package/test/docker/arm64.Dockerfile +7 -2
- package/test/pages/basic.html +1 -0
- package/test/pages/download-test.txt +1 -0
- package/test/pages/long.html +41 -0
- package/test/runner.js +74 -0
- package/test/start-server.js +20 -0
- package/test/test-server.js +37 -0
- package/test/utils.js +5 -4
- package/src/browsers.js +0 -939
package/src/browsers.js
DELETED
|
@@ -1,939 +0,0 @@
|
|
|
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, execFile} 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 chrome from "selenium-webdriver/chrome.js";
|
|
26
|
-
import firefox from "selenium-webdriver/firefox.js";
|
|
27
|
-
import edge from "selenium-webdriver/edge.js";
|
|
28
|
-
import extractZip from "extract-zip";
|
|
29
|
-
|
|
30
|
-
import {download, extractTar, extractDmg, killDriverProcess, wait}
|
|
31
|
-
from "./utils.js";
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Root folder where browser and webdriver files get downloaded and extracted.
|
|
35
|
-
* @type {string}
|
|
36
|
-
*/
|
|
37
|
-
export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
|
|
38
|
-
|
|
39
|
-
let {until, By} = webdriver;
|
|
40
|
-
let {platform, arch} = process;
|
|
41
|
-
let platformArch = `${platform}-${arch}`;
|
|
42
|
-
|
|
43
|
-
const UNSUPPORTED_VERSION_ERROR = "Unsupported browser version";
|
|
44
|
-
const UNSUPPORTED_PLATFORM_ERROR = "Unsupported platform";
|
|
45
|
-
const DRIVER_DOWNLOAD_ERROR = "Driver download failed";
|
|
46
|
-
const DRIVER_START_ERROR = "Unable to start driver";
|
|
47
|
-
const EXTENSION_NOT_FOUND_ERROR = "Extension not found";
|
|
48
|
-
const BROWSER_DOWNLOAD_ERROR = "Browser download failed";
|
|
49
|
-
const BROWSER_NOT_INSTALLED_ERROR = "Browser is not installed";
|
|
50
|
-
const BROWSER_VERSION_CHECK_ERROR = "Checking the browser version failed";
|
|
51
|
-
const ELEMENT_NOT_FOUND_ERROR = "HTML element not found";
|
|
52
|
-
|
|
53
|
-
function getMajorVersion(versionNumber) {
|
|
54
|
-
let majorVersion = parseInt(versionNumber && versionNumber.split(".")[0], 10);
|
|
55
|
-
if (isNaN(majorVersion))
|
|
56
|
-
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${versionNumber}`);
|
|
57
|
-
|
|
58
|
-
return majorVersion;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function checkVersion(version, minVersion, channels = []) {
|
|
62
|
-
if (channels.includes(version))
|
|
63
|
-
return;
|
|
64
|
-
|
|
65
|
-
if (getMajorVersion(version) < minVersion)
|
|
66
|
-
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function checkPlatform() {
|
|
70
|
-
if (!["win32", "linux", "darwin"].includes(platform))
|
|
71
|
-
throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Base class for browser and webdriver functionality. Please see subclasses for
|
|
76
|
-
* browser specific details. All classes can be used statically.
|
|
77
|
-
* @hideconstructor
|
|
78
|
-
*/
|
|
79
|
-
class Browser {
|
|
80
|
-
/**
|
|
81
|
-
* @typedef {Object} BrowserBinary
|
|
82
|
-
* @property {string} binary The path to the browser binary.
|
|
83
|
-
* @property {string} versionNumber The version number of the browser binary.
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Installs the browser. The installation process is detailed on the
|
|
88
|
-
* subclasses.
|
|
89
|
-
* @param {string} [version=latest] Either full version number or
|
|
90
|
-
* channel/release. Please find examples on the subclasses.
|
|
91
|
-
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
92
|
-
* install files to complete. When set to 0 there is no time limit.
|
|
93
|
-
* @return {BrowserBinary}
|
|
94
|
-
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
95
|
-
* download failed.
|
|
96
|
-
*/
|
|
97
|
-
static async installBrowser(version, downloadTimeout = 0) {
|
|
98
|
-
// to be implemented by the subclass
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Gets the installed version returned by the browser binary.
|
|
103
|
-
* @param {string} binary The path to the browser binary.
|
|
104
|
-
* @return {string} Installed browser version.
|
|
105
|
-
* @throws {Error} Browser is not installed.
|
|
106
|
-
*/
|
|
107
|
-
static async getInstalledVersion(binary) {
|
|
108
|
-
try {
|
|
109
|
-
let stdout;
|
|
110
|
-
let stderr;
|
|
111
|
-
if (platform == "win32") {
|
|
112
|
-
({stdout, stderr} = await promisify(exec)(
|
|
113
|
-
`(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
|
|
114
|
-
{shell: "powershell.exe"})
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
({stdout, stderr} = await promisify(execFile)(binary, ["--version"]));
|
|
119
|
-
}
|
|
120
|
-
if (stderr)
|
|
121
|
-
throw new Error(stderr);
|
|
122
|
-
|
|
123
|
-
return stdout.trim();
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
throw new Error(`${BROWSER_NOT_INSTALLED_ERROR}.\nBinary path: ${binary}\n${err}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @typedef {Object} webdriver
|
|
132
|
-
* @see https://www.selenium.dev/selenium/docs/api/javascript/index.html
|
|
133
|
-
*/
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @typedef {Object} driverOptions
|
|
137
|
-
* @property {boolean} [headless=true] Run the browser in headless mode,
|
|
138
|
-
* or not. In Chromium >= 111, the
|
|
139
|
-
* {@link https://developer.chrome.com/articles/new-headless/ new headless mode}
|
|
140
|
-
* is used.
|
|
141
|
-
* @property {Array.<string>} [extensionPaths=[]] Loads extensions to the
|
|
142
|
-
* browser.
|
|
143
|
-
* @property {boolean} [incognito=false] Runs the browser in incognito mode,
|
|
144
|
-
* or not.
|
|
145
|
-
* @property {boolean} [insecure=false] Forces the browser to accept insecure
|
|
146
|
-
* certificates, or not.
|
|
147
|
-
* @property {Array.<string>} [extraArgs=[]] Additional arguments to start
|
|
148
|
-
* the browser with.
|
|
149
|
-
* @property {string} [customBrowserBinary] Path to the browser binary to be
|
|
150
|
-
* used, instead of the browser installed by installBrowser(). This option
|
|
151
|
-
* overrides the version parameter in getDriver().
|
|
152
|
-
*/
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Installs the webdriver matching the browser version and runs the
|
|
156
|
-
* browser. If needed, the browser binary is also installed.
|
|
157
|
-
* @param {string} [version=latest] Either full version number or
|
|
158
|
-
* channel/release. Please find examples on the subclasses.
|
|
159
|
-
* @param {driverOptions} [options={}] Options to start the browser with.
|
|
160
|
-
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
161
|
-
* browser install files to complete. When set to 0 there is no time limit.
|
|
162
|
-
* @return {webdriver}
|
|
163
|
-
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
164
|
-
* download failed, Driver download failed, Unable to start driver.
|
|
165
|
-
*/
|
|
166
|
-
static async getDriver(version, options = {}, downloadTimeout = 0) {
|
|
167
|
-
// to be implemented by the subclass
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* By default, extensions are disabled in incognito mode. This function
|
|
172
|
-
* enables the extension when loaded in incognito.
|
|
173
|
-
* @param {webdriver} driver The driver controlling the browser.
|
|
174
|
-
* @param {string} extensionTitle Title of the extension to be enabled.
|
|
175
|
-
* @return {webdriver}
|
|
176
|
-
* @throws {Error} Unsupported browser version, Extension not found, HTML
|
|
177
|
-
* element not found.
|
|
178
|
-
*/
|
|
179
|
-
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
180
|
-
// Allowing the extension in incognito mode can't happen programmatically:
|
|
181
|
-
// https://stackoverflow.com/questions/57419654
|
|
182
|
-
// https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
|
|
183
|
-
// That is done through the UI, to be implemented by the subclass
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Browser and webdriver functionality for Chromium.
|
|
189
|
-
* @hideconstructor
|
|
190
|
-
* @extends Browser
|
|
191
|
-
*/
|
|
192
|
-
class Chromium extends Browser {
|
|
193
|
-
static #CHANNELS = ["latest", "beta", "dev"];
|
|
194
|
-
static #MAX_VERSION_DECREMENTS = 200;
|
|
195
|
-
|
|
196
|
-
static async #getVersionForChannel(channel) {
|
|
197
|
-
if (!Chromium.#CHANNELS.includes(channel))
|
|
198
|
-
return channel;
|
|
199
|
-
|
|
200
|
-
if (channel == "latest")
|
|
201
|
-
channel = "stable";
|
|
202
|
-
|
|
203
|
-
let os = {
|
|
204
|
-
"win32-ia32": "win",
|
|
205
|
-
"win32-x64": "win64",
|
|
206
|
-
"linux-x64": "linux",
|
|
207
|
-
"darwin-x64": "mac",
|
|
208
|
-
"darwin-arm64": "mac_arm64"
|
|
209
|
-
}[platformArch];
|
|
210
|
-
let url = `https://versionhistory.googleapis.com/v1/chrome/platforms/${os}/channels/${channel}/versions/all/releases`;
|
|
211
|
-
|
|
212
|
-
let data;
|
|
213
|
-
try {
|
|
214
|
-
data = await got(url).json();
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
|
|
218
|
-
}
|
|
219
|
-
let {version} = data.releases[0];
|
|
220
|
-
|
|
221
|
-
return version;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
static #getBinaryPath(dir) {
|
|
225
|
-
checkPlatform();
|
|
226
|
-
return {
|
|
227
|
-
win32: path.join(dir, "chrome-win", "chrome.exe"),
|
|
228
|
-
linux: path.join(dir, "chrome-linux", "chrome"),
|
|
229
|
-
darwin: path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
|
|
230
|
-
"Chromium")
|
|
231
|
-
}[platform];
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
static async #getBase(chromiumVersion) {
|
|
235
|
-
let url;
|
|
236
|
-
let chromiumBase;
|
|
237
|
-
try {
|
|
238
|
-
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/55
|
|
239
|
-
if (getMajorVersion(chromiumVersion) < 91) {
|
|
240
|
-
url = `https://omahaproxy.appspot.com/deps.json?version=${chromiumVersion}`;
|
|
241
|
-
({chromium_base_position: chromiumBase} = await got(url).json());
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
url = `https://chromiumdash.appspot.com/fetch_version?version=${chromiumVersion}`;
|
|
245
|
-
({chromium_main_branch_position: chromiumBase} = await got(url).json());
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
|
|
250
|
-
}
|
|
251
|
-
return parseInt(chromiumBase, 10);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
static async #getInstalledBrowserInfo(binary) {
|
|
255
|
-
let installedVersion = await Chromium.getInstalledVersion(binary);
|
|
256
|
-
// Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
|
|
257
|
-
// Windows example: "114.0.5735.0"
|
|
258
|
-
let versionNumber = installedVersion.split(" ")[1] || installedVersion;
|
|
259
|
-
let base = await Chromium.#getBase(versionNumber);
|
|
260
|
-
return {binary, versionNumber, base};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Installs the browser. The Chromium executable gets extracted in the
|
|
265
|
-
* {@link snapshotsBaseDir} folder, ready to go.
|
|
266
|
-
* @param {string} [version=latest] Either "latest", "beta", "dev" or a full
|
|
267
|
-
* version number (i.e. "77.0.3865.0").
|
|
268
|
-
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
269
|
-
* install files to complete. When set to 0 there is no time limit.
|
|
270
|
-
* @return {BrowserBinary}
|
|
271
|
-
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
272
|
-
* download failed.
|
|
273
|
-
*/
|
|
274
|
-
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
275
|
-
const MIN_VERSION = 75;
|
|
276
|
-
|
|
277
|
-
let binary;
|
|
278
|
-
let versionNumber;
|
|
279
|
-
let base;
|
|
280
|
-
|
|
281
|
-
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
|
|
282
|
-
if (platformArch == "linux-arm64")
|
|
283
|
-
return await Chromium.#getInstalledBrowserInfo("/usr/bin/chromium");
|
|
284
|
-
|
|
285
|
-
checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
|
|
286
|
-
versionNumber = await Chromium.#getVersionForChannel(version);
|
|
287
|
-
|
|
288
|
-
base = await Chromium.#getBase(versionNumber);
|
|
289
|
-
let startBase = base;
|
|
290
|
-
let [platformDir, fileName] = {
|
|
291
|
-
"win32-ia32": ["Win", "chrome-win.zip"],
|
|
292
|
-
"win32-x64": ["Win_x64", "chrome-win.zip"],
|
|
293
|
-
"linux-x64": ["Linux_x64", "chrome-linux.zip"],
|
|
294
|
-
"darwin-x64": ["Mac", "chrome-mac.zip"],
|
|
295
|
-
"darwin-arm64": ["Mac_Arm", "chrome-mac.zip"]
|
|
296
|
-
}[platformArch];
|
|
297
|
-
let archive;
|
|
298
|
-
let browserDir;
|
|
299
|
-
let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
|
|
300
|
-
|
|
301
|
-
while (true) {
|
|
302
|
-
browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
|
|
303
|
-
binary = Chromium.#getBinaryPath(browserDir);
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
await fs.promises.access(browserDir);
|
|
307
|
-
return {binary, versionNumber, base};
|
|
308
|
-
}
|
|
309
|
-
catch (e) {}
|
|
310
|
-
|
|
311
|
-
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
312
|
-
|
|
313
|
-
archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
|
|
314
|
-
let url = `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`;
|
|
315
|
-
try {
|
|
316
|
-
try {
|
|
317
|
-
await fs.promises.access(archive);
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
await download(url, archive, downloadTimeout);
|
|
321
|
-
}
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
catch (err) {
|
|
325
|
-
if (err.name == "HTTPError") {
|
|
326
|
-
// Chromium advises decrementing the branch_base_position when no
|
|
327
|
-
// matching build was found. See https://www.chromium.org/getting-involved/download-chromium
|
|
328
|
-
base--;
|
|
329
|
-
if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
|
|
330
|
-
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
await extractZip(archive, {dir: browserDir});
|
|
338
|
-
|
|
339
|
-
return {binary, versionNumber, base};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
static async #installDriver(startBase) {
|
|
343
|
-
let [dir, zip, driverBinary] = {
|
|
344
|
-
"win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
345
|
-
"win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
346
|
-
"linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
|
|
347
|
-
"linux-arm64": ["", "", "chromedriver"],
|
|
348
|
-
"darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
|
|
349
|
-
"darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
|
|
350
|
-
}[platformArch];
|
|
351
|
-
|
|
352
|
-
let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
|
|
353
|
-
"chromedriver");
|
|
354
|
-
let archive = path.join(cacheDir, `${startBase}-${zip}`);
|
|
355
|
-
try {
|
|
356
|
-
await fs.promises.access(archive);
|
|
357
|
-
await extractZip(archive, {dir: cacheDir});
|
|
358
|
-
}
|
|
359
|
-
catch (e) { // zip file is either not cached or corrupted
|
|
360
|
-
if (platformArch == "linux-arm64") {
|
|
361
|
-
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
|
|
362
|
-
// It's unclear how electron releases match Chromium versions. Once that
|
|
363
|
-
// is figured out, the link below will depend on the Chromium version.
|
|
364
|
-
// https://stackoverflow.com/questions/38732822/compile-chromedriver-on-arm
|
|
365
|
-
let url = "https://github.com/electron/electron/releases/download/v24.1.2/chromedriver-v24.1.2-linux-arm64.zip";
|
|
366
|
-
try {
|
|
367
|
-
await download(url, archive);
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
let base = startBase;
|
|
375
|
-
while (true) {
|
|
376
|
-
let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
|
|
377
|
-
try {
|
|
378
|
-
await download(url, archive);
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
catch (err) {
|
|
382
|
-
if (err.name == "HTTPError") {
|
|
383
|
-
base--;
|
|
384
|
-
archive = path.join(cacheDir, `${base}-${zip}`);
|
|
385
|
-
if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
|
|
386
|
-
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
await extractZip(archive, {dir: cacheDir});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
await killDriverProcess("chromedriver");
|
|
398
|
-
let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
|
|
399
|
-
if (platformArch == "linux-arm64")
|
|
400
|
-
driverPath = path.join(cacheDir, driverBinary);
|
|
401
|
-
await fs.promises.rm(driverPath, {force: true});
|
|
402
|
-
await extractZip(archive, {dir: cacheDir});
|
|
403
|
-
|
|
404
|
-
return driverPath;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/** @see Browser.getDriver */
|
|
408
|
-
static async getDriver(version = "latest", {
|
|
409
|
-
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
410
|
-
extraArgs = [], customBrowserBinary
|
|
411
|
-
} = {}, downloadTimeout = 0) {
|
|
412
|
-
let {binary, versionNumber, base} = customBrowserBinary ?
|
|
413
|
-
await Chromium.#getInstalledBrowserInfo(customBrowserBinary) :
|
|
414
|
-
await Chromium.installBrowser(version, downloadTimeout);
|
|
415
|
-
let driverPath = await Chromium.#installDriver(base);
|
|
416
|
-
let serviceBuilder = new chrome.ServiceBuilder(driverPath);
|
|
417
|
-
let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
|
|
418
|
-
if (extensionPaths.length > 0)
|
|
419
|
-
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
420
|
-
if (headless) {
|
|
421
|
-
// New headless mode introduced in Chrome 111
|
|
422
|
-
// https://developer.chrome.com/articles/new-headless/
|
|
423
|
-
if (getMajorVersion(versionNumber) >= 111)
|
|
424
|
-
options.addArguments("headless=new");
|
|
425
|
-
else
|
|
426
|
-
options.headless();
|
|
427
|
-
}
|
|
428
|
-
if (insecure)
|
|
429
|
-
options.addArguments("ignore-certificate-errors");
|
|
430
|
-
if (incognito)
|
|
431
|
-
options.addArguments("incognito");
|
|
432
|
-
options.setChromeBinaryPath(binary);
|
|
433
|
-
|
|
434
|
-
let builder = new webdriver.Builder();
|
|
435
|
-
builder.forBrowser("chrome");
|
|
436
|
-
builder.setChromeOptions(options);
|
|
437
|
-
builder.setChromeService(serviceBuilder);
|
|
438
|
-
|
|
439
|
-
return builder.build();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/** @see Browser.enableExtensionInIncognito */
|
|
443
|
-
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
444
|
-
let handle = await driver.getWindowHandle();
|
|
445
|
-
|
|
446
|
-
let version = getMajorVersion(
|
|
447
|
-
(await driver.getCapabilities()).getBrowserVersion());
|
|
448
|
-
if (version >= 115)
|
|
449
|
-
// On Chromium 115 opening chrome://extensions on the default tab causes
|
|
450
|
-
// WebDriverError: disconnected. Switching to a new window as a workaround
|
|
451
|
-
await driver.switchTo().newWindow("window");
|
|
452
|
-
|
|
453
|
-
await driver.navigate().to("chrome://extensions");
|
|
454
|
-
await driver.executeScript((...args) => {
|
|
455
|
-
let enable = () => document.querySelector("extensions-manager").shadowRoot
|
|
456
|
-
.querySelector("extensions-detail-view").shadowRoot
|
|
457
|
-
.getElementById("allow-incognito").shadowRoot
|
|
458
|
-
.getElementById("crToggle").click();
|
|
459
|
-
|
|
460
|
-
let extensions = document.querySelector("extensions-manager").shadowRoot
|
|
461
|
-
.getElementById("items-list").shadowRoot
|
|
462
|
-
.querySelectorAll("extensions-item");
|
|
463
|
-
|
|
464
|
-
return new Promise((resolve, reject) => {
|
|
465
|
-
let extensionDetails;
|
|
466
|
-
for (let {shadowRoot} of extensions) {
|
|
467
|
-
if (shadowRoot.getElementById("name").innerHTML != args[0])
|
|
468
|
-
continue;
|
|
469
|
-
|
|
470
|
-
extensionDetails = shadowRoot.getElementById("detailsButton");
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
if (!extensionDetails)
|
|
474
|
-
reject(`${args[1]}: ${args[0]}`);
|
|
475
|
-
|
|
476
|
-
extensionDetails.click();
|
|
477
|
-
setTimeout(() => resolve(enable()), 100);
|
|
478
|
-
});
|
|
479
|
-
}, extensionTitle, EXTENSION_NOT_FOUND_ERROR);
|
|
480
|
-
if (version >= 115)
|
|
481
|
-
// Closing the previously opened new window
|
|
482
|
-
await driver.close();
|
|
483
|
-
|
|
484
|
-
await driver.switchTo().window(handle);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Browser and webdriver functionality for Firefox.
|
|
490
|
-
* @hideconstructor
|
|
491
|
-
* @extends Browser
|
|
492
|
-
*/
|
|
493
|
-
class Firefox extends Browser {
|
|
494
|
-
static #CHANNELS = ["latest", "beta"];
|
|
495
|
-
|
|
496
|
-
static async #getVersionForChannel(channel) {
|
|
497
|
-
if (!Firefox.#CHANNELS.includes(channel))
|
|
498
|
-
return channel;
|
|
499
|
-
|
|
500
|
-
let url = "https://product-details.mozilla.org/1.0/firefox_versions.json";
|
|
501
|
-
let data;
|
|
502
|
-
try {
|
|
503
|
-
data = await got(url).json();
|
|
504
|
-
}
|
|
505
|
-
catch (err) {
|
|
506
|
-
throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
|
|
507
|
-
}
|
|
508
|
-
return channel == "beta" ?
|
|
509
|
-
data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
static #getBinaryPath(dir) {
|
|
513
|
-
checkPlatform();
|
|
514
|
-
return {
|
|
515
|
-
win32: path.join(dir, "core", "firefox.exe"),
|
|
516
|
-
linux: path.join(dir, "firefox", "firefox"),
|
|
517
|
-
darwin: path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox")
|
|
518
|
-
}[platform];
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
static #extractFirefoxArchive(archive, dir) {
|
|
522
|
-
switch (platform) {
|
|
523
|
-
case "win32":
|
|
524
|
-
return promisify(exec)(`"${archive}" /extractdir=${dir}`);
|
|
525
|
-
case "linux":
|
|
526
|
-
return extractTar(archive, dir);
|
|
527
|
-
case "darwin":
|
|
528
|
-
return extractDmg(archive, dir);
|
|
529
|
-
default:
|
|
530
|
-
checkPlatform();
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Installs the browser. The Firefox executable gets extracted in the
|
|
536
|
-
* {@link snapshotsBaseDir} folder, ready to go.
|
|
537
|
-
* @param {string} [version=latest] Either "latest", "beta" or a full version
|
|
538
|
-
* number (i.e. "68.0").
|
|
539
|
-
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
540
|
-
* install files to complete. When set to 0 there is no time limit.
|
|
541
|
-
* @return {BrowserBinary}
|
|
542
|
-
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
543
|
-
* download failed.
|
|
544
|
-
*/
|
|
545
|
-
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
546
|
-
const MIN_VERSION = 60;
|
|
547
|
-
|
|
548
|
-
checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
|
|
549
|
-
let versionNumber = await Firefox.#getVersionForChannel(version);
|
|
550
|
-
|
|
551
|
-
let [buildPlatform, fileName] = {
|
|
552
|
-
"win32-ia32": ["win32", `Firefox Setup ${versionNumber}.exe`],
|
|
553
|
-
"win32-x64": ["win64", `Firefox Setup ${versionNumber}.exe`],
|
|
554
|
-
"linux-x64": ["linux-x86_64", `firefox-${versionNumber}.tar.bz2`],
|
|
555
|
-
"darwin-x64": ["mac", `Firefox ${versionNumber}.dmg`],
|
|
556
|
-
"darwin-arm64": ["mac", `Firefox ${versionNumber}.dmg`]
|
|
557
|
-
}[platformArch];
|
|
558
|
-
|
|
559
|
-
let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
|
|
560
|
-
let browserDir = path.join(snapshotsDir,
|
|
561
|
-
`firefox-${platformArch}-${versionNumber}`);
|
|
562
|
-
let binary = Firefox.#getBinaryPath(browserDir);
|
|
563
|
-
try {
|
|
564
|
-
await fs.promises.access(browserDir);
|
|
565
|
-
return {binary, versionNumber};
|
|
566
|
-
}
|
|
567
|
-
catch (e) {}
|
|
568
|
-
|
|
569
|
-
let archive = path.join(snapshotsDir, "cache", fileName);
|
|
570
|
-
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
571
|
-
try {
|
|
572
|
-
await fs.promises.access(archive);
|
|
573
|
-
}
|
|
574
|
-
catch (e) {
|
|
575
|
-
let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
|
|
576
|
-
try {
|
|
577
|
-
await download(url, archive, downloadTimeout);
|
|
578
|
-
}
|
|
579
|
-
catch (err) {
|
|
580
|
-
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
await Firefox.#extractFirefoxArchive(archive, browserDir);
|
|
584
|
-
|
|
585
|
-
return {binary, versionNumber};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** @see Browser.getDriver */
|
|
589
|
-
static async getDriver(version = "latest", {
|
|
590
|
-
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
591
|
-
extraArgs = [], customBrowserBinary
|
|
592
|
-
} = {}, downloadTimeout = 0) {
|
|
593
|
-
let binary;
|
|
594
|
-
if (!customBrowserBinary)
|
|
595
|
-
({binary} = await Firefox.installBrowser(version, downloadTimeout));
|
|
596
|
-
|
|
597
|
-
let options = new firefox.Options();
|
|
598
|
-
if (headless)
|
|
599
|
-
options.headless();
|
|
600
|
-
if (incognito)
|
|
601
|
-
options.addArguments("--private");
|
|
602
|
-
if (insecure)
|
|
603
|
-
options.set("acceptInsecureCerts", true);
|
|
604
|
-
if (extraArgs.length > 0)
|
|
605
|
-
options.addArguments(...extraArgs);
|
|
606
|
-
options.setBinary(customBrowserBinary || binary);
|
|
607
|
-
|
|
608
|
-
let driver;
|
|
609
|
-
// The OS may be low on resources, that's why building the driver is retried
|
|
610
|
-
// https://github.com/mozilla/geckodriver/issues/1560
|
|
611
|
-
await wait(async() => {
|
|
612
|
-
try {
|
|
613
|
-
driver = await new webdriver.Builder()
|
|
614
|
-
.forBrowser("firefox")
|
|
615
|
-
.setFirefoxOptions(options)
|
|
616
|
-
.build();
|
|
617
|
-
return true;
|
|
618
|
-
}
|
|
619
|
-
catch (err) {
|
|
620
|
-
if (err.message != "Failed to decode response from marionette")
|
|
621
|
-
throw err;
|
|
622
|
-
await killDriverProcess("geckodriver");
|
|
623
|
-
}
|
|
624
|
-
}, 30000, `${DRIVER_START_ERROR}: geckodriver`, 1000);
|
|
625
|
-
|
|
626
|
-
for (let extensionPath of extensionPaths) {
|
|
627
|
-
let temporary = true; // Parameter not documented on the webdriver docs
|
|
628
|
-
await driver.installAddon(extensionPath, temporary);
|
|
629
|
-
}
|
|
630
|
-
return driver;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/** @see Browser.enableExtensionInIncognito */
|
|
634
|
-
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
635
|
-
let version = (await driver.getCapabilities()).getBrowserVersion();
|
|
636
|
-
// The UI workaround assumes web elements only present on Firefox >= 87
|
|
637
|
-
checkVersion(version, 87);
|
|
638
|
-
|
|
639
|
-
await driver.navigate().to("about:addons");
|
|
640
|
-
await driver.wait(until.elementLocated(By.name("extension")), 1000).click();
|
|
641
|
-
|
|
642
|
-
for (let elem of await driver.findElements(By.className("card addon"))) {
|
|
643
|
-
let text = await elem.getAttribute("innerHTML");
|
|
644
|
-
if (!text.includes(extensionTitle))
|
|
645
|
-
continue;
|
|
646
|
-
|
|
647
|
-
await elem.click();
|
|
648
|
-
return await driver.findElement(By.name("private-browsing")).click();
|
|
649
|
-
}
|
|
650
|
-
throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Browser and webdriver functionality for Edge.
|
|
656
|
-
* @hideconstructor
|
|
657
|
-
* @extends Browser
|
|
658
|
-
*/
|
|
659
|
-
class Edge extends Browser {
|
|
660
|
-
static #CHANNELS = ["latest", "beta", "dev"];
|
|
661
|
-
|
|
662
|
-
static async #getVersionForChannel(version) {
|
|
663
|
-
let channel = "stable";
|
|
664
|
-
if (Edge.#CHANNELS.includes(version) && version != "latest")
|
|
665
|
-
channel = version;
|
|
666
|
-
|
|
667
|
-
let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`;
|
|
668
|
-
let body;
|
|
669
|
-
try {
|
|
670
|
-
({body} = await got(url));
|
|
671
|
-
}
|
|
672
|
-
catch (err) {
|
|
673
|
-
throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
|
|
674
|
-
}
|
|
675
|
-
let versionNumber;
|
|
676
|
-
if (Edge.#CHANNELS.includes(version)) {
|
|
677
|
-
let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
|
|
678
|
-
let matches;
|
|
679
|
-
let versionNumbers = [];
|
|
680
|
-
while ((matches = regex.exec(body)) !== null)
|
|
681
|
-
versionNumbers.push(matches[2]);
|
|
682
|
-
|
|
683
|
-
let compareVersions = (v1, v2) =>
|
|
684
|
-
getMajorVersion(v1) < getMajorVersion(v2) ? 1 : -1;
|
|
685
|
-
versionNumber = versionNumbers.sort(compareVersions)[0];
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
let split = version.split(".");
|
|
689
|
-
let minorVersion = split.length == 4 ? parseInt(split.pop(), 10) : -1;
|
|
690
|
-
let majorVersion = split.join(".");
|
|
691
|
-
let found;
|
|
692
|
-
while (!found && minorVersion >= 0) {
|
|
693
|
-
versionNumber = `${majorVersion}.${minorVersion}`;
|
|
694
|
-
found = body.includes(versionNumber);
|
|
695
|
-
minorVersion--;
|
|
696
|
-
}
|
|
697
|
-
if (!found)
|
|
698
|
-
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
return {versionNumber, channel};
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
static #darwinApp = "Microsoft Edge";
|
|
705
|
-
|
|
706
|
-
static #getBinaryPath(channel = "stable") {
|
|
707
|
-
switch (platform) {
|
|
708
|
-
case "win32":
|
|
709
|
-
let programFiles = process.env["ProgramFiles(x86)"] ?
|
|
710
|
-
"${Env:ProgramFiles(x86)}" : "${Env:ProgramFiles}";
|
|
711
|
-
return `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
|
|
712
|
-
case "linux":
|
|
713
|
-
return channel == "stable" ?
|
|
714
|
-
"microsoft-edge" : `microsoft-edge-${channel}`;
|
|
715
|
-
case "darwin":
|
|
716
|
-
return `${process.env.HOME}/Applications/${Edge.#darwinApp}.app/Contents/MacOS/${Edge.#darwinApp}`;
|
|
717
|
-
default:
|
|
718
|
-
checkPlatform();
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* Installs the browser. On Linux, Edge is installed as a system package,
|
|
724
|
-
* which requires root permissions. On MacOS, Edge is installed as a user
|
|
725
|
-
* app (not as a system app). Installing Edge on Windows is not supported.
|
|
726
|
-
* @param {string} [version=latest] Either "latest", "beta", "dev" or a full
|
|
727
|
-
* version number (i.e. "95.0.1020.40").
|
|
728
|
-
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
729
|
-
* install files to complete. When set to 0 there is no time limit.
|
|
730
|
-
* @return {BrowserBinary}
|
|
731
|
-
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
732
|
-
* download failed.
|
|
733
|
-
*/
|
|
734
|
-
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
735
|
-
if (platform == "win32")
|
|
736
|
-
// Edge is mandatory on Windows, can't be uninstalled or downgraded
|
|
737
|
-
// https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
|
|
738
|
-
throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
|
|
739
|
-
|
|
740
|
-
if (platform == "darwin" && version != "latest")
|
|
741
|
-
// Only latest Edge is supported on macOS
|
|
742
|
-
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}. Only "latest" is supported`);
|
|
743
|
-
|
|
744
|
-
const MIN_VERSION = 95;
|
|
745
|
-
checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
|
|
746
|
-
let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
|
|
747
|
-
|
|
748
|
-
let filename = {
|
|
749
|
-
linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
|
|
750
|
-
darwin: `MicrosoftEdge-${versionNumber}.pkg`
|
|
751
|
-
}[platform];
|
|
752
|
-
|
|
753
|
-
let snapshotsDir = path.join(snapshotsBaseDir, "edge");
|
|
754
|
-
let archive = path.join(snapshotsDir, "cache", filename);
|
|
755
|
-
let binary = Edge.#getBinaryPath(channel);
|
|
756
|
-
try {
|
|
757
|
-
if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
|
|
758
|
-
return {binary, versionNumber};
|
|
759
|
-
}
|
|
760
|
-
catch (e) {}
|
|
761
|
-
|
|
762
|
-
let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`;
|
|
763
|
-
try {
|
|
764
|
-
if (platform == "darwin") {
|
|
765
|
-
let caskUrl = "https://formulae.brew.sh/api/cask/microsoft-edge.json";
|
|
766
|
-
let caskJson;
|
|
767
|
-
try {
|
|
768
|
-
caskJson = await got(caskUrl).json();
|
|
769
|
-
}
|
|
770
|
-
catch (err) {
|
|
771
|
-
throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${caskUrl}\n${err}`);
|
|
772
|
-
}
|
|
773
|
-
({url} = process.arch == "arm64" ?
|
|
774
|
-
caskJson.variations.arm64_ventura : caskJson);
|
|
775
|
-
}
|
|
776
|
-
await download(url, archive, downloadTimeout);
|
|
777
|
-
}
|
|
778
|
-
catch (err) {
|
|
779
|
-
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if (platform == "linux") {
|
|
783
|
-
await promisify(exec)(`dpkg -i ${archive}`);
|
|
784
|
-
}
|
|
785
|
-
else if (platform == "darwin") {
|
|
786
|
-
await fs.promises.rm(`${process.env.HOME}/Applications/${Edge.#darwinApp}.app`, {force: true, recursive: true});
|
|
787
|
-
await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
return {binary, versionNumber};
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
static async #getInstalledVersionNumber(binary) {
|
|
794
|
-
let installedVersion = await Edge.getInstalledVersion(binary);
|
|
795
|
-
for (let word of ["beta", "dev", "Beta", "Dev"])
|
|
796
|
-
installedVersion = installedVersion.replace(word, "");
|
|
797
|
-
return installedVersion.trim().replace(/.*\s/, "");
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
static async #installDriver(binary) {
|
|
801
|
-
async function extractEdgeZip(archive, cacheDir, driverPath) {
|
|
802
|
-
await killDriverProcess("msedgedriver");
|
|
803
|
-
await fs.promises.rm(driverPath, {force: true});
|
|
804
|
-
await extractZip(archive, {dir: cacheDir});
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
let binaryPath = binary || Edge.#getBinaryPath();
|
|
808
|
-
let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
|
|
809
|
-
let [zip, driverBinary] = {
|
|
810
|
-
"win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
|
|
811
|
-
"win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
|
|
812
|
-
"linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
|
|
813
|
-
"darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
|
|
814
|
-
"darwin-arm64": ["edgedriver_mac64_m1.zip", "msedgedriver"]
|
|
815
|
-
}[platformArch];
|
|
816
|
-
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
|
|
817
|
-
`edgedriver-${versionNumber}`);
|
|
818
|
-
let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
|
|
819
|
-
let driverPath = path.join(cacheDir, driverBinary);
|
|
820
|
-
|
|
821
|
-
try {
|
|
822
|
-
await fs.promises.access(archive);
|
|
823
|
-
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
824
|
-
}
|
|
825
|
-
catch (e) { // zip file is either not cached or corrupted
|
|
826
|
-
let vSplit = versionNumber.split(".");
|
|
827
|
-
let lastNum = parseInt(vSplit[3], 10);
|
|
828
|
-
while (lastNum >= 0) {
|
|
829
|
-
try {
|
|
830
|
-
let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
|
|
831
|
-
await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
|
|
832
|
-
archive);
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
catch (e2) {
|
|
836
|
-
lastNum--;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if (lastNum < 0)
|
|
840
|
-
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: Edge ${versionNumber}`);
|
|
841
|
-
|
|
842
|
-
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
843
|
-
}
|
|
844
|
-
return driverPath;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/** @see Browser.getDriver */
|
|
848
|
-
static async getDriver(version = "latest", {
|
|
849
|
-
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
850
|
-
extraArgs = [], customBrowserBinary
|
|
851
|
-
} = {}, downloadTimeout = 0) {
|
|
852
|
-
let binary;
|
|
853
|
-
let versionNumber;
|
|
854
|
-
if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
|
|
855
|
-
({binary, versionNumber} =
|
|
856
|
-
await Edge.installBrowser(version, downloadTimeout));
|
|
857
|
-
}
|
|
858
|
-
else {
|
|
859
|
-
binary = customBrowserBinary || Edge.#getBinaryPath();
|
|
860
|
-
versionNumber =
|
|
861
|
-
await Edge.#getInstalledVersionNumber(binary);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
let driverPath = await Edge.#installDriver(binary);
|
|
865
|
-
let serviceBuilder = new edge.ServiceBuilder(driverPath);
|
|
866
|
-
|
|
867
|
-
let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
|
|
868
|
-
if (headless) {
|
|
869
|
-
if (versionNumber && getMajorVersion(versionNumber) >= 114)
|
|
870
|
-
options.addArguments("headless=new");
|
|
871
|
-
else
|
|
872
|
-
options.headless();
|
|
873
|
-
}
|
|
874
|
-
if (extensionPaths.length > 0)
|
|
875
|
-
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
876
|
-
if (incognito)
|
|
877
|
-
options.addArguments("inprivate");
|
|
878
|
-
if (insecure)
|
|
879
|
-
options.addArguments("ignore-certificate-errors");
|
|
880
|
-
if (platform == "linux")
|
|
881
|
-
options.setEdgeChromiumBinaryPath(`/usr/bin/${binary}`);
|
|
882
|
-
|
|
883
|
-
let builder = new webdriver.Builder();
|
|
884
|
-
builder.forBrowser("MicrosoftEdge");
|
|
885
|
-
builder.setEdgeOptions(options);
|
|
886
|
-
builder.setEdgeService(serviceBuilder);
|
|
887
|
-
|
|
888
|
-
let driver;
|
|
889
|
-
// On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
|
|
890
|
-
// due to low OS resources, that's why building the driver is retried
|
|
891
|
-
await wait(async() => {
|
|
892
|
-
try {
|
|
893
|
-
driver = await builder.build();
|
|
894
|
-
return true;
|
|
895
|
-
}
|
|
896
|
-
catch (err) {
|
|
897
|
-
if (err.name != "SessionNotCreatedError")
|
|
898
|
-
throw err;
|
|
899
|
-
await killDriverProcess("msedgedriver");
|
|
900
|
-
}
|
|
901
|
-
}, 30000, `${DRIVER_START_ERROR}: msedgedriver`, 1000);
|
|
902
|
-
|
|
903
|
-
return driver;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/** @see Browser.enableExtensionInIncognito */
|
|
907
|
-
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
908
|
-
await driver.navigate().to("edge://extensions/");
|
|
909
|
-
for (let elem of await driver.findElements(By.css("[role=listitem]"))) {
|
|
910
|
-
let text = await elem.getAttribute("innerHTML");
|
|
911
|
-
if (!text.includes(extensionTitle))
|
|
912
|
-
continue;
|
|
913
|
-
|
|
914
|
-
for (let button of await elem.findElements(By.css("button"))) {
|
|
915
|
-
text = await elem.getAttribute("innerHTML");
|
|
916
|
-
if (!text.includes("Details"))
|
|
917
|
-
continue;
|
|
918
|
-
|
|
919
|
-
await button.click();
|
|
920
|
-
return await driver.findElement(By.id("itemAllowIncognito")).click();
|
|
921
|
-
}
|
|
922
|
-
throw new Error(`${ELEMENT_NOT_FOUND_ERROR}: Details button`);
|
|
923
|
-
}
|
|
924
|
-
throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* @type {Object}
|
|
930
|
-
* @property {Chromium} chromium Browser and webdriver functionality for
|
|
931
|
-
* Chromium.
|
|
932
|
-
* @property {Firefox} firefox Browser and webdriver functionality for Firefox.
|
|
933
|
-
* @property {Edge} edge Browser and webdriver functionality for Edge.
|
|
934
|
-
*/
|
|
935
|
-
export const BROWSERS = {
|
|
936
|
-
chromium: Chromium,
|
|
937
|
-
firefox: Firefox,
|
|
938
|
-
edge: Edge
|
|
939
|
-
};
|