@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.
- package/.gitlab-ci.yml +2 -2
- package/README.md +41 -19
- package/RELEASE_NOTES.md +40 -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 +69 -40
- package/test/docker/Dockerfile +1 -1
- package/test/docker/arm64.Dockerfile +7 -2
- package/test/extension/mv3/manifest.json +2 -1
- 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 -919
package/src/chromium.js
ADDED
|
@@ -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
|
+
}
|