@eyeo/get-browser-binary 0.4.0 → 0.5.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/.eslintrc.json +2 -1
- package/.gitlab-ci.yml +2 -3
- package/README.md +16 -11
- package/RELEASE_NOTES.md +8 -0
- package/package.json +1 -2
- package/src/browsers.js +201 -124
- package/test/.eslintrc.json +2 -1
- package/test/browsers.js +58 -14
- package/test/docker/Dockerfile +1 -12
package/.eslintrc.json
CHANGED
package/.gitlab-ci.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
default:
|
|
2
|
-
image:
|
|
2
|
+
image: registry.gitlab.com/eyeo/docker/get-browser-binary:node16
|
|
3
3
|
interruptible: true
|
|
4
4
|
|
|
5
5
|
stages:
|
|
@@ -37,7 +37,7 @@ test:browsers:linux:
|
|
|
37
37
|
before_script:
|
|
38
38
|
- docker build -f test/docker/Dockerfile -t browsers .
|
|
39
39
|
script:
|
|
40
|
-
- docker run browsers
|
|
40
|
+
- docker run --shm-size=256m browsers
|
|
41
41
|
|
|
42
42
|
test:browsers:windows:
|
|
43
43
|
stage: test
|
|
@@ -51,7 +51,6 @@ test:browsers:windows:
|
|
|
51
51
|
- Start-Process msiexec
|
|
52
52
|
-ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
|
|
53
53
|
- choco upgrade -y nodejs --version 16.10.0
|
|
54
|
-
- npm install -g npm
|
|
55
54
|
- npm install
|
|
56
55
|
script:
|
|
57
56
|
# Running only a subset of Firefox and Opera tests to avoid low OS resources error
|
package/README.md
CHANGED
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
Download specific browser versions for Chromium, Firefox, Edge and Opera, and
|
|
4
4
|
their matching [selenium webdriver](https://www.selenium.dev/selenium/docs/api/javascript/index.html).
|
|
5
5
|
|
|
6
|
-
Note: Edge download is only supported on Linux. On other platforms it is assumed
|
|
7
|
-
to be already installed.
|
|
8
|
-
|
|
9
|
-
Note: Using the `operadriver` on Windows is not supported.
|
|
10
|
-
|
|
11
6
|
## Getting started
|
|
12
7
|
|
|
13
8
|
The sample below shows how to download the latest Chromium and run it using
|
|
@@ -17,12 +12,10 @@ selenium webdriver:
|
|
|
17
12
|
import {BROWSERS} from "@eyeo/get-browser-binary";
|
|
18
13
|
|
|
19
14
|
(async function example() {
|
|
20
|
-
let {
|
|
21
|
-
|
|
22
|
-
let {binary} = await chromium.downloadBinary();
|
|
15
|
+
let {binary} = await BROWSERS.chromium.downloadBinary("latest");
|
|
23
16
|
console.log(`Chromium binary downloaded to ${binary}`);
|
|
24
17
|
|
|
25
|
-
let driver = await chromium.getDriver();
|
|
18
|
+
let driver = await BROWSERS.chromium.getDriver("latest");
|
|
26
19
|
await driver.navigate().to("https://example.com/");
|
|
27
20
|
await driver.quit();
|
|
28
21
|
})();
|
|
@@ -35,6 +28,18 @@ For more information, please refer to the [API documention](https://gitlab.com/e
|
|
|
35
28
|
If you are already on the documentation page, you may find the API contents on
|
|
36
29
|
the right side.
|
|
37
30
|
|
|
31
|
+
### Supported browser versions
|
|
32
|
+
|
|
33
|
+
- Chromium >= 75
|
|
34
|
+
- Firefox >= 60
|
|
35
|
+
- Edge >= 95
|
|
36
|
+
- Opera >= 62
|
|
37
|
+
|
|
38
|
+
Note: Edge download is not supported on Windows. It is assumed to be installed
|
|
39
|
+
because it is the default browser on that platform.
|
|
40
|
+
|
|
41
|
+
Note: Using the `operadriver` on Windows is not supported.
|
|
42
|
+
|
|
38
43
|
## Development
|
|
39
44
|
|
|
40
45
|
### Prerequisites
|
|
@@ -79,13 +84,13 @@ Useful to reproduce the CI environment of the `test:browsers:linux` job:
|
|
|
79
84
|
|
|
80
85
|
```shell
|
|
81
86
|
docker build -f test/docker/Dockerfile -t browsers .
|
|
82
|
-
docker run -it browsers
|
|
87
|
+
docker run --shm-size=256m -it browsers
|
|
83
88
|
```
|
|
84
89
|
|
|
85
90
|
The `grep` option can also be used on Docker via the `TEST_ARGS` parameter:
|
|
86
91
|
|
|
87
92
|
```shell
|
|
88
|
-
docker run -e TEST_ARGS="--grep chromium.*latest" -it browsers
|
|
93
|
+
docker run --shm-size=256m -e TEST_ARGS="--grep chromium.*latest" -it browsers
|
|
89
94
|
```
|
|
90
95
|
|
|
91
96
|
Note: For a full automated run, `opera` tests should not use the interactive
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
# 0.5.0
|
|
2
|
+
|
|
3
|
+
- Enables Edge binary downloads on MacOS (!25)
|
|
4
|
+
- Sets unsupported browser versions (#16)
|
|
5
|
+
- Fixes the Edge repo url (!27)
|
|
6
|
+
- Fixes an issue when downloading Opera (#21)
|
|
7
|
+
- Fixes redundant Opera installations on Linux (!24)
|
|
8
|
+
|
|
1
9
|
# 0.4.0
|
|
2
10
|
|
|
3
11
|
- Adds Opera as a supported browser (#7)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyeo/get-browser-binary",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Download browser binaries and matching webdrivers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"fs-extra": "^10.0.0",
|
|
22
22
|
"geckodriver": "^3.0.2",
|
|
23
23
|
"got": "^11.8.2",
|
|
24
|
-
"msedgedriver": "^91.0.0",
|
|
25
24
|
"selenium-webdriver": "^4.2.0"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
package/src/browsers.js
CHANGED
|
@@ -25,6 +25,7 @@ import got from "got";
|
|
|
25
25
|
import webdriver from "selenium-webdriver";
|
|
26
26
|
import chrome from "selenium-webdriver/chrome.js";
|
|
27
27
|
import firefox from "selenium-webdriver/firefox.js";
|
|
28
|
+
import edge from "selenium-webdriver/edge.js";
|
|
28
29
|
import command from "selenium-webdriver/lib/command.js";
|
|
29
30
|
import extractZip from "extract-zip";
|
|
30
31
|
|
|
@@ -38,10 +39,17 @@ import {download, extractTar, extractDmg, getBrowserVersion, killDriverProcess,
|
|
|
38
39
|
export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
|
|
39
40
|
|
|
40
41
|
let {until, By} = webdriver;
|
|
41
|
-
const ERROR_DOWNLOAD_NOT_SUPPORTED =
|
|
42
|
-
"Downloading this browser is not supported";
|
|
43
42
|
let platform = `${process.platform}-${process.arch}`;
|
|
44
43
|
|
|
44
|
+
function checkVersion(version, minVersion, channels) {
|
|
45
|
+
if (channels.includes(version))
|
|
46
|
+
return;
|
|
47
|
+
|
|
48
|
+
let mainVersion = parseInt(version && version.split(".")[0], 10);
|
|
49
|
+
if (mainVersion < minVersion)
|
|
50
|
+
throw new Error(`Unsupported browser version: ${version}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
/**
|
|
46
54
|
* Base class for browser download functionality. Please see subclasses for
|
|
47
55
|
* browser specific details.
|
|
@@ -60,9 +68,10 @@ class Browser {
|
|
|
60
68
|
* @param {string} version - Either full version number or channel/release.
|
|
61
69
|
* Please find examples on the subclasses.
|
|
62
70
|
* @return {BrowserBinary}
|
|
71
|
+
* @throws {Error} Unsupported browser version.
|
|
63
72
|
*/
|
|
64
73
|
static async downloadBinary(version) {
|
|
65
|
-
|
|
74
|
+
// to be implemented by the subclass
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
/**
|
|
@@ -81,7 +90,7 @@ class Browser {
|
|
|
81
90
|
else {
|
|
82
91
|
({stdout} = await promisify(execFile)(binary, ["--version"]));
|
|
83
92
|
}
|
|
84
|
-
return stdout;
|
|
93
|
+
return stdout.trim();
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
/**
|
|
@@ -107,11 +116,10 @@ class Browser {
|
|
|
107
116
|
* Installs the webdriver matching the browser version and runs the
|
|
108
117
|
* browser. If needed, the browser binary is also installed.
|
|
109
118
|
* @param {string} version - Either full version number or channel/release.
|
|
110
|
-
* Please find examples on the subclasses.
|
|
111
|
-
* no effect.
|
|
119
|
+
* Please find examples on the subclasses.
|
|
112
120
|
* @param {driverOptions?} options - Options to start the browser with.
|
|
113
121
|
* @return {webdriver}
|
|
114
|
-
* @throws {Error} Unsupported
|
|
122
|
+
* @throws {Error} Unsupported browser version.
|
|
115
123
|
*/
|
|
116
124
|
static async getDriver(version, options = {}) {
|
|
117
125
|
// to be implemented by the subclass
|
|
@@ -123,6 +131,7 @@ class Browser {
|
|
|
123
131
|
* @param {webdriver} driver - The driver controlling the browser.
|
|
124
132
|
* @param {string} extensionTitle - Title of the extebsion to be enabled.
|
|
125
133
|
* @return {webdriver}
|
|
134
|
+
* @throws {Error} Unsupported browser version.
|
|
126
135
|
*/
|
|
127
136
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
128
137
|
// Allowing the extension in incognito mode can't happen programmatically:
|
|
@@ -145,28 +154,30 @@ class Chromium extends Browser {
|
|
|
145
154
|
return data.chromium_base_position;
|
|
146
155
|
}
|
|
147
156
|
|
|
148
|
-
static async #
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
os = process.arch == "x64" ? "win64" : "win";
|
|
152
|
-
else if (os == "darwin")
|
|
153
|
-
os = process.arch == "arm64" ? "mac_arm64" : "mac";
|
|
157
|
+
static async #getVersionForChannel(channel) {
|
|
158
|
+
if (channel == "latest")
|
|
159
|
+
channel = "stable";
|
|
154
160
|
|
|
161
|
+
let os = {
|
|
162
|
+
"win32-ia32": "win",
|
|
163
|
+
"win32-x64": "win64",
|
|
164
|
+
"linux-x64": "linux",
|
|
165
|
+
"darwin-x64": "mac",
|
|
166
|
+
"dawrin-arm64": "mac_arm64"
|
|
167
|
+
}[platform];
|
|
155
168
|
let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
|
|
156
169
|
let release = data[0].versions.find(ver => ver.channel == channel);
|
|
157
|
-
let {current_version: version
|
|
170
|
+
let {current_version: version} = release;
|
|
158
171
|
|
|
159
|
-
if (release.true_branch && release.true_branch.includes("_"))
|
|
172
|
+
if (release.true_branch && release.true_branch.includes("_"))
|
|
160
173
|
// A wrong base may be caused by a mini-branch (patched) release
|
|
161
174
|
// In that case, the base is taken from the unpatched version
|
|
162
175
|
version = [...version.split(".").slice(0, 3), "0"].join(".");
|
|
163
|
-
base = await Chromium.#getBranchBasePosition(version);
|
|
164
|
-
}
|
|
165
176
|
|
|
166
|
-
return
|
|
177
|
+
return version;
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
static #
|
|
180
|
+
static #getBinaryPath(dir) {
|
|
170
181
|
switch (process.platform) {
|
|
171
182
|
case "win32":
|
|
172
183
|
return path.join(dir, "chrome-win", "chrome.exe");
|
|
@@ -201,7 +212,7 @@ class Chromium extends Browser {
|
|
|
201
212
|
|
|
202
213
|
try {
|
|
203
214
|
await fs.promises.access(browserDir);
|
|
204
|
-
return {binary: Chromium.#
|
|
215
|
+
return {binary: Chromium.#getBinaryPath(browserDir), revision};
|
|
205
216
|
}
|
|
206
217
|
catch (e) {}
|
|
207
218
|
|
|
@@ -230,7 +241,7 @@ class Chromium extends Browser {
|
|
|
230
241
|
}
|
|
231
242
|
|
|
232
243
|
await extractZip(archive, {dir: browserDir});
|
|
233
|
-
return {binary: Chromium.#
|
|
244
|
+
return {binary: Chromium.#getBinaryPath(browserDir), revision};
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
/**
|
|
@@ -239,15 +250,19 @@ class Chromium extends Browser {
|
|
|
239
250
|
* number (i.e. "77.0.3865.0"). Defaults to "latest".
|
|
240
251
|
* @return {BrowserBinary}
|
|
241
252
|
*/
|
|
242
|
-
static async downloadBinary(version) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
static async downloadBinary(version = "latest") {
|
|
254
|
+
const MIN_VERSION = 75;
|
|
255
|
+
const CHANNELS = ["latest", "beta", "dev"];
|
|
256
|
+
|
|
257
|
+
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
258
|
+
|
|
259
|
+
let versionNumber = CHANNELS.includes(version) ?
|
|
260
|
+
await Chromium.#getVersionForChannel(version) : version;
|
|
261
|
+
|
|
262
|
+
let base = await Chromium.#getBranchBasePosition(versionNumber);
|
|
248
263
|
|
|
249
264
|
let {binary, revision} = await Chromium.#downloadChromium(base);
|
|
250
|
-
return {binary, versionNumber
|
|
265
|
+
return {binary, versionNumber, revision};
|
|
251
266
|
}
|
|
252
267
|
|
|
253
268
|
static async #installDriver(revision, version) {
|
|
@@ -274,9 +289,10 @@ class Chromium extends Browser {
|
|
|
274
289
|
}
|
|
275
290
|
|
|
276
291
|
/** @see Browser.getDriver */
|
|
277
|
-
static async getDriver(version
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
static async getDriver(version = "latest", {
|
|
293
|
+
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
294
|
+
extraArgs = []
|
|
295
|
+
} = {}) {
|
|
280
296
|
let {binary, revision, versionNumber} =
|
|
281
297
|
await Chromium.downloadBinary(version);
|
|
282
298
|
await Chromium.#installDriver(revision, versionNumber);
|
|
@@ -301,14 +317,8 @@ class Chromium extends Browser {
|
|
|
301
317
|
|
|
302
318
|
/** @see Browser.enableExtensionInIncognito */
|
|
303
319
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
304
|
-
let version = await getBrowserVersion(driver);
|
|
305
|
-
// Webdriver capabilities don't include a browser version for Opera
|
|
306
|
-
if (version && version < 75)
|
|
307
|
-
// The UI workaround needs a chromedriver >= 75
|
|
308
|
-
throw new Error(`Only supported on Chromium >= 75. Current version: ${version}`);
|
|
309
|
-
|
|
310
320
|
await driver.navigate().to("chrome://extensions");
|
|
311
|
-
await driver.executeScript(
|
|
321
|
+
await driver.executeScript((...args) => {
|
|
312
322
|
let enable = () => document.querySelector("extensions-manager").shadowRoot
|
|
313
323
|
.querySelector("extensions-detail-view").shadowRoot
|
|
314
324
|
.getElementById("allow-incognito").shadowRoot
|
|
@@ -321,7 +331,7 @@ class Chromium extends Browser {
|
|
|
321
331
|
return new Promise((resolve, reject) => {
|
|
322
332
|
let extensionDetails;
|
|
323
333
|
for (let {shadowRoot} of extensions) {
|
|
324
|
-
if (shadowRoot.getElementById("name").innerHTML !=
|
|
334
|
+
if (shadowRoot.getElementById("name").innerHTML != args[0])
|
|
325
335
|
continue;
|
|
326
336
|
|
|
327
337
|
extensionDetails = shadowRoot.getElementById("detailsButton");
|
|
@@ -332,7 +342,8 @@ class Chromium extends Browser {
|
|
|
332
342
|
|
|
333
343
|
extensionDetails.click();
|
|
334
344
|
setTimeout(() => resolve(enable()), 100);
|
|
335
|
-
})
|
|
345
|
+
});
|
|
346
|
+
}, extensionTitle);
|
|
336
347
|
}
|
|
337
348
|
}
|
|
338
349
|
|
|
@@ -342,13 +353,13 @@ class Chromium extends Browser {
|
|
|
342
353
|
* @extends Browser
|
|
343
354
|
*/
|
|
344
355
|
class Firefox extends Browser {
|
|
345
|
-
static async #
|
|
356
|
+
static async #getVersionForChannel(branch) {
|
|
346
357
|
let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
|
|
347
358
|
return branch == "beta" ?
|
|
348
359
|
data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
|
|
349
360
|
}
|
|
350
361
|
|
|
351
|
-
static #
|
|
362
|
+
static #getBinaryPath(dir) {
|
|
352
363
|
switch (process.platform) {
|
|
353
364
|
case "win32":
|
|
354
365
|
return path.join(dir, "core", "firefox.exe");
|
|
@@ -391,7 +402,7 @@ class Firefox extends Browser {
|
|
|
391
402
|
|
|
392
403
|
try {
|
|
393
404
|
await fs.promises.access(browserDir);
|
|
394
|
-
return Firefox.#
|
|
405
|
+
return Firefox.#getBinaryPath(browserDir);
|
|
395
406
|
}
|
|
396
407
|
catch (e) {}
|
|
397
408
|
|
|
@@ -405,7 +416,7 @@ class Firefox extends Browser {
|
|
|
405
416
|
}
|
|
406
417
|
|
|
407
418
|
await Firefox.#extractFirefoxArchive(archive, browserDir);
|
|
408
|
-
return Firefox.#
|
|
419
|
+
return Firefox.#getBinaryPath(browserDir);
|
|
409
420
|
}
|
|
410
421
|
|
|
411
422
|
/**
|
|
@@ -414,18 +425,24 @@ class Firefox extends Browser {
|
|
|
414
425
|
* number (i.e. "68.0"). Defaults to "latest".
|
|
415
426
|
* @return {BrowserBinary}
|
|
416
427
|
*/
|
|
417
|
-
static async downloadBinary(version) {
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
static async downloadBinary(version = "latest") {
|
|
429
|
+
const MIN_VERSION = 60;
|
|
430
|
+
const CHANNELS = ["latest", "beta"];
|
|
431
|
+
|
|
432
|
+
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
420
433
|
|
|
421
|
-
let
|
|
422
|
-
|
|
434
|
+
let versionNumber = CHANNELS.includes(version) ?
|
|
435
|
+
await Firefox.#getVersionForChannel(version) : version;
|
|
436
|
+
|
|
437
|
+
let binary = await Firefox.#downloadFirefox(versionNumber);
|
|
438
|
+
return {binary, versionNumber};
|
|
423
439
|
}
|
|
424
440
|
|
|
425
441
|
/** @see Browser.getDriver */
|
|
426
|
-
static async getDriver(version
|
|
427
|
-
|
|
428
|
-
|
|
442
|
+
static async getDriver(version = "latest", {
|
|
443
|
+
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
444
|
+
extraArgs = []
|
|
445
|
+
} = {}) {
|
|
429
446
|
let {binary} = await Firefox.downloadBinary(version);
|
|
430
447
|
|
|
431
448
|
let options = new firefox.Options();
|
|
@@ -502,7 +519,7 @@ class Edge extends Browser {
|
|
|
502
519
|
return {versionNumber: version, channel: "stable"};
|
|
503
520
|
|
|
504
521
|
let channel = version == "latest" ? "stable" : version;
|
|
505
|
-
let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}
|
|
522
|
+
let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`);
|
|
506
523
|
let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
|
|
507
524
|
let matches;
|
|
508
525
|
let versionNumbers = [];
|
|
@@ -516,6 +533,28 @@ class Edge extends Browser {
|
|
|
516
533
|
return {versionNumber, channel};
|
|
517
534
|
}
|
|
518
535
|
|
|
536
|
+
static #getDarwinAppName(channel) {
|
|
537
|
+
let extra = channel == "stable" ?
|
|
538
|
+
"" : " " + channel.charAt(0).toUpperCase() + channel.slice(1);
|
|
539
|
+
return `Microsoft Edge${extra}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
static #getBinaryPath(channel = "stable") {
|
|
543
|
+
switch (process.platform) {
|
|
544
|
+
case "win32":
|
|
545
|
+
return "${Env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\" +
|
|
546
|
+
"msedge.exe";
|
|
547
|
+
case "linux":
|
|
548
|
+
return channel == "stable" ?
|
|
549
|
+
"microsoft-edge" : `microsoft-edge-${channel}`;
|
|
550
|
+
case "darwin":
|
|
551
|
+
let appName = Edge.#getDarwinAppName(channel);
|
|
552
|
+
return `${process.env.HOME}/Applications/${appName}.app/Contents/MacOS/${appName}`;
|
|
553
|
+
default:
|
|
554
|
+
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
519
558
|
/**
|
|
520
559
|
* Downloads the browser binary file.
|
|
521
560
|
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
@@ -524,20 +563,36 @@ class Edge extends Browser {
|
|
|
524
563
|
* @return {BrowserBinary}
|
|
525
564
|
*/
|
|
526
565
|
static async downloadBinary(version = "latest") {
|
|
527
|
-
if (process.platform
|
|
528
|
-
|
|
529
|
-
|
|
566
|
+
if (process.platform == "win32")
|
|
567
|
+
// Edge is mandatory on Windows, can't be uninstalled or downgraded
|
|
568
|
+
// https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
|
|
569
|
+
throw new Error("Edge download is not supported in Windows");
|
|
570
|
+
|
|
571
|
+
const MIN_VERSION = 95;
|
|
572
|
+
const CHANNELS = ["latest", "beta", "dev"];
|
|
573
|
+
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
530
574
|
let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
|
|
531
575
|
|
|
532
|
-
let
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
stable: "microsoft-edge",
|
|
537
|
-
beta: "microsoft-edge-beta",
|
|
538
|
-
dev: "microsoft-edge-dev"
|
|
576
|
+
let darwinName = {
|
|
577
|
+
stable: "MicrosoftEdge",
|
|
578
|
+
beta: "MicrosoftEdgeBeta",
|
|
579
|
+
dev: "MicrosoftEdgeDev"
|
|
539
580
|
}[channel];
|
|
581
|
+
let filename = {
|
|
582
|
+
linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
|
|
583
|
+
darwin: `${darwinName}-${versionNumber}.pkg`
|
|
584
|
+
}[process.platform];
|
|
585
|
+
let darwinArch = process.arch == "arm64" ?
|
|
586
|
+
"03adf619-38c6-4249-95ff-4a01c0ffc962" :
|
|
587
|
+
"C1297A47-86C4-4C1F-97FA-950631F94777";
|
|
588
|
+
let downloadUrl = {
|
|
589
|
+
linux: `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
|
|
590
|
+
darwin: `https://officecdnmac.microsoft.com/pr/${darwinArch}/MacAutoupdate/${filename}`
|
|
591
|
+
}[process.platform];
|
|
540
592
|
|
|
593
|
+
let snapshotsDir = path.join(snapshotsBaseDir, "edge");
|
|
594
|
+
let archive = path.join(snapshotsDir, "cache", filename);
|
|
595
|
+
let binary = Edge.#getBinaryPath(channel);
|
|
541
596
|
try {
|
|
542
597
|
if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
|
|
543
598
|
return {binary, versionNumber};
|
|
@@ -545,34 +600,38 @@ class Edge extends Browser {
|
|
|
545
600
|
catch (e) {}
|
|
546
601
|
|
|
547
602
|
try {
|
|
548
|
-
await download(
|
|
549
|
-
`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
|
|
550
|
-
archive
|
|
551
|
-
);
|
|
603
|
+
await download(downloadUrl, archive);
|
|
552
604
|
}
|
|
553
605
|
catch (err) {
|
|
554
606
|
throw new Error(`Edge download failed: ${err}`);
|
|
555
607
|
}
|
|
556
|
-
|
|
608
|
+
|
|
609
|
+
if (process.platform == "linux") {
|
|
610
|
+
await promisify(exec)(`dpkg -i ${archive}`);
|
|
611
|
+
}
|
|
612
|
+
else if (process.platform == "darwin") {
|
|
613
|
+
let appName = Edge.#getDarwinAppName(channel);
|
|
614
|
+
try {
|
|
615
|
+
await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {recursive: true});
|
|
616
|
+
}
|
|
617
|
+
catch (e) {}
|
|
618
|
+
await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
|
|
619
|
+
}
|
|
557
620
|
|
|
558
621
|
return {binary, versionNumber};
|
|
559
622
|
}
|
|
560
623
|
|
|
561
624
|
static async #getInstalledVersionNumber(binary) {
|
|
562
625
|
let installedVersion = await Edge.getInstalledVersion(binary);
|
|
563
|
-
|
|
564
|
-
.
|
|
626
|
+
for (let word of ["beta", "dev", "Beta", "Dev"])
|
|
627
|
+
installedVersion = installedVersion.replace(word, "");
|
|
628
|
+
return installedVersion.trim().replace(/.*\s/, "");
|
|
565
629
|
}
|
|
566
630
|
|
|
567
631
|
static async #installDriver() {
|
|
568
|
-
let binary =
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
darwin: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
572
|
-
linux: "microsoft-edge"
|
|
573
|
-
}[process.platform];
|
|
574
|
-
let version = await Edge.#getInstalledVersionNumber(binary);
|
|
575
|
-
if (!version)
|
|
632
|
+
let binary = Edge.#getBinaryPath();
|
|
633
|
+
let versionNumber = await Edge.#getInstalledVersionNumber(binary);
|
|
634
|
+
if (!versionNumber)
|
|
576
635
|
throw new Error("Edge is not installed");
|
|
577
636
|
|
|
578
637
|
let [zip, driver] = {
|
|
@@ -582,14 +641,11 @@ class Edge extends Browser {
|
|
|
582
641
|
"darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
|
|
583
642
|
"darwin-arm64": ["edgedriver_arm64.zip", "msedgedriver"]
|
|
584
643
|
}[platform];
|
|
585
|
-
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache"
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
let archive = path.join(cacheDir, `${version}-${zip}`);
|
|
591
|
-
|
|
592
|
-
let vSplit = version.split(".");
|
|
644
|
+
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
|
|
645
|
+
`edgedriver-${versionNumber}`);
|
|
646
|
+
let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
|
|
647
|
+
|
|
648
|
+
let vSplit = versionNumber.split(".");
|
|
593
649
|
let lastNum = parseInt(vSplit[3], 10);
|
|
594
650
|
while (lastNum >= 0) {
|
|
595
651
|
try {
|
|
@@ -604,53 +660,50 @@ class Edge extends Browser {
|
|
|
604
660
|
}
|
|
605
661
|
|
|
606
662
|
if (lastNum < 0)
|
|
607
|
-
throw new Error(`msedgedriver was not found for Edge ${
|
|
663
|
+
throw new Error(`msedgedriver was not found for Edge ${versionNumber}`);
|
|
608
664
|
|
|
609
|
-
await extractZip(archive, {dir: cacheDir});
|
|
610
665
|
await killDriverProcess(Edge.#DRIVER);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
await fs.promises.
|
|
614
|
-
path.join(destinationDir, driver));
|
|
666
|
+
let driverPath = path.join(cacheDir, driver);
|
|
667
|
+
try {
|
|
668
|
+
await fs.promises.rm(driverPath, {recursive: true});
|
|
615
669
|
}
|
|
670
|
+
catch (e) {} // file does not exist
|
|
671
|
+
await extractZip(archive, {dir: cacheDir});
|
|
672
|
+
|
|
673
|
+
return driverPath;
|
|
616
674
|
}
|
|
617
675
|
|
|
618
676
|
/** @see Browser.getDriver */
|
|
619
|
-
static async getDriver(version
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
677
|
+
static async getDriver(version = "latest", {
|
|
678
|
+
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
679
|
+
extraArgs = []
|
|
680
|
+
} = {}) {
|
|
681
|
+
if (process.platform == "linux" || process.platform == "darwin")
|
|
623
682
|
await Edge.downloadBinary(version);
|
|
624
683
|
|
|
625
|
-
await Edge.#installDriver();
|
|
684
|
+
let driverPath = await Edge.#installDriver();
|
|
685
|
+
let serviceBuilder = new edge.ServiceBuilder(driverPath);
|
|
626
686
|
|
|
627
|
-
let
|
|
687
|
+
let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
|
|
628
688
|
if (headless)
|
|
629
|
-
|
|
689
|
+
options.headless();
|
|
630
690
|
if (extensionPaths.length > 0)
|
|
631
|
-
|
|
691
|
+
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
632
692
|
if (incognito)
|
|
633
|
-
|
|
693
|
+
options.addArguments("incognito");
|
|
694
|
+
if (insecure)
|
|
695
|
+
options.addArguments("ignore-certificate-errors");
|
|
634
696
|
|
|
635
697
|
let builder = new webdriver.Builder();
|
|
636
698
|
builder.forBrowser("MicrosoftEdge");
|
|
637
|
-
builder.
|
|
638
|
-
|
|
639
|
-
"ms:edgeChromium": true,
|
|
640
|
-
"ms:edgeOptions": {args},
|
|
641
|
-
"acceptInsecureCerts": insecure
|
|
642
|
-
});
|
|
699
|
+
builder.setEdgeOptions(options);
|
|
700
|
+
builder.setEdgeService(serviceBuilder);
|
|
643
701
|
|
|
644
702
|
return builder.build();
|
|
645
703
|
}
|
|
646
704
|
|
|
647
705
|
/** @see Browser.enableExtensionInIncognito */
|
|
648
706
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
649
|
-
let version = await getBrowserVersion(driver);
|
|
650
|
-
if (version < 79)
|
|
651
|
-
// The UI workaround needs a chromium based msedgedriver
|
|
652
|
-
throw new Error(`Only supported on Edge >= 79. Current version: ${version}`);
|
|
653
|
-
|
|
654
707
|
await driver.navigate().to("edge://extensions/");
|
|
655
708
|
for (let elem of await driver.findElements(By.css("[role=listitem]"))) {
|
|
656
709
|
let text = await elem.getAttribute("innerHTML");
|
|
@@ -677,7 +730,7 @@ class Edge extends Browser {
|
|
|
677
730
|
* @extends Browser
|
|
678
731
|
*/
|
|
679
732
|
class Opera extends Browser {
|
|
680
|
-
static async #getVersionForChannel(version) {
|
|
733
|
+
static async #getVersionForChannel(version, platformDir) {
|
|
681
734
|
let channelPath = "opera/desktop";
|
|
682
735
|
let filePrefix = "Opera";
|
|
683
736
|
if (version != "latest")
|
|
@@ -686,12 +739,24 @@ class Opera extends Browser {
|
|
|
686
739
|
let {body} = await got(`https://ftp.opera.com/pub/${channelPath}`);
|
|
687
740
|
let regex = /href="(\d.*)\/"/gm;
|
|
688
741
|
let matches = body.match(regex);
|
|
689
|
-
let versionNumber
|
|
742
|
+
let versionNumber;
|
|
743
|
+
while (matches.length > 0) {
|
|
744
|
+
let result = regex.exec(matches.pop());
|
|
745
|
+
if (!result)
|
|
746
|
+
continue;
|
|
747
|
+
|
|
748
|
+
versionNumber = result[1];
|
|
749
|
+
try {
|
|
750
|
+
await got(`https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}`);
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
catch (e) {}
|
|
754
|
+
}
|
|
690
755
|
|
|
691
756
|
return {versionNumber, channelPath, filePrefix};
|
|
692
757
|
}
|
|
693
758
|
|
|
694
|
-
static #
|
|
759
|
+
static #getBinaryPath(dir) {
|
|
695
760
|
switch (process.platform) {
|
|
696
761
|
case "win32":
|
|
697
762
|
return path.join(dir, "launcher.exe");
|
|
@@ -708,7 +773,7 @@ class Opera extends Browser {
|
|
|
708
773
|
let child = spawn("dpkg", ["-i", archive]);
|
|
709
774
|
|
|
710
775
|
child.stdout.on("data", data => {
|
|
711
|
-
if (data.toString().
|
|
776
|
+
if (data.toString().includes("Do you want to update Opera")) {
|
|
712
777
|
process.stdin.pipe(child.stdin);
|
|
713
778
|
child.stdin.write("no\r\n");
|
|
714
779
|
}
|
|
@@ -761,8 +826,10 @@ class Opera extends Browser {
|
|
|
761
826
|
* @return {BrowserBinary}
|
|
762
827
|
*/
|
|
763
828
|
static async downloadBinary(version = "latest") {
|
|
764
|
-
|
|
765
|
-
|
|
829
|
+
const MIN_VERSION = 62;
|
|
830
|
+
const CHANNELS = ["latest"];
|
|
831
|
+
|
|
832
|
+
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
766
833
|
|
|
767
834
|
let [platformDir, fileSuffix] = {
|
|
768
835
|
"win32-ia32": ["win", "Autoupdate.exe"],
|
|
@@ -772,13 +839,20 @@ class Opera extends Browser {
|
|
|
772
839
|
"dawrin-arm64": ["mac", "Autoupdate_arm64.tar.xz"]
|
|
773
840
|
}[platform];
|
|
774
841
|
|
|
842
|
+
let {versionNumber, channelPath, filePrefix} =
|
|
843
|
+
await Opera.#getVersionForChannel(version, platformDir);
|
|
844
|
+
|
|
775
845
|
let snapshotsDir = path.join(snapshotsBaseDir, "opera");
|
|
776
846
|
let browserDir = path.join(snapshotsDir, `opera-${platform}-${versionNumber}`);
|
|
777
847
|
let filename = `${filePrefix}_${versionNumber}_${fileSuffix}`;
|
|
778
848
|
let archive = path.join(snapshotsDir, "cache", filename);
|
|
779
849
|
|
|
780
|
-
let binary = Opera.#
|
|
850
|
+
let binary = Opera.#getBinaryPath(browserDir);
|
|
781
851
|
try {
|
|
852
|
+
if (process.platform == "linux" &&
|
|
853
|
+
await Opera.getInstalledVersion("opera") == versionNumber)
|
|
854
|
+
return {binary, versionNumber};
|
|
855
|
+
|
|
782
856
|
await fs.promises.access(browserDir);
|
|
783
857
|
return {binary, versionNumber};
|
|
784
858
|
}
|
|
@@ -789,10 +863,13 @@ class Opera extends Browser {
|
|
|
789
863
|
await fs.promises.access(archive);
|
|
790
864
|
}
|
|
791
865
|
catch (e) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
archive
|
|
795
|
-
|
|
866
|
+
let url = `https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}/${filename}`;
|
|
867
|
+
try {
|
|
868
|
+
await download(url, archive);
|
|
869
|
+
}
|
|
870
|
+
catch (err) {
|
|
871
|
+
throw new Error(`Browser download unavailable at ${url}\n${err}`);
|
|
872
|
+
}
|
|
796
873
|
}
|
|
797
874
|
|
|
798
875
|
await Opera.#extractOperaArchive(archive, browserDir, filename);
|
|
@@ -812,7 +889,7 @@ class Opera extends Browser {
|
|
|
812
889
|
versionNumber = versionNumber.split(".")[0];
|
|
813
890
|
|
|
814
891
|
let cacheDir = path.join(snapshotsBaseDir, "opera", "cache",
|
|
815
|
-
`
|
|
892
|
+
`operadriver-${versionNumber}`);
|
|
816
893
|
let archive = path.join(cacheDir, zip);
|
|
817
894
|
|
|
818
895
|
let {body} = await got(`https://github.com/operasoftware/operachromiumdriver/releases?q=Opera+${versionNumber}&expanded=true`);
|
package/test/.eslintrc.json
CHANGED
package/test/browsers.js
CHANGED
|
@@ -25,13 +25,12 @@ import {killDriverProcess} from "../src/utils.js";
|
|
|
25
25
|
// Required to set the driver path on Windows
|
|
26
26
|
import "chromedriver";
|
|
27
27
|
import "geckodriver";
|
|
28
|
-
import "msedgedriver";
|
|
29
28
|
|
|
30
29
|
const VERSIONS = {
|
|
31
|
-
chromium: [
|
|
32
|
-
firefox: [
|
|
33
|
-
edge: [
|
|
34
|
-
opera: [
|
|
30
|
+
chromium: ["latest", "75.0.3770.0", "beta", "dev"],
|
|
31
|
+
firefox: ["latest", "60.0", "beta"],
|
|
32
|
+
edge: ["latest", "95.0.1020.53", "beta", "dev"],
|
|
33
|
+
opera: ["latest", "62.0.3331.66"]
|
|
35
34
|
};
|
|
36
35
|
const TEST_URL = "https://gitlab.com/eyeo/developer-experience/get-browser-binary";
|
|
37
36
|
let extensionPaths = [path.resolve(process.cwd(), "test", "extension")];
|
|
@@ -74,9 +73,27 @@ function normalize(version) {
|
|
|
74
73
|
return normalized.split("b")[0];
|
|
75
74
|
}
|
|
76
75
|
|
|
76
|
+
function getWindowSize(driver) {
|
|
77
|
+
return driver.executeScript(() => {
|
|
78
|
+
return {height: window.innerHeight, width: window.innerWidth};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
expect.extend({
|
|
83
|
+
toMeasureLessThan(small, big) {
|
|
84
|
+
let pass = small.width < big.width || small.height < big.height;
|
|
85
|
+
let message = () =>
|
|
86
|
+
`expected small sizes (w: ${small.width}, h: ${small.height}) ` +
|
|
87
|
+
`to be smaller than big sizes (w: ${big.width}, h: ${big.height})`;
|
|
88
|
+
if (pass)
|
|
89
|
+
return {message, pass: true};
|
|
90
|
+
return {message, pass: false};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
77
94
|
for (let browser of Object.keys(BROWSERS)) {
|
|
78
95
|
describe(`Browser: ${browser}`, function() {
|
|
79
|
-
this.timeout(
|
|
96
|
+
this.timeout(150000);
|
|
80
97
|
|
|
81
98
|
before(async() => {
|
|
82
99
|
try {
|
|
@@ -86,9 +103,10 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
86
103
|
});
|
|
87
104
|
|
|
88
105
|
for (let version of VERSIONS[browser]) {
|
|
89
|
-
describe(`Version: ${version
|
|
106
|
+
describe(`Version: ${version}`, () => {
|
|
90
107
|
let driver = null;
|
|
91
|
-
|
|
108
|
+
|
|
109
|
+
async function quitDriver() {
|
|
92
110
|
if (!driver)
|
|
93
111
|
return;
|
|
94
112
|
|
|
@@ -99,16 +117,21 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
99
117
|
await killDriverProcess("chromedriver");
|
|
100
118
|
else if (browser == "firefox")
|
|
101
119
|
await killDriverProcess("geckodriver");
|
|
102
|
-
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
afterEach(quitDriver);
|
|
103
123
|
|
|
104
124
|
it("downloads", async function() {
|
|
105
|
-
if (browser == "edge" && process.platform
|
|
125
|
+
if (browser == "edge" && process.platform == "win32")
|
|
106
126
|
this.skip();
|
|
107
127
|
|
|
108
128
|
let {binary, versionNumber} =
|
|
109
129
|
await BROWSERS[browser].downloadBinary(version);
|
|
110
|
-
let
|
|
111
|
-
|
|
130
|
+
let browserName =
|
|
131
|
+
browser == "opera" ? /(opera|Opera)/ :
|
|
132
|
+
browser == "edge" ? /(edge|Edge)/ :
|
|
133
|
+
browser;
|
|
134
|
+
expect(binary).toEqual(expect.stringMatching(browserName));
|
|
112
135
|
|
|
113
136
|
let installedVersion =
|
|
114
137
|
await BROWSERS[browser].getInstalledVersion(binary);
|
|
@@ -132,10 +155,21 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
132
155
|
});
|
|
133
156
|
|
|
134
157
|
it("supports extra args", async() => {
|
|
158
|
+
let headless = false;
|
|
135
159
|
let extraArgs = browser == "firefox" ?
|
|
136
160
|
["--devtools"] : ["auto-open-devtools-for-tabs"];
|
|
137
|
-
|
|
161
|
+
|
|
162
|
+
driver = await BROWSERS[browser].getDriver(
|
|
163
|
+
version, {headless, extraArgs});
|
|
164
|
+
await driver.navigate().to(TEST_URL);
|
|
165
|
+
let sizeDevToolsOpen = await getWindowSize(driver);
|
|
166
|
+
await quitDriver();
|
|
167
|
+
|
|
168
|
+
driver = await BROWSERS[browser].getDriver(version, {headless});
|
|
138
169
|
await driver.navigate().to(TEST_URL);
|
|
170
|
+
let sizeDevToolsClosed = await getWindowSize(driver);
|
|
171
|
+
|
|
172
|
+
expect(sizeDevToolsOpen).toMeasureLessThan(sizeDevToolsClosed);
|
|
139
173
|
});
|
|
140
174
|
|
|
141
175
|
it("loads an extension", async() => {
|
|
@@ -147,7 +181,7 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
147
181
|
});
|
|
148
182
|
|
|
149
183
|
it("loads an extension in incognito mode", async function() {
|
|
150
|
-
if (browser == "firefox" && version == "
|
|
184
|
+
if (browser == "firefox" && version == "60.0")
|
|
151
185
|
this.skip();
|
|
152
186
|
|
|
153
187
|
driver = await BROWSERS[browser].getDriver(
|
|
@@ -159,5 +193,15 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
159
193
|
});
|
|
160
194
|
});
|
|
161
195
|
}
|
|
196
|
+
|
|
197
|
+
it("does not download unsupported versions", async function() {
|
|
198
|
+
if (browser == "edge" && process.platform == "win32")
|
|
199
|
+
this.skip();
|
|
200
|
+
|
|
201
|
+
const UNSUPPORTED = "0.0";
|
|
202
|
+
|
|
203
|
+
await expect(BROWSERS[browser].downloadBinary(UNSUPPORTED))
|
|
204
|
+
.rejects.toThrow(`Unsupported browser version: ${UNSUPPORTED}`);
|
|
205
|
+
});
|
|
162
206
|
});
|
|
163
207
|
}
|
package/test/docker/Dockerfile
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
FROM
|
|
2
|
-
|
|
3
|
-
# General packages
|
|
4
|
-
RUN apt-get update && apt-get install -y git procps wget unzip bzip2 gnupg
|
|
5
|
-
# xvfb (headful browser run)
|
|
6
|
-
RUN apt-get install -y libgtk-3-0 libxt6 xvfb libnss3 libxss1
|
|
7
|
-
# General browser dependencies
|
|
8
|
-
RUN apt-get install -y libgconf-2-4 libasound2 libgbm1
|
|
9
|
-
# Edge dependencies
|
|
10
|
-
RUN apt-get install -y fonts-liberation libatomic1 xdg-utils
|
|
11
|
-
# Opera dependencies
|
|
12
|
-
RUN apt-get install -y libcurl4 libgdk-pixbuf2.0-0
|
|
1
|
+
FROM registry.gitlab.com/eyeo/docker/get-browser-binary:node16
|
|
13
2
|
|
|
14
3
|
COPY package*.json get-browser-binary/
|
|
15
4
|
RUN cd get-browser-binary && npm install
|