@eyeo/get-browser-binary 0.6.0 → 0.7.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 -1
- package/.mocharc.json +3 -0
- package/README.md +24 -8
- package/RELEASE_NOTES.md +19 -0
- package/package.json +4 -4
- package/src/browsers.js +323 -210
- package/src/utils.js +0 -5
- package/test/browsers.js +43 -13
- package/test/extension/{background.js → mv2/background.js} +0 -0
- package/test/extension/mv2/index.html +1 -0
- package/test/extension/{manifest.json → mv2/manifest.json} +1 -2
- package/test/extension/mv3/background.js +3 -0
- package/test/extension/mv3/index.html +1 -0
- package/test/extension/mv3/manifest.json +8 -0
- package/test/extension/index.html +0 -1
package/.gitlab-ci.yml
CHANGED
|
@@ -55,10 +55,11 @@ test:browsers:windows:
|
|
|
55
55
|
script:
|
|
56
56
|
# Running only a subset of Firefox tests to avoid low OS resources error
|
|
57
57
|
# https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/2
|
|
58
|
-
- npm test -- --grep "firefox.*
|
|
58
|
+
- npm test -- --grep "firefox.*installs"
|
|
59
59
|
# Running npm v8 on powershell has issues when the grep value contains the
|
|
60
60
|
# pipe (|) literal. Storing that string as a verbatim string (single quotes)
|
|
61
61
|
# and then sorrounding it with four double quotes does the trick.
|
|
62
|
+
# https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/29
|
|
62
63
|
- $full_tests = '(chromium|edge.*latest)'
|
|
63
64
|
- npm test -- --grep """"$full_tests""""
|
|
64
65
|
tags:
|
package/.mocharc.json
ADDED
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# get-browser-binary
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install specific browser versions for Chromium, Firefox and Edge, and their
|
|
4
4
|
matching [selenium webdriver](https://www.selenium.dev/selenium/docs/api/javascript/index.html).
|
|
5
5
|
|
|
6
6
|
## Getting started
|
|
7
7
|
|
|
8
|
-
The sample below shows how to
|
|
8
|
+
The sample below shows how to install the latest Chromium and run it using
|
|
9
9
|
selenium webdriver:
|
|
10
10
|
|
|
11
11
|
```javascript
|
|
12
12
|
import {BROWSERS} from "@eyeo/get-browser-binary";
|
|
13
13
|
|
|
14
14
|
(async function example() {
|
|
15
|
-
let {binary} = await BROWSERS.chromium.
|
|
16
|
-
console.log(`Chromium
|
|
15
|
+
let {binary} = await BROWSERS.chromium.installBrowser("latest");
|
|
16
|
+
console.log(`Chromium executable location: ${binary}`);
|
|
17
17
|
|
|
18
18
|
let driver = await BROWSERS.chromium.getDriver("latest");
|
|
19
19
|
await driver.navigate().to("https://example.com/");
|
|
@@ -34,7 +34,7 @@ the right side.
|
|
|
34
34
|
- Firefox >= 60
|
|
35
35
|
- Edge >= 95
|
|
36
36
|
|
|
37
|
-
Note: Edge
|
|
37
|
+
Note: Installing Edge is not supported on Windows. It is assumed to be installed
|
|
38
38
|
because it is the default browser on that platform.
|
|
39
39
|
|
|
40
40
|
## Development
|
|
@@ -52,7 +52,7 @@ npm install
|
|
|
52
52
|
|
|
53
53
|
### Folders to ignore / cache
|
|
54
54
|
|
|
55
|
-
All browser and webdriver files will be
|
|
55
|
+
All browser and webdriver files will be extracted to the `./browser-snapshots`
|
|
56
56
|
folder, which probably makes sense to be ignored (for instance, by adding it to
|
|
57
57
|
`.gitignore`).
|
|
58
58
|
|
|
@@ -75,6 +75,13 @@ The `grep` option filters the tests to run with a regular expression. Example:
|
|
|
75
75
|
npm test -- --grep "chromium.*latest"
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
The `timeout` option overrides the timeout defined by `.mocharc.json`.
|
|
79
|
+
Increasing the timeout may be useful on slow connection environments:
|
|
80
|
+
|
|
81
|
+
```shell
|
|
82
|
+
npm test -- --timeout <ms>
|
|
83
|
+
```
|
|
84
|
+
|
|
78
85
|
### Running tests on Docker
|
|
79
86
|
|
|
80
87
|
Useful to reproduce the CI environment of the `test:browsers:linux` job:
|
|
@@ -84,10 +91,19 @@ docker build -f test/docker/Dockerfile -t browsers .
|
|
|
84
91
|
docker run --shm-size=256m -it browsers
|
|
85
92
|
```
|
|
86
93
|
|
|
87
|
-
The `grep`
|
|
94
|
+
The `grep` and `timeout` options can also be used on Docker via the `TEST_ARGS`
|
|
95
|
+
parameter:
|
|
96
|
+
|
|
97
|
+
```shell
|
|
98
|
+
docker run --shm-size=256m -e TEST_ARGS="--grep chromium.*latest --timeout 100000" -it browsers
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
By default, tests delete the `./browser-snapshots` before each `Browser` suite
|
|
102
|
+
runs. To change that behavior you may set the `TEST_KEEP_SNAPSHOTS` environment
|
|
103
|
+
variable to `true`. Example:
|
|
88
104
|
|
|
89
105
|
```shell
|
|
90
|
-
|
|
106
|
+
TEST_KEEP_SNAPSHOTS=true npm test
|
|
91
107
|
```
|
|
92
108
|
|
|
93
109
|
## Building the documentation
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# 0.7.0
|
|
2
|
+
|
|
3
|
+
- Implement takeFullPageScreenshot (#32)
|
|
4
|
+
- Unify error messages (#22)
|
|
5
|
+
- Document when a browser binary is downloaded or installed (#30)
|
|
6
|
+
- Use cached driver binary if it already exists (#20)
|
|
7
|
+
- Remove chromedriver dependency (#33)
|
|
8
|
+
- Fallback mechanism on minor browser versions (Edge) (#27)
|
|
9
|
+
|
|
10
|
+
### Testing
|
|
11
|
+
|
|
12
|
+
- Use a manifest V3 extension on latest chromium-based browsers tests (#31)
|
|
13
|
+
- Allow test run to not delete browser snapshots (#28)
|
|
14
|
+
- Add a timeout option to run the tests (#24)
|
|
15
|
+
|
|
16
|
+
### Notes for integrators
|
|
17
|
+
|
|
18
|
+
- The `downloadBinary` function has been renamed to `installBrowser` (!38).
|
|
19
|
+
|
|
1
20
|
# 0.6.0
|
|
2
21
|
|
|
3
22
|
- Removes Opera support (#26)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyeo/get-browser-binary",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Install browser binaries and matching webdrivers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://gitlab.com/eyeo/developer-experience/get-browser-binary"
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "index.js",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"chromedriver": "^90.0.1",
|
|
19
18
|
"dmg": "^0.1.0",
|
|
20
19
|
"extract-zip": "^2.0.1",
|
|
21
20
|
"geckodriver": "^3.0.2",
|
|
22
21
|
"got": "^11.8.2",
|
|
23
|
-
"
|
|
22
|
+
"jimp": "^0.16.2",
|
|
23
|
+
"selenium-webdriver": "^4.7.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"eslint": "^8.17.0",
|
package/src/browsers.js
CHANGED
|
@@ -27,31 +27,47 @@ import firefox from "selenium-webdriver/firefox.js";
|
|
|
27
27
|
import edge from "selenium-webdriver/edge.js";
|
|
28
28
|
import command from "selenium-webdriver/lib/command.js";
|
|
29
29
|
import extractZip from "extract-zip";
|
|
30
|
+
import Jimp from "jimp";
|
|
30
31
|
|
|
31
|
-
import {download, extractTar, extractDmg,
|
|
32
|
-
|
|
32
|
+
import {download, extractTar, extractDmg, killDriverProcess, wait}
|
|
33
|
+
from "./utils.js";
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Root folder where browser and webdriver
|
|
36
|
+
* Root folder where browser and webdriver files get downloaded and extracted.
|
|
36
37
|
* @type {string}
|
|
37
38
|
*/
|
|
38
39
|
export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
|
|
39
40
|
|
|
40
41
|
let {until, By} = webdriver;
|
|
41
|
-
let platform =
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
let {platform, arch} = process;
|
|
43
|
+
let platformArch = `${platform}-${arch}`;
|
|
44
|
+
|
|
45
|
+
const UNSUPPORTED_VERSION_ERROR = "Unsupported browser version";
|
|
46
|
+
const UNSUPPORTED_PLATFORM_ERROR = "Unsupported platform";
|
|
47
|
+
const DRIVER_DOWNLOAD_ERROR = "Driver download failed";
|
|
48
|
+
const DRIVER_START_ERROR = "Unable to start driver";
|
|
49
|
+
const EXTENSION_NOT_FOUND_ERROR = "Extension not found";
|
|
50
|
+
const BROWSER_DOWNLOAD_ERROR = "Browser download failed";
|
|
51
|
+
const BROWSER_NOT_INSTALLED_ERROR = "Browser is not installed";
|
|
52
|
+
const ELEMENT_NOT_FOUND_ERROR = "HTML element not found";
|
|
53
|
+
|
|
54
|
+
function checkVersion(version, minVersion, channels = []) {
|
|
44
55
|
if (channels.includes(version))
|
|
45
56
|
return;
|
|
46
57
|
|
|
47
58
|
let mainVersion = parseInt(version && version.split(".")[0], 10);
|
|
48
59
|
if (isNaN(mainVersion) || mainVersion < minVersion)
|
|
49
|
-
throw new Error(
|
|
60
|
+
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkPlatform() {
|
|
64
|
+
if (!["win32", "linux", "darwin"].includes(platform))
|
|
65
|
+
throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
/**
|
|
53
|
-
* Base class for browser
|
|
54
|
-
*
|
|
69
|
+
* Base class for browser and webdriver functionality. Please see subclasses for
|
|
70
|
+
* browser specific details. All classes can be used statically.
|
|
55
71
|
* @hideconstructor
|
|
56
72
|
*/
|
|
57
73
|
class Browser {
|
|
@@ -63,13 +79,15 @@ class Browser {
|
|
|
63
79
|
*/
|
|
64
80
|
|
|
65
81
|
/**
|
|
66
|
-
*
|
|
82
|
+
* Installs the browser. The installation process is detailed on the
|
|
83
|
+
* subclasses.
|
|
67
84
|
* @param {string} version - Either full version number or channel/release.
|
|
68
85
|
* Please find examples on the subclasses.
|
|
69
86
|
* @return {BrowserBinary}
|
|
70
|
-
* @throws {Error} Unsupported browser version
|
|
87
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
88
|
+
* download failed.
|
|
71
89
|
*/
|
|
72
|
-
static async
|
|
90
|
+
static async installBrowser(version) {
|
|
73
91
|
// to be implemented by the subclass
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -80,7 +98,7 @@ class Browser {
|
|
|
80
98
|
*/
|
|
81
99
|
static async getInstalledVersion(binary) {
|
|
82
100
|
let stdout;
|
|
83
|
-
if (
|
|
101
|
+
if (platform == "win32") {
|
|
84
102
|
({stdout} = await promisify(exec)(
|
|
85
103
|
`(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
|
|
86
104
|
{shell: "powershell.exe"})
|
|
@@ -118,7 +136,8 @@ class Browser {
|
|
|
118
136
|
* Please find examples on the subclasses.
|
|
119
137
|
* @param {driverOptions?} options - Options to start the browser with.
|
|
120
138
|
* @return {webdriver}
|
|
121
|
-
* @throws {Error} Unsupported browser version
|
|
139
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
140
|
+
* download failed, Driver download failed, Unable to start driver.
|
|
122
141
|
*/
|
|
123
142
|
static async getDriver(version, options = {}) {
|
|
124
143
|
// to be implemented by the subclass
|
|
@@ -130,7 +149,8 @@ class Browser {
|
|
|
130
149
|
* @param {webdriver} driver - The driver controlling the browser.
|
|
131
150
|
* @param {string} extensionTitle - Title of the extebsion to be enabled.
|
|
132
151
|
* @return {webdriver}
|
|
133
|
-
* @throws {Error} Unsupported browser version
|
|
152
|
+
* @throws {Error} Unsupported browser version, Extension not found, HTML
|
|
153
|
+
* element not found.
|
|
134
154
|
*/
|
|
135
155
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
136
156
|
// Allowing the extension in incognito mode can't happen programmatically:
|
|
@@ -138,22 +158,80 @@ class Browser {
|
|
|
138
158
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
|
|
139
159
|
// That is done through the UI, to be implemented by the subclass
|
|
140
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @typedef {Object} Jimp
|
|
164
|
+
* @see https://github.com/oliver-moran/jimp/tree/master/packages/jimp
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Takes a screenshot of the full page by scrolling from top to bottom.
|
|
169
|
+
* @param {webdriver} driver - The driver controlling the browser.
|
|
170
|
+
* @property {boolean} hideScrollbars=true - Hides any scrollbars before
|
|
171
|
+
* taking the screenshot, or not.
|
|
172
|
+
* @return {Jimp} A Jimp image object containing the screenshot.
|
|
173
|
+
* @example
|
|
174
|
+
* // Getting a base-64 encoded PNG from the returned screenshot
|
|
175
|
+
* let image = await Browser.takeFullPageScreenshot(driver);
|
|
176
|
+
* let encodedPNG = await image.getBase64Async("image/png");
|
|
177
|
+
*/
|
|
178
|
+
static async takeFullPageScreenshot(driver, hideScrollbars = true) {
|
|
179
|
+
// On macOS scrollbars appear and disappear overlapping the content as
|
|
180
|
+
// scrolling occurs. Hiding the scrollbars helps getting reproducible
|
|
181
|
+
// screenshots.
|
|
182
|
+
if (hideScrollbars) {
|
|
183
|
+
await driver.executeScript(() => {
|
|
184
|
+
if (!document.head)
|
|
185
|
+
return;
|
|
186
|
+
|
|
187
|
+
let style = document.createElement("style");
|
|
188
|
+
style.textContent = "html { overflow-y: scroll; }";
|
|
189
|
+
document.head.appendChild(style);
|
|
190
|
+
if (document.documentElement.clientWidth == window.innerWidth)
|
|
191
|
+
style.textContent = "html::-webkit-scrollbar { display: none; }";
|
|
192
|
+
else
|
|
193
|
+
document.head.removeChild(style);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let fullScreenshot = new Jimp(0, 0);
|
|
198
|
+
while (true) {
|
|
199
|
+
let [width, height, offset] = await driver.executeScript((...args) => {
|
|
200
|
+
window.scrollTo(0, args[0]);
|
|
201
|
+
// Math.ceil rounds up potential decimal values on window.scrollY,
|
|
202
|
+
// ensuring the loop will not hang due to never reaching enough
|
|
203
|
+
// fullScreenshot's height.
|
|
204
|
+
return [document.documentElement.clientWidth,
|
|
205
|
+
document.documentElement.scrollHeight,
|
|
206
|
+
Math.ceil(window.scrollY)];
|
|
207
|
+
}, fullScreenshot.bitmap.height);
|
|
208
|
+
let data = await driver.takeScreenshot();
|
|
209
|
+
let partialScreenshot = await Jimp.read(Buffer.from(data, "base64"));
|
|
210
|
+
let combinedScreenshot =
|
|
211
|
+
new Jimp(width, offset + partialScreenshot.bitmap.height);
|
|
212
|
+
combinedScreenshot.composite(fullScreenshot, 0, 0);
|
|
213
|
+
combinedScreenshot.composite(partialScreenshot, 0, offset);
|
|
214
|
+
fullScreenshot = combinedScreenshot;
|
|
215
|
+
|
|
216
|
+
if (fullScreenshot.bitmap.height >= height)
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
return fullScreenshot;
|
|
220
|
+
}
|
|
141
221
|
}
|
|
142
222
|
|
|
143
223
|
/**
|
|
144
|
-
*
|
|
224
|
+
* Browser and webdriver functionality for Chromium.
|
|
145
225
|
* @hideconstructor
|
|
146
226
|
* @extends Browser
|
|
147
227
|
*/
|
|
148
228
|
class Chromium extends Browser {
|
|
149
|
-
static #
|
|
150
|
-
|
|
151
|
-
static async #getBranchBasePosition(version) {
|
|
152
|
-
let data = await got(`https://omahaproxy.appspot.com/deps.json?version=${version}`).json();
|
|
153
|
-
return data.chromium_base_position;
|
|
154
|
-
}
|
|
229
|
+
static #CHANNELS = ["latest", "beta", "dev"];
|
|
155
230
|
|
|
156
231
|
static async #getVersionForChannel(channel) {
|
|
232
|
+
if (!Chromium.#CHANNELS.includes(channel))
|
|
233
|
+
return channel;
|
|
234
|
+
|
|
157
235
|
if (channel == "latest")
|
|
158
236
|
channel = "stable";
|
|
159
237
|
|
|
@@ -163,7 +241,7 @@ class Chromium extends Browser {
|
|
|
163
241
|
"linux-x64": "linux",
|
|
164
242
|
"darwin-x64": "mac",
|
|
165
243
|
"dawrin-arm64": "mac_arm64"
|
|
166
|
-
}[
|
|
244
|
+
}[platformArch];
|
|
167
245
|
let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
|
|
168
246
|
let release = data[0].versions.find(ver => ver.channel == channel);
|
|
169
247
|
let {current_version: version} = release;
|
|
@@ -177,55 +255,67 @@ class Chromium extends Browser {
|
|
|
177
255
|
}
|
|
178
256
|
|
|
179
257
|
static #getBinaryPath(dir) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"Chromium");
|
|
188
|
-
default:
|
|
189
|
-
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
190
|
-
}
|
|
258
|
+
checkPlatform();
|
|
259
|
+
return {
|
|
260
|
+
win32: path.join(dir, "chrome-win", "chrome.exe"),
|
|
261
|
+
linux: path.join(dir, "chrome-linux", "chrome"),
|
|
262
|
+
darwin: path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
|
|
263
|
+
"Chromium")
|
|
264
|
+
}[platform];
|
|
191
265
|
}
|
|
192
266
|
|
|
193
|
-
|
|
194
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Installs the browser. The Chromium executable gets extracted in the
|
|
269
|
+
* {@link snapshotsBaseDir} folder, ready to go.
|
|
270
|
+
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
271
|
+
* number (i.e. "77.0.3865.0"). Defaults to "latest".
|
|
272
|
+
* @return {BrowserBinary}
|
|
273
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
274
|
+
* download failed.
|
|
275
|
+
*/
|
|
276
|
+
static async installBrowser(version = "latest") {
|
|
277
|
+
const MIN_VERSION = 75;
|
|
278
|
+
const MAX_VERSION_DECREMENTS = 80;
|
|
279
|
+
|
|
280
|
+
checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
|
|
281
|
+
let versionNumber = await Chromium.#getVersionForChannel(version);
|
|
195
282
|
|
|
196
|
-
let
|
|
197
|
-
|
|
283
|
+
let {chromium_base_position: chromiumBase} =
|
|
284
|
+
await got(`https://omahaproxy.appspot.com/deps.json?version=${versionNumber}`).json();
|
|
285
|
+
let base = parseInt(chromiumBase, 10);
|
|
286
|
+
let startBase = base;
|
|
198
287
|
let [platformDir, fileName] = {
|
|
199
288
|
"win32-ia32": ["Win", "chrome-win.zip"],
|
|
200
289
|
"win32-x64": ["Win_x64", "chrome-win.zip"],
|
|
201
290
|
"linux-x64": ["Linux_x64", "chrome-linux.zip"],
|
|
202
291
|
"darwin-x64": ["Mac", "chrome-mac.zip"],
|
|
203
292
|
"dawrin-arm64": ["Mac_Arm", "chrome-mac.zip"]
|
|
204
|
-
}[
|
|
205
|
-
let archive
|
|
206
|
-
let browserDir
|
|
293
|
+
}[platformArch];
|
|
294
|
+
let archive;
|
|
295
|
+
let browserDir;
|
|
207
296
|
let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
|
|
297
|
+
let binary;
|
|
208
298
|
|
|
209
299
|
while (true) {
|
|
210
|
-
browserDir = path.join(snapshotsDir, `chromium-${
|
|
300
|
+
browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
|
|
301
|
+
binary = Chromium.#getBinaryPath(browserDir);
|
|
211
302
|
|
|
212
303
|
try {
|
|
213
304
|
await fs.promises.access(browserDir);
|
|
214
|
-
return {binary
|
|
305
|
+
return {binary, versionNumber, base};
|
|
215
306
|
}
|
|
216
307
|
catch (e) {}
|
|
217
308
|
|
|
218
309
|
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
219
310
|
|
|
220
|
-
archive = path.join(snapshotsDir, "cache", `${
|
|
221
|
-
|
|
311
|
+
archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
|
|
222
312
|
try {
|
|
223
313
|
try {
|
|
224
314
|
await fs.promises.access(archive);
|
|
225
315
|
}
|
|
226
316
|
catch (e) {
|
|
227
317
|
await download(
|
|
228
|
-
`https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${
|
|
318
|
+
`https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`,
|
|
229
319
|
archive);
|
|
230
320
|
}
|
|
231
321
|
break;
|
|
@@ -233,58 +323,53 @@ class Chromium extends Browser {
|
|
|
233
323
|
catch (e) {
|
|
234
324
|
// Chromium advises decrementing the branch_base_position when no
|
|
235
325
|
// matching build was found. See https://www.chromium.org/getting-involved/download-chromium
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
throw new Error(
|
|
326
|
+
base--;
|
|
327
|
+
if (base <= startBase - MAX_VERSION_DECREMENTS)
|
|
328
|
+
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
|
|
239
329
|
}
|
|
240
330
|
}
|
|
241
|
-
|
|
242
331
|
await extractZip(archive, {dir: browserDir});
|
|
243
|
-
return {binary: Chromium.#getBinaryPath(browserDir), revision};
|
|
244
|
-
}
|
|
245
332
|
|
|
246
|
-
|
|
247
|
-
* Downloads the browser binary file.
|
|
248
|
-
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
249
|
-
* number (i.e. "77.0.3865.0"). Defaults to "latest".
|
|
250
|
-
* @return {BrowserBinary}
|
|
251
|
-
*/
|
|
252
|
-
static async downloadBinary(version = "latest") {
|
|
253
|
-
const MIN_VERSION = 75;
|
|
254
|
-
const CHANNELS = ["latest", "beta", "dev"];
|
|
255
|
-
|
|
256
|
-
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
257
|
-
|
|
258
|
-
let versionNumber = CHANNELS.includes(version) ?
|
|
259
|
-
await Chromium.#getVersionForChannel(version) : version;
|
|
260
|
-
|
|
261
|
-
let base = await Chromium.#getBranchBasePosition(versionNumber);
|
|
262
|
-
|
|
263
|
-
let {binary, revision} = await Chromium.#downloadChromium(base);
|
|
264
|
-
return {binary, versionNumber, revision};
|
|
333
|
+
return {binary, versionNumber, base};
|
|
265
334
|
}
|
|
266
335
|
|
|
267
|
-
static async #installDriver(
|
|
268
|
-
let [dir, zip,
|
|
336
|
+
static async #installDriver(base, versionNumber) {
|
|
337
|
+
let [dir, zip, driverBinary] = {
|
|
269
338
|
"win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
270
339
|
"win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
271
340
|
"linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
|
|
272
341
|
"darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
|
|
273
342
|
"darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
|
|
274
|
-
}[
|
|
343
|
+
}[platformArch];
|
|
275
344
|
|
|
276
|
-
let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
let archive = path.join(cacheDir, `${revision}-${zip}`);
|
|
345
|
+
let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
|
|
346
|
+
versionNumber);
|
|
347
|
+
let archive = path.join(cacheDir, `${base}-${zip}`);
|
|
280
348
|
|
|
281
|
-
|
|
282
|
-
|
|
349
|
+
try {
|
|
350
|
+
await fs.promises.access(archive);
|
|
351
|
+
await extractZip(archive, {dir: cacheDir});
|
|
352
|
+
}
|
|
353
|
+
catch (e) { // zip file is either not cached or corrupted
|
|
354
|
+
let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
|
|
355
|
+
try {
|
|
356
|
+
await download(url, archive);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
360
|
+
}
|
|
361
|
+
await extractZip(archive, {dir: cacheDir});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await killDriverProcess("chromedriver");
|
|
365
|
+
let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
|
|
366
|
+
try {
|
|
367
|
+
await fs.promises.rm(driverPath, {recursive: true});
|
|
368
|
+
}
|
|
369
|
+
catch (e) {} // file does not exist
|
|
283
370
|
await extractZip(archive, {dir: cacheDir});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
await fs.promises.copyFile(path.join(cacheDir, zip.split(".")[0], driver),
|
|
287
|
-
path.join(destinationDir, driver));
|
|
371
|
+
|
|
372
|
+
return driverPath;
|
|
288
373
|
}
|
|
289
374
|
|
|
290
375
|
/** @see Browser.getDriver */
|
|
@@ -292,10 +377,9 @@ class Chromium extends Browser {
|
|
|
292
377
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
293
378
|
extraArgs = []
|
|
294
379
|
} = {}) {
|
|
295
|
-
let {binary,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
380
|
+
let {binary, versionNumber, base} = await Chromium.installBrowser(version);
|
|
381
|
+
let driverPath = await Chromium.#installDriver(base, versionNumber);
|
|
382
|
+
let serviceBuilder = new chrome.ServiceBuilder(driverPath);
|
|
299
383
|
let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
|
|
300
384
|
if (extensionPaths.length > 0)
|
|
301
385
|
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
@@ -310,6 +394,7 @@ class Chromium extends Browser {
|
|
|
310
394
|
let builder = new webdriver.Builder();
|
|
311
395
|
builder.forBrowser("chrome");
|
|
312
396
|
builder.setChromeOptions(options);
|
|
397
|
+
builder.setChromeService(serviceBuilder);
|
|
313
398
|
|
|
314
399
|
return builder.build();
|
|
315
400
|
}
|
|
@@ -334,106 +419,106 @@ class Chromium extends Browser {
|
|
|
334
419
|
continue;
|
|
335
420
|
|
|
336
421
|
extensionDetails = shadowRoot.getElementById("detailsButton");
|
|
422
|
+
break;
|
|
337
423
|
}
|
|
338
|
-
|
|
339
424
|
if (!extensionDetails)
|
|
340
|
-
reject(
|
|
425
|
+
reject(`${args[1]}: ${args[0]}`);
|
|
341
426
|
|
|
342
427
|
extensionDetails.click();
|
|
343
428
|
setTimeout(() => resolve(enable()), 100);
|
|
344
429
|
});
|
|
345
|
-
}, extensionTitle);
|
|
430
|
+
}, extensionTitle, EXTENSION_NOT_FOUND_ERROR);
|
|
346
431
|
}
|
|
347
432
|
}
|
|
348
433
|
|
|
349
434
|
/**
|
|
350
|
-
*
|
|
435
|
+
* Browser and webdriver functionality for Firefox.
|
|
351
436
|
* @hideconstructor
|
|
352
437
|
* @extends Browser
|
|
353
438
|
*/
|
|
354
439
|
class Firefox extends Browser {
|
|
355
|
-
static
|
|
440
|
+
static #CHANNELS = ["latest", "beta"];
|
|
441
|
+
|
|
442
|
+
static async #getVersionForChannel(channel) {
|
|
443
|
+
if (!Firefox.#CHANNELS.includes(channel))
|
|
444
|
+
return channel;
|
|
445
|
+
|
|
356
446
|
let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
|
|
357
|
-
return
|
|
447
|
+
return channel == "beta" ?
|
|
358
448
|
data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
|
|
359
449
|
}
|
|
360
450
|
|
|
361
451
|
static #getBinaryPath(dir) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox");
|
|
369
|
-
default:
|
|
370
|
-
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
371
|
-
}
|
|
452
|
+
checkPlatform();
|
|
453
|
+
return {
|
|
454
|
+
win32: path.join(dir, "core", "firefox.exe"),
|
|
455
|
+
linux: path.join(dir, "firefox", "firefox"),
|
|
456
|
+
darwin: path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox")
|
|
457
|
+
}[platform];
|
|
372
458
|
}
|
|
373
459
|
|
|
374
460
|
static #extractFirefoxArchive(archive, dir) {
|
|
375
|
-
switch (
|
|
461
|
+
switch (platform) {
|
|
376
462
|
case "win32":
|
|
377
|
-
// Procedure inspired from mozinstall:
|
|
378
|
-
// https://hg.mozilla.org/mozilla-central/file/tip/testing/mozbase/mozinstall/mozinstall/mozinstall.py
|
|
379
463
|
return promisify(exec)(`"${archive}" /extractdir=${dir}`);
|
|
380
464
|
case "linux":
|
|
381
465
|
return extractTar(archive, dir);
|
|
382
466
|
case "darwin":
|
|
383
467
|
return extractDmg(archive, dir);
|
|
384
468
|
default:
|
|
385
|
-
|
|
469
|
+
checkPlatform();
|
|
386
470
|
}
|
|
387
471
|
}
|
|
388
472
|
|
|
389
|
-
|
|
473
|
+
/**
|
|
474
|
+
* Installs the browser. The Firefox executable gets extracted in the
|
|
475
|
+
* {@link snapshotsBaseDir} folder, ready to go.
|
|
476
|
+
* @param {string} version - Either "latest", "beta" or a full version
|
|
477
|
+
* number (i.e. "68.0"). Defaults to "latest".
|
|
478
|
+
* @return {BrowserBinary}
|
|
479
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
480
|
+
* download failed.
|
|
481
|
+
*/
|
|
482
|
+
static async installBrowser(version = "latest") {
|
|
483
|
+
const MIN_VERSION = 60;
|
|
484
|
+
|
|
485
|
+
checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
|
|
486
|
+
let versionNumber = await Firefox.#getVersionForChannel(version);
|
|
487
|
+
|
|
390
488
|
let [buildPlatform, fileName] = {
|
|
391
|
-
"win32-ia32": ["win32", `Firefox Setup ${
|
|
392
|
-
"win32-x64": ["win64", `Firefox Setup ${
|
|
393
|
-
"linux-x64": ["linux-x86_64", `firefox-${
|
|
394
|
-
"darwin-x64": ["mac", `Firefox ${
|
|
395
|
-
"darwin-arm64": ["mac", `Firefox ${
|
|
396
|
-
}[
|
|
489
|
+
"win32-ia32": ["win32", `Firefox Setup ${versionNumber}.exe`],
|
|
490
|
+
"win32-x64": ["win64", `Firefox Setup ${versionNumber}.exe`],
|
|
491
|
+
"linux-x64": ["linux-x86_64", `firefox-${versionNumber}.tar.bz2`],
|
|
492
|
+
"darwin-x64": ["mac", `Firefox ${versionNumber}.dmg`],
|
|
493
|
+
"darwin-arm64": ["mac", `Firefox ${versionNumber}.dmg`]
|
|
494
|
+
}[platformArch];
|
|
397
495
|
|
|
398
496
|
let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
|
|
399
|
-
let browserDir = path.join(snapshotsDir,
|
|
400
|
-
|
|
401
|
-
|
|
497
|
+
let browserDir = path.join(snapshotsDir,
|
|
498
|
+
`firefox-${platformArch}-${versionNumber}`);
|
|
499
|
+
let binary = Firefox.#getBinaryPath(browserDir);
|
|
402
500
|
try {
|
|
403
501
|
await fs.promises.access(browserDir);
|
|
404
|
-
return
|
|
502
|
+
return {binary, versionNumber};
|
|
405
503
|
}
|
|
406
504
|
catch (e) {}
|
|
407
505
|
|
|
506
|
+
let archive = path.join(snapshotsDir, "cache", fileName);
|
|
408
507
|
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
409
508
|
try {
|
|
410
509
|
await fs.promises.access(archive);
|
|
411
510
|
}
|
|
412
511
|
catch (e) {
|
|
413
|
-
let url = `https://archive.mozilla.org/pub/firefox/releases/${
|
|
414
|
-
|
|
512
|
+
let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
|
|
513
|
+
try {
|
|
514
|
+
await download(url, archive);
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
518
|
+
}
|
|
415
519
|
}
|
|
416
|
-
|
|
417
520
|
await Firefox.#extractFirefoxArchive(archive, browserDir);
|
|
418
|
-
return Firefox.#getBinaryPath(browserDir);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Downloads the browser binary file
|
|
423
|
-
* @param {string} version - Either "latest", "beta" or a full version
|
|
424
|
-
* number (i.e. "68.0"). Defaults to "latest".
|
|
425
|
-
* @return {BrowserBinary}
|
|
426
|
-
*/
|
|
427
|
-
static async downloadBinary(version = "latest") {
|
|
428
|
-
const MIN_VERSION = 60;
|
|
429
|
-
const CHANNELS = ["latest", "beta"];
|
|
430
|
-
|
|
431
|
-
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
432
|
-
|
|
433
|
-
let versionNumber = CHANNELS.includes(version) ?
|
|
434
|
-
await Firefox.#getVersionForChannel(version) : version;
|
|
435
521
|
|
|
436
|
-
let binary = await Firefox.#downloadFirefox(versionNumber);
|
|
437
522
|
return {binary, versionNumber};
|
|
438
523
|
}
|
|
439
524
|
|
|
@@ -442,7 +527,7 @@ class Firefox extends Browser {
|
|
|
442
527
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
443
528
|
extraArgs = []
|
|
444
529
|
} = {}) {
|
|
445
|
-
let {binary} = await Firefox.
|
|
530
|
+
let {binary} = await Firefox.installBrowser(version);
|
|
446
531
|
|
|
447
532
|
let options = new firefox.Options();
|
|
448
533
|
if (headless)
|
|
@@ -471,7 +556,7 @@ class Firefox extends Browser {
|
|
|
471
556
|
throw err;
|
|
472
557
|
await killDriverProcess("geckodriver");
|
|
473
558
|
}
|
|
474
|
-
}, 30000,
|
|
559
|
+
}, 30000, `${DRIVER_START_ERROR}: geckodriver`, 1000);
|
|
475
560
|
|
|
476
561
|
for (let extensionPath of extensionPaths) {
|
|
477
562
|
await driver.execute(
|
|
@@ -485,10 +570,9 @@ class Firefox extends Browser {
|
|
|
485
570
|
|
|
486
571
|
/** @see Browser.enableExtensionInIncognito */
|
|
487
572
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
488
|
-
let version = await getBrowserVersion(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
throw new Error(`Only supported on Firefox >= 87. Current version: ${version}`);
|
|
573
|
+
let version = (await driver.getCapabilities()).getBrowserVersion();
|
|
574
|
+
// The UI workaround assumes web elements only present on Firefox >= 87
|
|
575
|
+
checkVersion(version, 87);
|
|
492
576
|
|
|
493
577
|
await driver.navigate().to("about:addons");
|
|
494
578
|
await driver.wait(until.elementLocated(By.name("extension")), 1000).click();
|
|
@@ -501,33 +585,50 @@ class Firefox extends Browser {
|
|
|
501
585
|
await elem.click();
|
|
502
586
|
return await driver.findElement(By.name("private-browsing")).click();
|
|
503
587
|
}
|
|
504
|
-
throw new Error(
|
|
588
|
+
throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
|
|
505
589
|
}
|
|
506
590
|
}
|
|
507
591
|
|
|
508
592
|
/**
|
|
509
|
-
*
|
|
593
|
+
* Browser and webdriver functionality for Edge.
|
|
510
594
|
* @hideconstructor
|
|
511
595
|
* @extends Browser
|
|
512
596
|
*/
|
|
513
597
|
class Edge extends Browser {
|
|
514
|
-
static #
|
|
598
|
+
static #CHANNELS = ["latest", "beta", "dev"];
|
|
515
599
|
|
|
516
600
|
static async #getVersionForChannel(version) {
|
|
517
|
-
|
|
518
|
-
|
|
601
|
+
let channel = "stable";
|
|
602
|
+
if (Edge.#CHANNELS.includes(version) && version != "latest")
|
|
603
|
+
channel = version;
|
|
519
604
|
|
|
520
|
-
let channel = version == "latest" ? "stable" : version;
|
|
521
605
|
let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`);
|
|
522
|
-
let
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
versionNumbers
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
606
|
+
let versionNumber;
|
|
607
|
+
if (Edge.#CHANNELS.includes(version)) {
|
|
608
|
+
let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
|
|
609
|
+
let matches;
|
|
610
|
+
let versionNumbers = [];
|
|
611
|
+
while ((matches = regex.exec(body)) !== null)
|
|
612
|
+
versionNumbers.push(matches[2]);
|
|
613
|
+
|
|
614
|
+
let compareVersions = (v1, v2) =>
|
|
615
|
+
parseInt(v1.split(".")[0], 10) < parseInt(v2.split(".")[0], 10) ?
|
|
616
|
+
1 : -1;
|
|
617
|
+
versionNumber = versionNumbers.sort(compareVersions)[0];
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
let split = version.split(".");
|
|
621
|
+
let minorVersion = split.length == 4 ? parseInt(split.pop(), 10) : -1;
|
|
622
|
+
let majorVersion = split.join(".");
|
|
623
|
+
let found;
|
|
624
|
+
while (!found && minorVersion >= 0) {
|
|
625
|
+
versionNumber = `${majorVersion}.${minorVersion}`;
|
|
626
|
+
found = body.includes(versionNumber);
|
|
627
|
+
minorVersion--;
|
|
628
|
+
}
|
|
629
|
+
if (!found)
|
|
630
|
+
throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
|
|
631
|
+
}
|
|
531
632
|
|
|
532
633
|
return {versionNumber, channel};
|
|
533
634
|
}
|
|
@@ -539,7 +640,7 @@ class Edge extends Browser {
|
|
|
539
640
|
}
|
|
540
641
|
|
|
541
642
|
static #getBinaryPath(channel = "stable") {
|
|
542
|
-
switch (
|
|
643
|
+
switch (platform) {
|
|
543
644
|
case "win32":
|
|
544
645
|
let programFiles = process.env["ProgramFiles(x86)"] ?
|
|
545
646
|
"${Env:ProgramFiles(x86)}" : "${Env:ProgramFiles}";
|
|
@@ -551,26 +652,28 @@ class Edge extends Browser {
|
|
|
551
652
|
let appName = Edge.#getDarwinAppName(channel);
|
|
552
653
|
return `${process.env.HOME}/Applications/${appName}.app/Contents/MacOS/${appName}`;
|
|
553
654
|
default:
|
|
554
|
-
|
|
655
|
+
checkPlatform();
|
|
555
656
|
}
|
|
556
657
|
}
|
|
557
658
|
|
|
558
659
|
/**
|
|
559
|
-
*
|
|
660
|
+
* Installs the browser. On Linux, Edge is installed as a system package,
|
|
661
|
+
* which requires root permissions. On MacOS, Edge is installed as a user
|
|
662
|
+
* app (not as a system app). Installing Edge on Windows is not supported.
|
|
560
663
|
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
561
|
-
* number (i.e. "95.0.1020.
|
|
562
|
-
* available on Linux.
|
|
664
|
+
* number (i.e. "95.0.1020.40"). Defaults to "latest".
|
|
563
665
|
* @return {BrowserBinary}
|
|
666
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
667
|
+
* download failed.
|
|
564
668
|
*/
|
|
565
|
-
static async
|
|
566
|
-
if (
|
|
669
|
+
static async installBrowser(version = "latest") {
|
|
670
|
+
if (platform == "win32")
|
|
567
671
|
// Edge is mandatory on Windows, can't be uninstalled or downgraded
|
|
568
672
|
// https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
|
|
569
|
-
throw new Error(
|
|
673
|
+
throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
|
|
570
674
|
|
|
571
675
|
const MIN_VERSION = 95;
|
|
572
|
-
|
|
573
|
-
checkVersion(version, MIN_VERSION, CHANNELS);
|
|
676
|
+
checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
|
|
574
677
|
let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
|
|
575
678
|
|
|
576
679
|
let darwinName = {
|
|
@@ -581,14 +684,14 @@ class Edge extends Browser {
|
|
|
581
684
|
let filename = {
|
|
582
685
|
linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
|
|
583
686
|
darwin: `${darwinName}-${versionNumber}.pkg`
|
|
584
|
-
}[
|
|
687
|
+
}[platform];
|
|
585
688
|
let darwinArch = process.arch == "arm64" ?
|
|
586
689
|
"03adf619-38c6-4249-95ff-4a01c0ffc962" :
|
|
587
690
|
"C1297A47-86C4-4C1F-97FA-950631F94777";
|
|
588
|
-
let
|
|
691
|
+
let url = {
|
|
589
692
|
linux: `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
|
|
590
693
|
darwin: `https://officecdnmac.microsoft.com/pr/${darwinArch}/MacAutoupdate/${filename}`
|
|
591
|
-
}[
|
|
694
|
+
}[platform];
|
|
592
695
|
|
|
593
696
|
let snapshotsDir = path.join(snapshotsBaseDir, "edge");
|
|
594
697
|
let archive = path.join(snapshotsDir, "cache", filename);
|
|
@@ -600,16 +703,16 @@ class Edge extends Browser {
|
|
|
600
703
|
catch (e) {}
|
|
601
704
|
|
|
602
705
|
try {
|
|
603
|
-
await download(
|
|
706
|
+
await download(url, archive);
|
|
604
707
|
}
|
|
605
708
|
catch (err) {
|
|
606
|
-
throw new Error(
|
|
709
|
+
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
607
710
|
}
|
|
608
711
|
|
|
609
|
-
if (
|
|
712
|
+
if (platform == "linux") {
|
|
610
713
|
await promisify(exec)(`dpkg -i ${archive}`);
|
|
611
714
|
}
|
|
612
|
-
else if (
|
|
715
|
+
else if (platform == "darwin") {
|
|
613
716
|
let appName = Edge.#getDarwinAppName(channel);
|
|
614
717
|
try {
|
|
615
718
|
await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {recursive: true});
|
|
@@ -629,47 +732,55 @@ class Edge extends Browser {
|
|
|
629
732
|
}
|
|
630
733
|
|
|
631
734
|
static async #installDriver() {
|
|
735
|
+
async function extractEdgeZip(archive, cacheDir, driverPath) {
|
|
736
|
+
await killDriverProcess("msedgedriver");
|
|
737
|
+
try {
|
|
738
|
+
await fs.promises.rm(driverPath, {recursive: true});
|
|
739
|
+
}
|
|
740
|
+
catch (e) {} // file does not exist
|
|
741
|
+
await extractZip(archive, {dir: cacheDir});
|
|
742
|
+
}
|
|
743
|
+
|
|
632
744
|
let binary = Edge.#getBinaryPath();
|
|
633
745
|
let versionNumber = await Edge.#getInstalledVersionNumber(binary);
|
|
634
746
|
if (!versionNumber)
|
|
635
|
-
throw new Error(
|
|
747
|
+
throw new Error(`${BROWSER_NOT_INSTALLED_ERROR}: Edge`);
|
|
636
748
|
|
|
637
|
-
let [zip,
|
|
749
|
+
let [zip, driverBinary] = {
|
|
638
750
|
"win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
|
|
639
751
|
"win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
|
|
640
752
|
"linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
|
|
641
753
|
"darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
|
|
642
754
|
"darwin-arm64": ["edgedriver_arm64.zip", "msedgedriver"]
|
|
643
|
-
}[
|
|
755
|
+
}[platformArch];
|
|
644
756
|
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
|
|
645
757
|
`edgedriver-${versionNumber}`);
|
|
646
758
|
let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
|
|
759
|
+
let driverPath = path.join(cacheDir, driverBinary);
|
|
647
760
|
|
|
648
|
-
let vSplit = versionNumber.split(".");
|
|
649
|
-
let lastNum = parseInt(vSplit[3], 10);
|
|
650
|
-
while (lastNum >= 0) {
|
|
651
|
-
try {
|
|
652
|
-
let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
|
|
653
|
-
await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
|
|
654
|
-
archive);
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
catch (e) {
|
|
658
|
-
lastNum--;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (lastNum < 0)
|
|
663
|
-
throw new Error(`msedgedriver was not found for Edge ${versionNumber}`);
|
|
664
|
-
|
|
665
|
-
await killDriverProcess(Edge.#DRIVER);
|
|
666
|
-
let driverPath = path.join(cacheDir, driver);
|
|
667
761
|
try {
|
|
668
|
-
await fs.promises.
|
|
762
|
+
await fs.promises.access(archive);
|
|
763
|
+
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
669
764
|
}
|
|
670
|
-
catch (e) {
|
|
671
|
-
|
|
765
|
+
catch (e) { // zip file is either not cached or corrupted
|
|
766
|
+
let vSplit = versionNumber.split(".");
|
|
767
|
+
let lastNum = parseInt(vSplit[3], 10);
|
|
768
|
+
while (lastNum >= 0) {
|
|
769
|
+
try {
|
|
770
|
+
let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
|
|
771
|
+
await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
|
|
772
|
+
archive);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
catch (e2) {
|
|
776
|
+
lastNum--;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (lastNum < 0)
|
|
780
|
+
throw new Error(`${DRIVER_DOWNLOAD_ERROR}: Edge ${versionNumber}`);
|
|
672
781
|
|
|
782
|
+
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
783
|
+
}
|
|
673
784
|
return driverPath;
|
|
674
785
|
}
|
|
675
786
|
|
|
@@ -678,8 +789,8 @@ class Edge extends Browser {
|
|
|
678
789
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
679
790
|
extraArgs = []
|
|
680
791
|
} = {}) {
|
|
681
|
-
if (
|
|
682
|
-
await Edge.
|
|
792
|
+
if (platform == "linux" || platform == "darwin")
|
|
793
|
+
await Edge.installBrowser(version);
|
|
683
794
|
|
|
684
795
|
let driverPath = await Edge.#installDriver();
|
|
685
796
|
let serviceBuilder = new edge.ServiceBuilder(driverPath);
|
|
@@ -718,17 +829,19 @@ class Edge extends Browser {
|
|
|
718
829
|
await button.click();
|
|
719
830
|
return await driver.findElement(By.id("itemAllowIncognito")).click();
|
|
720
831
|
}
|
|
721
|
-
throw new Error(
|
|
832
|
+
throw new Error(`${ELEMENT_NOT_FOUND_ERROR}: Details button`);
|
|
722
833
|
}
|
|
723
|
-
throw new Error(
|
|
834
|
+
throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
|
|
724
835
|
}
|
|
725
836
|
}
|
|
726
837
|
|
|
727
838
|
/**
|
|
728
839
|
* @type {Object}
|
|
729
|
-
* @property {Chromium} chromium -
|
|
730
|
-
*
|
|
731
|
-
* @property {
|
|
840
|
+
* @property {Chromium} chromium - Browser and webdriver functionality for
|
|
841
|
+
* Chromium.
|
|
842
|
+
* @property {Firefox} firefox - Browser and webdriver functionality for
|
|
843
|
+
* Firefox.
|
|
844
|
+
* @property {Edge} edge - Browser and webdriver functionality for Edge.
|
|
732
845
|
*/
|
|
733
846
|
export const BROWSERS = {
|
|
734
847
|
chromium: Chromium,
|
package/src/utils.js
CHANGED
|
@@ -77,11 +77,6 @@ export async function extractDmg(archive, dir) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export async function getBrowserVersion(driver) {
|
|
81
|
-
let version = (await driver.getCapabilities()).getBrowserVersion();
|
|
82
|
-
return version ? parseInt(version.split(".")[0], 10) : null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
80
|
// Useful to unlock the driver file before replacing it or executing it
|
|
86
81
|
export async function killDriverProcess(driverName) {
|
|
87
82
|
let cmd = `kill $(pgrep ${driverName})`;
|
package/test/browsers.js
CHANGED
|
@@ -21,10 +21,9 @@ import path from "path";
|
|
|
21
21
|
|
|
22
22
|
import {BROWSERS, snapshotsBaseDir} from "../index.js";
|
|
23
23
|
import {killDriverProcess} from "../src/utils.js";
|
|
24
|
+
import Jimp from "jimp";
|
|
24
25
|
|
|
25
|
-
// Required to set the driver path on Windows
|
|
26
|
-
import "chromedriver";
|
|
27
|
-
import "geckodriver";
|
|
26
|
+
import "geckodriver"; // Required to set the driver path on Windows
|
|
28
27
|
|
|
29
28
|
const VERSIONS = {
|
|
30
29
|
chromium: ["latest", "75.0.3770.0", "beta", "dev"],
|
|
@@ -32,7 +31,7 @@ const VERSIONS = {
|
|
|
32
31
|
edge: ["latest", "95.0.1020.40", "beta", "dev"]
|
|
33
32
|
};
|
|
34
33
|
const TEST_URL = "https://gitlab.com/eyeo/developer-experience/get-browser-binary";
|
|
35
|
-
|
|
34
|
+
const TEST_URL_LONG_PAGE = "https://abptestpages.org/";
|
|
36
35
|
|
|
37
36
|
async function switchToHandle(driver, testFn) {
|
|
38
37
|
for (let handle of await driver.getAllWindowHandles()) {
|
|
@@ -44,7 +43,6 @@ async function switchToHandle(driver, testFn) {
|
|
|
44
43
|
catch (e) {
|
|
45
44
|
continue;
|
|
46
45
|
}
|
|
47
|
-
|
|
48
46
|
if (testFn(url))
|
|
49
47
|
return handle;
|
|
50
48
|
}
|
|
@@ -90,11 +88,28 @@ expect.extend({
|
|
|
90
88
|
}
|
|
91
89
|
});
|
|
92
90
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
function getExtension(browser, version) {
|
|
92
|
+
let manifest = "mv2";
|
|
93
|
+
// The latest Edge installed on the Gitlab Windows Shared Runners is Edge 79,
|
|
94
|
+
// which does not support manifest v3. More info:
|
|
95
|
+
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/29
|
|
96
|
+
let windowsSharedRunners =
|
|
97
|
+
browser == "edge" && process.platform == "win32" && process.env.CI_JOB_ID;
|
|
98
|
+
if (["chromium", "edge"].includes(browser) &&
|
|
99
|
+
["latest", "beta", "dev"].includes(version) && !windowsSharedRunners)
|
|
100
|
+
manifest = "mv3";
|
|
101
|
+
|
|
102
|
+
let extensionPaths =
|
|
103
|
+
[path.resolve(process.cwd(), "test", "extension", manifest)];
|
|
104
|
+
return {extensionPaths, manifest};
|
|
105
|
+
}
|
|
96
106
|
|
|
107
|
+
for (let browser of Object.keys(BROWSERS)) {
|
|
108
|
+
describe(`Browser: ${browser}`, () => {
|
|
97
109
|
before(async() => {
|
|
110
|
+
if (process.env.TEST_KEEP_SNAPSHOTS == "true")
|
|
111
|
+
return;
|
|
112
|
+
|
|
98
113
|
try {
|
|
99
114
|
await fs.promises.rm(snapshotsBaseDir, {recursive: true});
|
|
100
115
|
}
|
|
@@ -120,12 +135,12 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
120
135
|
|
|
121
136
|
afterEach(quitDriver);
|
|
122
137
|
|
|
123
|
-
it("
|
|
138
|
+
it("installs", async function() {
|
|
124
139
|
if (browser == "edge" && process.platform == "win32")
|
|
125
140
|
this.skip();
|
|
126
141
|
|
|
127
142
|
let {binary, versionNumber} =
|
|
128
|
-
await BROWSERS[browser].
|
|
143
|
+
await BROWSERS[browser].installBrowser(version);
|
|
129
144
|
let browserName = browser == "edge" ? /(edge|Edge)/ : browser;
|
|
130
145
|
expect(binary).toEqual(expect.stringMatching(browserName));
|
|
131
146
|
|
|
@@ -167,8 +182,22 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
167
182
|
expect(sizeDevToolsOpen).toMeasureLessThan(sizeDevToolsClosed);
|
|
168
183
|
});
|
|
169
184
|
|
|
185
|
+
it("takes a full page screenshot", async() => {
|
|
186
|
+
driver = await BROWSERS[browser].getDriver(version);
|
|
187
|
+
await driver.navigate().to(TEST_URL_LONG_PAGE);
|
|
188
|
+
|
|
189
|
+
let fullImg = await BROWSERS[browser].takeFullPageScreenshot(driver);
|
|
190
|
+
// Taking a regular webdriver screenshot, which should be shorter
|
|
191
|
+
let data = await driver.takeScreenshot();
|
|
192
|
+
let partImg = await Jimp.read(Buffer.from(data, "base64"));
|
|
193
|
+
|
|
194
|
+
expect(fullImg.bitmap.width).toBeGreaterThan(0);
|
|
195
|
+
expect(fullImg.bitmap.height).toBeGreaterThan(partImg.bitmap.height);
|
|
196
|
+
});
|
|
197
|
+
|
|
170
198
|
it("loads an extension", async() => {
|
|
171
199
|
let headless = browser == "firefox";
|
|
200
|
+
let {extensionPaths} = getExtension(browser, version);
|
|
172
201
|
|
|
173
202
|
driver = await BROWSERS[browser].getDriver(
|
|
174
203
|
version, {headless, extensionPaths});
|
|
@@ -179,22 +208,23 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
179
208
|
if (browser == "firefox" && version == "60.0")
|
|
180
209
|
this.skip();
|
|
181
210
|
|
|
211
|
+
let {extensionPaths, manifest} = getExtension(browser, version);
|
|
182
212
|
driver = await BROWSERS[browser].getDriver(
|
|
183
213
|
version, {headless: false, extensionPaths, incognito: true});
|
|
184
214
|
await BROWSERS[browser].enableExtensionInIncognito(
|
|
185
|
-
driver,
|
|
215
|
+
driver, `Browser test extension - ${manifest}`
|
|
186
216
|
);
|
|
187
217
|
await getHandle(driver, "/index.html");
|
|
188
218
|
});
|
|
189
219
|
});
|
|
190
220
|
}
|
|
191
221
|
|
|
192
|
-
it("does not
|
|
222
|
+
it("does not install unsupported versions", async function() {
|
|
193
223
|
if (browser == "edge" && process.platform == "win32")
|
|
194
224
|
this.skip();
|
|
195
225
|
|
|
196
226
|
for (let unsupported of ["0.0", "invalid"]) {
|
|
197
|
-
await expect(BROWSERS[browser].
|
|
227
|
+
await expect(BROWSERS[browser].installBrowser(unsupported))
|
|
198
228
|
.rejects.toThrow(`Unsupported browser version: ${unsupported}`);
|
|
199
229
|
}
|
|
200
230
|
});
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>Browser test extension - mv2</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>Browser test extension - mv3</h1>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<h1>Browser download test extension</h1>
|