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