@eyeo/get-browser-binary 0.13.0 → 0.15.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 +4 -4
- package/README.md +32 -14
- package/RELEASE_NOTES.md +36 -0
- package/index.js +19 -2
- package/package.json +8 -5
- package/src/browser.js +134 -0
- package/src/chromium.js +324 -0
- package/src/edge.js +308 -0
- package/src/firefox.js +212 -0
- package/src/utils.js +43 -0
- package/test/browsers.js +53 -46
- package/test/docker/Dockerfile +1 -1
- package/test/pages/basic.html +1 -0
- package/test/pages/download-test.txt +1 -0
- package/test/pages/long.html +41 -0
- package/test/runner.js +74 -0
- package/test/start-server.js +20 -0
- package/test/test-server.js +37 -0
- package/test/utils.js +5 -4
- package/src/browsers.js +0 -939
- package/test/docker/arm64.Dockerfile +0 -22
package/.gitlab-ci.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
default:
|
|
2
|
-
image: registry.gitlab.com/eyeo/docker/get-browser-binary:
|
|
2
|
+
image: registry.gitlab.com/eyeo/docker/get-browser-binary:node18
|
|
3
3
|
interruptible: true
|
|
4
4
|
|
|
5
5
|
stages:
|
|
@@ -29,9 +29,9 @@ test:basic:
|
|
|
29
29
|
|
|
30
30
|
test:browsers:linux:
|
|
31
31
|
stage: test
|
|
32
|
-
image: docker:
|
|
32
|
+
image: docker:24.0.5
|
|
33
33
|
services:
|
|
34
|
-
- docker:
|
|
34
|
+
- docker:24.0.5-dind
|
|
35
35
|
before_script:
|
|
36
36
|
- docker build -f test/docker/Dockerfile -t browsers .
|
|
37
37
|
script:
|
|
@@ -48,7 +48,7 @@ test:browsers:windows:
|
|
|
48
48
|
-OutFile 'MicrosoftEdgeEnterpriseX64.msi'
|
|
49
49
|
- Start-Process msiexec
|
|
50
50
|
-ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
|
|
51
|
-
- choco upgrade -y nodejs --version
|
|
51
|
+
- choco upgrade -y --no-progress nodejs --version 18.17.1
|
|
52
52
|
- npm install
|
|
53
53
|
script:
|
|
54
54
|
# Running Edge tests only on the preinstalled version
|
package/README.md
CHANGED
|
@@ -30,20 +30,25 @@ the right side.
|
|
|
30
30
|
|
|
31
31
|
### Supported browser versions
|
|
32
32
|
|
|
33
|
-
- Chromium >=
|
|
34
|
-
- Firefox >=
|
|
33
|
+
- Chromium >= 77 (Chromium ARM >= 92)
|
|
34
|
+
- Firefox >= 68
|
|
35
35
|
- Edge >= 95
|
|
36
36
|
|
|
37
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. On macOS, only the latest
|
|
39
39
|
Edge version is supported.
|
|
40
40
|
|
|
41
|
+
### Verbose logging
|
|
42
|
+
|
|
43
|
+
Set the `VERBOSE` environment variable to `"true"` to get verbose logging on
|
|
44
|
+
download requests.
|
|
45
|
+
|
|
41
46
|
## Development
|
|
42
47
|
|
|
43
48
|
### Prerequisites
|
|
44
49
|
|
|
45
|
-
- Node >=
|
|
46
|
-
- npm >=
|
|
50
|
+
- Node >= 18
|
|
51
|
+
- npm >= 9
|
|
47
52
|
|
|
48
53
|
### Installing/Updating dependencies
|
|
49
54
|
|
|
@@ -70,6 +75,8 @@ Running all tests:
|
|
|
70
75
|
npm test
|
|
71
76
|
```
|
|
72
77
|
|
|
78
|
+
### Options
|
|
79
|
+
|
|
73
80
|
The `grep` option filters the tests to run with a regular expression. Example:
|
|
74
81
|
|
|
75
82
|
```shell
|
|
@@ -91,6 +98,21 @@ variable to `true`. Example:
|
|
|
91
98
|
TEST_KEEP_SNAPSHOTS=true npm test
|
|
92
99
|
```
|
|
93
100
|
|
|
101
|
+
### Test server
|
|
102
|
+
|
|
103
|
+
Tests use a local http server, which is managed by the `npm test` command. If
|
|
104
|
+
needed, the test server can also run independently:
|
|
105
|
+
|
|
106
|
+
```shell
|
|
107
|
+
npm run test-server
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Then tests may be executed on a separate session. Example:
|
|
111
|
+
|
|
112
|
+
```shell
|
|
113
|
+
npm run test-suite -- --grep "chromium.*latest"
|
|
114
|
+
```
|
|
115
|
+
|
|
94
116
|
### Running tests on Docker
|
|
95
117
|
|
|
96
118
|
Useful to reproduce the CI environment of the `test:browsers:linux` job.
|
|
@@ -111,22 +133,18 @@ docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest --timeout 10000
|
|
|
111
133
|
|
|
112
134
|
#### ARM architecture (M1/M2 Apple Silicon)
|
|
113
135
|
|
|
114
|
-
|
|
136
|
+
The run is done emulating the AMD architecture. Requirements:
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
138
|
+
- macOS >= 13 (Ventura)
|
|
139
|
+
- Rosetta
|
|
140
|
+
- The feature "Use Rosetta for x86/amd64 emulation on Apple Silicon" enabled in Docker
|
|
120
141
|
|
|
121
|
-
|
|
142
|
+
The `--platform` option should be used when running the image:
|
|
122
143
|
|
|
123
144
|
```shell
|
|
124
|
-
docker
|
|
125
|
-
docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep firefox.*latest" -it browsers-amd
|
|
145
|
+
docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep chromium.*latest" -it browsers
|
|
126
146
|
```
|
|
127
147
|
|
|
128
|
-
Edge: Not supported
|
|
129
|
-
|
|
130
148
|
## Building the documentation
|
|
131
149
|
|
|
132
150
|
```shell
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
# 0.15.0
|
|
2
|
+
|
|
3
|
+
- Fixes the downloads of Chromium versions < 91 by replacing the remaining
|
|
4
|
+
usages of omahaproxy API (now removed) with chromiumdash (#55)
|
|
5
|
+
- Updates to selenium webdriver 4.15.0 (#66)
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
|
|
9
|
+
- Uses AMD emulation on ARM docker test runs (!93)
|
|
10
|
+
|
|
11
|
+
# 0.14.0
|
|
12
|
+
|
|
13
|
+
- Increases minimum supported browser versions (#64)
|
|
14
|
+
- Sets a minimum supported version for Chromium ARM (#54)
|
|
15
|
+
- Upgrades minimum node version to node 18 (#63)
|
|
16
|
+
- Fixes an occasional issue where the latest Edge on Windows could not find its
|
|
17
|
+
version on Chromium Dash (#61)
|
|
18
|
+
- Enables `dom.promise_rejection_events.enabled` for Firefox 68 (#49)
|
|
19
|
+
- Adds optional verbose logging on download requests (!79)
|
|
20
|
+
- Refactors `src/browsers.js` file (#65)
|
|
21
|
+
|
|
22
|
+
### Testing
|
|
23
|
+
|
|
24
|
+
- Adds a local http server for tests (#50)
|
|
25
|
+
- Allows running Firefox ARM in the Docker ARM image (!84)
|
|
26
|
+
- Skip other browser tests when install fails (#58)
|
|
27
|
+
- Fixes an issue where Linux Edge tests would fail when not following a very
|
|
28
|
+
specific order (#57)
|
|
29
|
+
- Reduces the size of the Windows CI log (#62)
|
|
30
|
+
|
|
31
|
+
### Notes for integrators
|
|
32
|
+
|
|
33
|
+
- The minimum broser versions are now Chromium >= 77 (Chromium ARM >= 92) and
|
|
34
|
+
Firefox >= 68. Edge keeps the same version (>= 95)
|
|
35
|
+
- The minimum required node version is now 18
|
|
36
|
+
|
|
1
37
|
# 0.13.0
|
|
2
38
|
|
|
3
39
|
- Fixes an issue that prevented the webdriver from locating the Edge binary on
|
package/index.js
CHANGED
|
@@ -15,5 +15,22 @@
|
|
|
15
15
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import {Chromium} from "./src/chromium.js";
|
|
19
|
+
import {Firefox} from "./src/firefox.js";
|
|
20
|
+
import {Edge} from "./src/edge.js";
|
|
21
|
+
|
|
22
|
+
export {download, takeFullPageScreenshot, snapshotsBaseDir}
|
|
23
|
+
from "./src/utils.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @type {Object}
|
|
27
|
+
* @property {Chromium} chromium Browser and webdriver functionality for
|
|
28
|
+
* Chromium.
|
|
29
|
+
* @property {Firefox} firefox Browser and webdriver functionality for Firefox.
|
|
30
|
+
* @property {Edge} edge Browser and webdriver functionality for Edge.
|
|
31
|
+
*/
|
|
32
|
+
export const BROWSERS = {
|
|
33
|
+
chromium: Chromium,
|
|
34
|
+
firefox: Firefox,
|
|
35
|
+
edge: Edge
|
|
36
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyeo/get-browser-binary",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Install browser binaries and matching webdrivers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"author": "eyeo GmbH",
|
|
10
10
|
"license": "GPL-3.0",
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
13
|
-
"npm": ">=
|
|
12
|
+
"node": ">=18",
|
|
13
|
+
"npm": ">=9"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "index.js",
|
|
@@ -33,18 +33,21 @@
|
|
|
33
33
|
"geckodriver": "3.1.0",
|
|
34
34
|
"got": "^12.5.3",
|
|
35
35
|
"jimp": "^0.22.4",
|
|
36
|
-
"selenium-webdriver": "^4.
|
|
36
|
+
"selenium-webdriver": "^4.15.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"eslint": "^8.33.0",
|
|
40
40
|
"eslint-config-eyeo": "^3.2.0",
|
|
41
41
|
"expect": "^29.4.2",
|
|
42
|
+
"express": "^4.18.2",
|
|
42
43
|
"jsdoc": "^4.0.0",
|
|
43
44
|
"mocha": "^10.2.0"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"docs": "jsdoc --readme README.md --destination docs src/*.js",
|
|
47
48
|
"lint": "eslint --ext js .",
|
|
48
|
-
"test": "
|
|
49
|
+
"test": "node test/runner.js --",
|
|
50
|
+
"test-suite": "mocha --exclude test/start-server.js --exclude test/test-server.js --exclude test/runner.js --",
|
|
51
|
+
"test-server": "node test/start-server.js"
|
|
49
52
|
}
|
|
50
53
|
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2006-present eyeo GmbH
|
|
3
|
+
*
|
|
4
|
+
* This module is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {exec, execFile} from "child_process";
|
|
19
|
+
import {promisify} from "util";
|
|
20
|
+
|
|
21
|
+
import {errMsg} from "./utils.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base class for browser and webdriver functionality. Please see subclasses for
|
|
25
|
+
* browser specific details. All classes can be used statically.
|
|
26
|
+
* @hideconstructor
|
|
27
|
+
*/
|
|
28
|
+
export class Browser {
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} BrowserBinary
|
|
31
|
+
* @property {string} binary The path to the browser binary.
|
|
32
|
+
* @property {string} versionNumber The version number of the browser binary.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Installs the browser. The installation process is detailed on the
|
|
37
|
+
* subclasses.
|
|
38
|
+
* @param {string} [version=latest] Either full version number or
|
|
39
|
+
* channel/release. Please find examples on the subclasses.
|
|
40
|
+
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
41
|
+
* install files to complete. When set to 0 there is no time limit.
|
|
42
|
+
* @return {BrowserBinary}
|
|
43
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
44
|
+
* download failed.
|
|
45
|
+
*/
|
|
46
|
+
static async installBrowser(version, downloadTimeout = 0) {
|
|
47
|
+
// to be implemented by the subclass
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gets the installed version returned by the browser binary.
|
|
52
|
+
* @param {string} binary The path to the browser binary.
|
|
53
|
+
* @return {string} Installed browser version.
|
|
54
|
+
* @throws {Error} Browser is not installed.
|
|
55
|
+
*/
|
|
56
|
+
static async getInstalledVersion(binary) {
|
|
57
|
+
try {
|
|
58
|
+
let stdout;
|
|
59
|
+
let stderr;
|
|
60
|
+
if (process.platform == "win32") {
|
|
61
|
+
({stdout, stderr} = await promisify(exec)(
|
|
62
|
+
`(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
|
|
63
|
+
{shell: "powershell.exe"})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
({stdout, stderr} = await promisify(execFile)(binary, ["--version"]));
|
|
68
|
+
}
|
|
69
|
+
if (stderr)
|
|
70
|
+
throw new Error(stderr);
|
|
71
|
+
|
|
72
|
+
return stdout.trim();
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
throw new Error(`${errMsg.browserNotInstalled}.\nBinary path: ${binary}\n${err}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @typedef {Object} webdriver
|
|
81
|
+
* @see https://www.selenium.dev/selenium/docs/api/javascript/index.html
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @typedef {Object} driverOptions
|
|
86
|
+
* @property {boolean} [headless=true] Run the browser in headless mode,
|
|
87
|
+
* or not. In Chromium >= 111, the
|
|
88
|
+
* {@link https://developer.chrome.com/articles/new-headless/ new headless mode}
|
|
89
|
+
* is used.
|
|
90
|
+
* @property {Array.<string>} [extensionPaths=[]] Loads extensions to the
|
|
91
|
+
* browser.
|
|
92
|
+
* @property {boolean} [incognito=false] Runs the browser in incognito mode,
|
|
93
|
+
* or not.
|
|
94
|
+
* @property {boolean} [insecure=false] Forces the browser to accept insecure
|
|
95
|
+
* certificates, or not.
|
|
96
|
+
* @property {Array.<string>} [extraArgs=[]] Additional arguments to start
|
|
97
|
+
* the browser with.
|
|
98
|
+
* @property {string} [customBrowserBinary] Path to the browser binary to be
|
|
99
|
+
* used, instead of the browser installed by installBrowser(). This option
|
|
100
|
+
* overrides the version parameter in getDriver().
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Installs the webdriver matching the browser version and runs the
|
|
105
|
+
* browser. If needed, the browser binary is also installed.
|
|
106
|
+
* @param {string} [version=latest] Either full version number or
|
|
107
|
+
* channel/release. Please find examples on the subclasses.
|
|
108
|
+
* @param {driverOptions} [options={}] Options to start the browser with.
|
|
109
|
+
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
110
|
+
* browser install files to complete. When set to 0 there is no time limit.
|
|
111
|
+
* @return {webdriver}
|
|
112
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
113
|
+
* download failed, Driver download failed, Unable to start driver.
|
|
114
|
+
*/
|
|
115
|
+
static async getDriver(version, options = {}, downloadTimeout = 0) {
|
|
116
|
+
// to be implemented by the subclass
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* By default, extensions are disabled in incognito mode. This function
|
|
121
|
+
* enables the extension when loaded in incognito.
|
|
122
|
+
* @param {webdriver} driver The driver controlling the browser.
|
|
123
|
+
* @param {string} extensionTitle Title of the extension to be enabled.
|
|
124
|
+
* @return {webdriver}
|
|
125
|
+
* @throws {Error} Unsupported browser version, Extension not found, HTML
|
|
126
|
+
* element not found.
|
|
127
|
+
*/
|
|
128
|
+
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
129
|
+
// Allowing the extension in incognito mode can't happen programmatically:
|
|
130
|
+
// https://stackoverflow.com/questions/57419654
|
|
131
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
|
|
132
|
+
// That is done through the UI, to be implemented by the subclass
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/chromium.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2006-present eyeo GmbH
|
|
3
|
+
*
|
|
4
|
+
* This module is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
|
|
21
|
+
import got from "got";
|
|
22
|
+
import webdriver from "selenium-webdriver";
|
|
23
|
+
import chrome from "selenium-webdriver/chrome.js";
|
|
24
|
+
import extractZip from "extract-zip";
|
|
25
|
+
|
|
26
|
+
import {Browser} from "./browser.js";
|
|
27
|
+
import {download, killDriverProcess, getMajorVersion, checkVersion,
|
|
28
|
+
checkPlatform, errMsg, snapshotsBaseDir, platformArch}
|
|
29
|
+
from "./utils.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Browser and webdriver functionality for Chromium.
|
|
33
|
+
* @hideconstructor
|
|
34
|
+
* @extends Browser
|
|
35
|
+
*/
|
|
36
|
+
export class Chromium extends Browser {
|
|
37
|
+
static #CHANNELS = ["latest", "beta", "dev"];
|
|
38
|
+
static #MAX_VERSION_DECREMENTS = 200;
|
|
39
|
+
|
|
40
|
+
static async #getVersionForChannel(channel) {
|
|
41
|
+
if (!Chromium.#CHANNELS.includes(channel))
|
|
42
|
+
return channel;
|
|
43
|
+
|
|
44
|
+
if (channel == "latest")
|
|
45
|
+
channel = "stable";
|
|
46
|
+
|
|
47
|
+
let os = {
|
|
48
|
+
"win32-ia32": "win",
|
|
49
|
+
"win32-x64": "win64",
|
|
50
|
+
"linux-x64": "linux",
|
|
51
|
+
"darwin-x64": "mac",
|
|
52
|
+
"darwin-arm64": "mac_arm64"
|
|
53
|
+
}[platformArch];
|
|
54
|
+
let url = `https://versionhistory.googleapis.com/v1/chrome/platforms/${os}/channels/${channel}/versions/all/releases`;
|
|
55
|
+
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
data = await got(url).json();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
|
|
62
|
+
}
|
|
63
|
+
let version;
|
|
64
|
+
let fraction;
|
|
65
|
+
for ({version, fraction} of data.releases) {
|
|
66
|
+
// Versions having small fractions may not appear on Chromium Dash
|
|
67
|
+
if (fraction > 0.0025)
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return version;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static #getBinaryPath(dir) {
|
|
75
|
+
checkPlatform();
|
|
76
|
+
return {
|
|
77
|
+
win32: path.join(dir, "chrome-win", "chrome.exe"),
|
|
78
|
+
linux: path.join(dir, "chrome-linux", "chrome"),
|
|
79
|
+
darwin: path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
|
|
80
|
+
"Chromium")
|
|
81
|
+
}[process.platform];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static async #getBase(chromiumVersion) {
|
|
85
|
+
let url;
|
|
86
|
+
let chromiumBase;
|
|
87
|
+
try {
|
|
88
|
+
let majorVersion = getMajorVersion(chromiumVersion);
|
|
89
|
+
if (majorVersion < 91) {
|
|
90
|
+
// Below v91, base branch position only exists once per milestone in
|
|
91
|
+
// the /fetch_milestones endpoint
|
|
92
|
+
url = "https://chromiumdash.appspot.com/fetch_milestones?only_branched=true";
|
|
93
|
+
let data = await got(url).json();
|
|
94
|
+
for (let {milestone, chromium_main_branch_position: base} of data) {
|
|
95
|
+
if (milestone == majorVersion) {
|
|
96
|
+
chromiumBase = base;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!chromiumBase)
|
|
101
|
+
throw new Error(`${errMsg.browserVersionCheck}: ${url}`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
url = `https://chromiumdash.appspot.com/fetch_version?version=${chromiumVersion}`;
|
|
105
|
+
({chromium_main_branch_position: chromiumBase} = await got(url).json());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
|
|
110
|
+
}
|
|
111
|
+
return parseInt(chromiumBase, 10);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static async #getInstalledBrowserInfo(binary) {
|
|
115
|
+
let installedVersion = await Chromium.getInstalledVersion(binary);
|
|
116
|
+
// Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
|
|
117
|
+
// Windows example: "114.0.5735.0"
|
|
118
|
+
let versionNumber = installedVersion.split(" ")[1] || installedVersion;
|
|
119
|
+
let base = await Chromium.#getBase(versionNumber);
|
|
120
|
+
return {binary, versionNumber, base};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Installs the browser. The Chromium executable gets extracted in the
|
|
125
|
+
* {@link snapshotsBaseDir} folder, ready to go.
|
|
126
|
+
* @param {string} [version=latest] Either "latest", "beta", "dev" or a full
|
|
127
|
+
* version number (i.e. "77.0.3865.0").
|
|
128
|
+
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
129
|
+
* install files to complete. When set to 0 there is no time limit.
|
|
130
|
+
* @return {BrowserBinary}
|
|
131
|
+
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
132
|
+
* download failed.
|
|
133
|
+
*/
|
|
134
|
+
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
135
|
+
const MIN_VERSION = process.arch == "arm64" ? 92 : 77;
|
|
136
|
+
|
|
137
|
+
let binary;
|
|
138
|
+
let versionNumber;
|
|
139
|
+
let base;
|
|
140
|
+
|
|
141
|
+
checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
|
|
142
|
+
versionNumber = await Chromium.#getVersionForChannel(version);
|
|
143
|
+
|
|
144
|
+
base = await Chromium.#getBase(versionNumber);
|
|
145
|
+
let startBase = base;
|
|
146
|
+
let [platformDir, fileName] = {
|
|
147
|
+
"win32-ia32": ["Win", "chrome-win.zip"],
|
|
148
|
+
"win32-x64": ["Win_x64", "chrome-win.zip"],
|
|
149
|
+
"linux-x64": ["Linux_x64", "chrome-linux.zip"],
|
|
150
|
+
"darwin-x64": ["Mac", "chrome-mac.zip"],
|
|
151
|
+
"darwin-arm64": ["Mac_Arm", "chrome-mac.zip"]
|
|
152
|
+
}[platformArch];
|
|
153
|
+
let archive;
|
|
154
|
+
let browserDir;
|
|
155
|
+
let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
|
|
156
|
+
|
|
157
|
+
while (true) {
|
|
158
|
+
browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
|
|
159
|
+
binary = Chromium.#getBinaryPath(browserDir);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await fs.promises.access(browserDir);
|
|
163
|
+
return {binary, versionNumber, base};
|
|
164
|
+
}
|
|
165
|
+
catch (e) {}
|
|
166
|
+
|
|
167
|
+
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
168
|
+
|
|
169
|
+
archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
|
|
170
|
+
let url = `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`;
|
|
171
|
+
try {
|
|
172
|
+
try {
|
|
173
|
+
await fs.promises.access(archive);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
await download(url, archive, downloadTimeout);
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
if (err.name == "HTTPError") {
|
|
182
|
+
// Chromium advises decrementing the branch_base_position when no
|
|
183
|
+
// matching build was found. See https://www.chromium.org/getting-involved/download-chromium
|
|
184
|
+
base--;
|
|
185
|
+
if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
|
|
186
|
+
throw new Error(`${errMsg.browserDownload}: Chromium base ${startBase}`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
throw new Error(`${errMsg.browserDownload}: ${url}\n${err}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await extractZip(archive, {dir: browserDir});
|
|
194
|
+
|
|
195
|
+
return {binary, versionNumber, base};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static async #installDriver(startBase) {
|
|
199
|
+
let [dir, zip, driverBinary] = {
|
|
200
|
+
"win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
201
|
+
"win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
202
|
+
"linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
|
|
203
|
+
"darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
|
|
204
|
+
"darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
|
|
205
|
+
}[platformArch];
|
|
206
|
+
|
|
207
|
+
let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
|
|
208
|
+
"chromedriver");
|
|
209
|
+
let archive = path.join(cacheDir, `${startBase}-${zip}`);
|
|
210
|
+
try {
|
|
211
|
+
await fs.promises.access(archive);
|
|
212
|
+
await extractZip(archive, {dir: cacheDir});
|
|
213
|
+
}
|
|
214
|
+
catch (e) { // zip file is either not cached or corrupted
|
|
215
|
+
let base = startBase;
|
|
216
|
+
while (true) {
|
|
217
|
+
let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
|
|
218
|
+
try {
|
|
219
|
+
await download(url, archive);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
if (err.name == "HTTPError") {
|
|
224
|
+
base--;
|
|
225
|
+
archive = path.join(cacheDir, `${base}-${zip}`);
|
|
226
|
+
if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
|
|
227
|
+
throw new Error(`${errMsg.driverDownload}: Chromium base ${startBase}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
throw new Error(`${errMsg.driverDownload}: ${url}\n${err}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
await extractZip(archive, {dir: cacheDir});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await killDriverProcess("chromedriver");
|
|
238
|
+
let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
|
|
239
|
+
await fs.promises.rm(driverPath, {force: true});
|
|
240
|
+
await extractZip(archive, {dir: cacheDir});
|
|
241
|
+
|
|
242
|
+
return driverPath;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** @see Browser.getDriver */
|
|
246
|
+
static async getDriver(version = "latest", {
|
|
247
|
+
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
248
|
+
extraArgs = [], customBrowserBinary
|
|
249
|
+
} = {}, downloadTimeout = 0) {
|
|
250
|
+
let {binary, versionNumber, base} = customBrowserBinary ?
|
|
251
|
+
await Chromium.#getInstalledBrowserInfo(customBrowserBinary) :
|
|
252
|
+
await Chromium.installBrowser(version, downloadTimeout);
|
|
253
|
+
let driverPath = await Chromium.#installDriver(base);
|
|
254
|
+
let serviceBuilder = new chrome.ServiceBuilder(driverPath);
|
|
255
|
+
let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
|
|
256
|
+
if (extensionPaths.length > 0)
|
|
257
|
+
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
258
|
+
if (headless) {
|
|
259
|
+
// New headless mode introduced in Chrome 111
|
|
260
|
+
// https://developer.chrome.com/articles/new-headless/
|
|
261
|
+
if (getMajorVersion(versionNumber) >= 111)
|
|
262
|
+
options.addArguments("headless=new");
|
|
263
|
+
else
|
|
264
|
+
options.headless();
|
|
265
|
+
}
|
|
266
|
+
if (insecure)
|
|
267
|
+
options.addArguments("ignore-certificate-errors");
|
|
268
|
+
if (incognito)
|
|
269
|
+
options.addArguments("incognito");
|
|
270
|
+
options.setChromeBinaryPath(binary);
|
|
271
|
+
|
|
272
|
+
let builder = new webdriver.Builder();
|
|
273
|
+
builder.forBrowser("chrome");
|
|
274
|
+
builder.setChromeOptions(options);
|
|
275
|
+
builder.setChromeService(serviceBuilder);
|
|
276
|
+
|
|
277
|
+
return builder.build();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** @see Browser.enableExtensionInIncognito */
|
|
281
|
+
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
282
|
+
let handle = await driver.getWindowHandle();
|
|
283
|
+
|
|
284
|
+
let version = getMajorVersion(
|
|
285
|
+
(await driver.getCapabilities()).getBrowserVersion());
|
|
286
|
+
if (version >= 115)
|
|
287
|
+
// On Chromium 115 opening chrome://extensions on the default tab causes
|
|
288
|
+
// WebDriverError: disconnected. Switching to a new window as a workaround
|
|
289
|
+
await driver.switchTo().newWindow("window");
|
|
290
|
+
|
|
291
|
+
await driver.navigate().to("chrome://extensions");
|
|
292
|
+
await driver.executeScript((...args) => {
|
|
293
|
+
let enable = () => document.querySelector("extensions-manager").shadowRoot
|
|
294
|
+
.querySelector("extensions-detail-view").shadowRoot
|
|
295
|
+
.getElementById("allow-incognito").shadowRoot
|
|
296
|
+
.getElementById("crToggle").click();
|
|
297
|
+
|
|
298
|
+
let extensions = document.querySelector("extensions-manager").shadowRoot
|
|
299
|
+
.getElementById("items-list").shadowRoot
|
|
300
|
+
.querySelectorAll("extensions-item");
|
|
301
|
+
|
|
302
|
+
return new Promise((resolve, reject) => {
|
|
303
|
+
let extensionDetails;
|
|
304
|
+
for (let {shadowRoot} of extensions) {
|
|
305
|
+
if (shadowRoot.getElementById("name").innerHTML != args[0])
|
|
306
|
+
continue;
|
|
307
|
+
|
|
308
|
+
extensionDetails = shadowRoot.getElementById("detailsButton");
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
if (!extensionDetails)
|
|
312
|
+
reject(`${args[1]}: ${args[0]}`);
|
|
313
|
+
|
|
314
|
+
extensionDetails.click();
|
|
315
|
+
setTimeout(() => resolve(enable()), 100);
|
|
316
|
+
});
|
|
317
|
+
}, extensionTitle, errMsg.extensionNotFound);
|
|
318
|
+
if (version >= 115)
|
|
319
|
+
// Closing the previously opened new window
|
|
320
|
+
await driver.close();
|
|
321
|
+
|
|
322
|
+
await driver.switchTo().window(handle);
|
|
323
|
+
}
|
|
324
|
+
}
|