@eyeo/get-browser-binary 0.1.1
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/.eslintignore +3 -0
- package/.eslintrc.json +41 -0
- package/.gitlab-ci.yml +75 -0
- package/README.md +65 -0
- package/RELEASE_NOTES.md +7 -0
- package/index.js +18 -0
- package/package.json +39 -0
- package/src/browsers.js +541 -0
- package/src/utils.js +80 -0
- package/test/.eslintrc.json +5 -0
- package/test/extension/.eslintrc.json +5 -0
- package/test/extension/background.js +3 -0
- package/test/extension/index.html +1 -0
- package/test/extension/manifest.json +9 -0
- package/test/runner.js +172 -0
package/.eslintignore
ADDED
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"eslint-config-eyeo"
|
|
4
|
+
],
|
|
5
|
+
"root": true,
|
|
6
|
+
"env": {
|
|
7
|
+
"es2022": true,
|
|
8
|
+
"node": true
|
|
9
|
+
},
|
|
10
|
+
"rules": {
|
|
11
|
+
"brace-style": ["error", "stroustrup"],
|
|
12
|
+
"curly": [
|
|
13
|
+
"error",
|
|
14
|
+
"multi-or-nest",
|
|
15
|
+
"consistent"
|
|
16
|
+
],
|
|
17
|
+
"max-len": [
|
|
18
|
+
"error",
|
|
19
|
+
{
|
|
20
|
+
"ignoreUrls": true,
|
|
21
|
+
"ignoreRegExpLiterals": true,
|
|
22
|
+
"ignoreTemplateLiterals": true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"valid-jsdoc": 0
|
|
26
|
+
},
|
|
27
|
+
"parserOptions": {
|
|
28
|
+
"sourceType": "module",
|
|
29
|
+
"ecmaVersion": 2022
|
|
30
|
+
},
|
|
31
|
+
"overrides": [
|
|
32
|
+
{
|
|
33
|
+
"files": [
|
|
34
|
+
"background.js"
|
|
35
|
+
],
|
|
36
|
+
"parserOptions": {
|
|
37
|
+
"sourceType": "script"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
package/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
default:
|
|
2
|
+
image: registry.gitlab.com/eyeo/docker/adblockplus-ci:node16-no-python
|
|
3
|
+
interruptible: true
|
|
4
|
+
|
|
5
|
+
stages:
|
|
6
|
+
- test
|
|
7
|
+
- docs
|
|
8
|
+
|
|
9
|
+
variables:
|
|
10
|
+
npm_config_audit: "false"
|
|
11
|
+
npm_config_fund: "false"
|
|
12
|
+
npm_config_prefer_offline: "true"
|
|
13
|
+
|
|
14
|
+
cache:
|
|
15
|
+
- key:
|
|
16
|
+
prefix: $CI_JOB_IMAGE
|
|
17
|
+
files:
|
|
18
|
+
- package-lock.json
|
|
19
|
+
paths:
|
|
20
|
+
- node_modules/
|
|
21
|
+
|
|
22
|
+
test:linux:
|
|
23
|
+
stage: test
|
|
24
|
+
before_script:
|
|
25
|
+
- apt-get update && apt-get install -y procps
|
|
26
|
+
- npm install
|
|
27
|
+
# https://www.how2shout.com/linux/install-microsoft-edge-on-linux/
|
|
28
|
+
- wget https://packages.microsoft.com/keys/microsoft.asc
|
|
29
|
+
- cat microsoft.asc | gpg --dearmor > microsoft.gpg
|
|
30
|
+
- install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/
|
|
31
|
+
- echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list
|
|
32
|
+
- rm microsoft.*
|
|
33
|
+
- apt-get update && apt-get install -y microsoft-edge-stable
|
|
34
|
+
script:
|
|
35
|
+
- npm run lint
|
|
36
|
+
- xvfb-run -a npm test -- -g "chromium (latest|beta)"
|
|
37
|
+
- xvfb-run -a npm test -- -g "chromium dev"
|
|
38
|
+
- xvfb-run -a npm test -- -g "chromium 77"
|
|
39
|
+
- xvfb-run -a npm test -- -g "(edge|firefox)"
|
|
40
|
+
|
|
41
|
+
test:windows:
|
|
42
|
+
stage: test
|
|
43
|
+
variables:
|
|
44
|
+
CI_PROJECT_ID_MAINSTREAM: 36688302
|
|
45
|
+
before_script:
|
|
46
|
+
- Invoke-WebRequest
|
|
47
|
+
-Uri "${Env:CI_API_V4_URL}/projects/${Env:CI_PROJECT_ID_MAINSTREAM}/packages/generic/microsoft-edge/79.0.309/MicrosoftEdgeEnterpriseX64.msi"
|
|
48
|
+
-Headers @{'JOB-TOKEN' = $Env:CI_JOB_TOKEN}
|
|
49
|
+
-OutFile 'MicrosoftEdgeEnterpriseX64.msi'
|
|
50
|
+
- Start-Process msiexec
|
|
51
|
+
-ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
|
|
52
|
+
- choco upgrade -y nodejs --version 16.10.0
|
|
53
|
+
- npm install
|
|
54
|
+
script:
|
|
55
|
+
- npm test -- -g "chromium latest"
|
|
56
|
+
- npm test -- -g "chromium beta"
|
|
57
|
+
- npm test -- -g "chromium dev"
|
|
58
|
+
- npm test -- -g "chromium 77"
|
|
59
|
+
- npm test -- -g "edge"
|
|
60
|
+
tags:
|
|
61
|
+
- shared-windows
|
|
62
|
+
- windows
|
|
63
|
+
- windows-1809
|
|
64
|
+
cache: {}
|
|
65
|
+
|
|
66
|
+
docs:
|
|
67
|
+
stage: docs
|
|
68
|
+
needs: []
|
|
69
|
+
before_script:
|
|
70
|
+
- npm install
|
|
71
|
+
script:
|
|
72
|
+
- npm run docs
|
|
73
|
+
artifacts:
|
|
74
|
+
paths:
|
|
75
|
+
- docs/
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# get-browser-binary
|
|
2
|
+
|
|
3
|
+
Download specific browser versions for Chromium, Firefox and Edge, and their
|
|
4
|
+
matching [selenium webdriver](https://www.selenium.dev/selenium/docs/api/javascript/index.html).
|
|
5
|
+
|
|
6
|
+
Note: Edge download is not implemented, and it is assumed to be already
|
|
7
|
+
installed on the running system. In that case, the matching `msedgedriver`
|
|
8
|
+
will be downloaded accordingly.
|
|
9
|
+
|
|
10
|
+
## Getting started
|
|
11
|
+
|
|
12
|
+
The sample below shows how to download the latest Chromium and run it using
|
|
13
|
+
selenium webdriver:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import {BROWSERS} from "@eyeo/get-browser-binary";
|
|
17
|
+
|
|
18
|
+
(async function example() {
|
|
19
|
+
let {chromium} = BROWSERS;
|
|
20
|
+
|
|
21
|
+
let {binary} = await chromium.downloadBinary();
|
|
22
|
+
console.log(`Chromium binary downloaded to ${binary}`);
|
|
23
|
+
|
|
24
|
+
let driver = await chromium.getDriver();
|
|
25
|
+
await driver.navigate().to("https://example.com/");
|
|
26
|
+
await driver.quit();
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
[test/runner.js](https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/blob/main/test/runner.js)
|
|
32
|
+
provides other usage examples of the library.
|
|
33
|
+
|
|
34
|
+
For more information, please refer to the [API documention](https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/jobs/artifacts/main/file/docs/index.html?job=docs).
|
|
35
|
+
If you are already on the documentation page, you may find the API contents on
|
|
36
|
+
the right side.
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
### Prerequisites
|
|
41
|
+
|
|
42
|
+
- Node >= 16.10.0
|
|
43
|
+
- npm >= 7
|
|
44
|
+
|
|
45
|
+
### Installing/Updating dependencies
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
npm install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Testing
|
|
52
|
+
|
|
53
|
+
The `grep` option filters the tests to run with a regular expression. Example:
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
npm test -- -g "chromium latest"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Note: Running all Chromium tests at once is currently not working.
|
|
60
|
+
|
|
61
|
+
## Building the documentation
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
npm run docs
|
|
65
|
+
```
|
package/RELEASE_NOTES.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
export * from "./src/browsers.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eyeo/get-browser-binary",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Download browser binaries and matching webdrivers",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://gitlab.com/eyeo/developer-experience/get-browser-binary"
|
|
8
|
+
},
|
|
9
|
+
"author": "eyeo GmbH",
|
|
10
|
+
"license": "GPL-3.0",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=16.10.0",
|
|
13
|
+
"npm": ">=7"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chromedriver": "^90.0.1",
|
|
19
|
+
"dmg": "^0.1.0",
|
|
20
|
+
"extract-zip": "^2.0.1",
|
|
21
|
+
"fs-extra": "^10.0.0",
|
|
22
|
+
"geckodriver": "^2.0.4",
|
|
23
|
+
"got": "^11.8.2",
|
|
24
|
+
"msedgedriver": "^91.0.0",
|
|
25
|
+
"selenium-webdriver": "^4.2.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"eslint": "^8.17.0",
|
|
29
|
+
"eslint-config-eyeo": "^3.2.0",
|
|
30
|
+
"expect": "^25.5.0",
|
|
31
|
+
"jsdoc": "^3.6.10",
|
|
32
|
+
"mocha": "^10.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"docs": "jsdoc --readme README.md --destination docs src/browsers.js",
|
|
36
|
+
"lint": "eslint --ext js .",
|
|
37
|
+
"test": "mocha test/runner.js --"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/browsers.js
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2006-present eyeo GmbH
|
|
3
|
+
*
|
|
4
|
+
* This module is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import path from "path";
|
|
19
|
+
import {exec, execFile} from "child_process";
|
|
20
|
+
import {promisify} from "util";
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
|
|
23
|
+
import got from "got";
|
|
24
|
+
import webdriver from "selenium-webdriver";
|
|
25
|
+
import chrome from "selenium-webdriver/chrome.js";
|
|
26
|
+
import firefox from "selenium-webdriver/firefox.js";
|
|
27
|
+
import command from "selenium-webdriver/lib/command.js";
|
|
28
|
+
import extractZip from "extract-zip";
|
|
29
|
+
|
|
30
|
+
import {download, extractTar, extractDmg, runWinInstaller, getBrowserVersion}
|
|
31
|
+
from "./utils.js";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Root folder where browser and webdriver binaries get downloaded.
|
|
35
|
+
* @type {string}
|
|
36
|
+
*/
|
|
37
|
+
export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
|
|
38
|
+
|
|
39
|
+
let {until, By} = webdriver;
|
|
40
|
+
const ERROR_INCOGNITO_NOT_SUPPORTED = "Incognito mode is not supported";
|
|
41
|
+
const ERROR_DOWNLOAD_NOT_SUPPORTED =
|
|
42
|
+
"Downloading this browser is not supported";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Base class for browser download functionality. Please see subclasses for
|
|
46
|
+
* browser specific details.
|
|
47
|
+
* @hideconstructor
|
|
48
|
+
*/
|
|
49
|
+
class Browser {
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} BrowserBinary
|
|
52
|
+
* @property {string} binary - The path to the Chromium binary.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Downloads the browser binary file.
|
|
57
|
+
* @param {string} version - Either full version number or channel/release.
|
|
58
|
+
* Please find examples on the subclasses.
|
|
59
|
+
* @return {BrowserBinary}
|
|
60
|
+
*/
|
|
61
|
+
static async downloadBinary(version) {
|
|
62
|
+
throw new Error(ERROR_DOWNLOAD_NOT_SUPPORTED);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {Object} webdriver
|
|
67
|
+
* @see https://www.selenium.dev/selenium/docs/api/javascript/index.html
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Installs the webdriver matching the browser version and runs the
|
|
72
|
+
* browser. If needed, the browser binary is also installed.
|
|
73
|
+
* @param {string} version - Either full version number or channel/release.
|
|
74
|
+
* Please find examples on the subclasses. On Edge this parameter has
|
|
75
|
+
* no effect.
|
|
76
|
+
* @param {boolean?} headless - Run the browser in headless mode, or not.
|
|
77
|
+
* @param {Array.<string>?} extensionPaths - Loads extensions to the running
|
|
78
|
+
* browser.
|
|
79
|
+
* @param {boolean?} incognito - Runs the browser in incognito mode, or not.
|
|
80
|
+
* @param {boolean?} insecure - Forces the browser to accept insecure
|
|
81
|
+
certificates, or not.
|
|
82
|
+
* @return {webdriver}
|
|
83
|
+
*/
|
|
84
|
+
static async getDriver(version, headless = true, extensionPaths = [],
|
|
85
|
+
incognito = false, insecure = false) {
|
|
86
|
+
// to be implemented by the subclass
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* By default, extensions are disabled in incognito mode. This function
|
|
91
|
+
* enables the extension when loaded in incognito.
|
|
92
|
+
* @param {webdriver} driver - The driver controlling the browser.
|
|
93
|
+
* @param {string} extensionTitle - Title of the extebsion to be enabled.
|
|
94
|
+
* @return {webdriver}
|
|
95
|
+
*/
|
|
96
|
+
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
97
|
+
throw new Error(ERROR_INCOGNITO_NOT_SUPPORTED);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Download functionality for Chromium. This class can be used statically.
|
|
103
|
+
* @hideconstructor
|
|
104
|
+
* @extends Browser
|
|
105
|
+
*/
|
|
106
|
+
class Chromium extends Browser {
|
|
107
|
+
static #DRIVER = "chromedriver";
|
|
108
|
+
|
|
109
|
+
static async #getBranchBasePosition(version) {
|
|
110
|
+
let data = await got(`https://omahaproxy.appspot.com/deps.json?version=${version}`).json();
|
|
111
|
+
return data.chromium_base_position;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static async #getLatestVersion(channel = "stable") {
|
|
115
|
+
let os = process.platform;
|
|
116
|
+
if (os == "win32")
|
|
117
|
+
os = process.arch == "x64" ? "win64" : "win";
|
|
118
|
+
else if (os == "darwin")
|
|
119
|
+
os = process.arch == "arm64" ? "mac_arm64" : "mac";
|
|
120
|
+
|
|
121
|
+
let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
|
|
122
|
+
let release = data[0].versions.find(ver => ver.channel == channel);
|
|
123
|
+
let {current_version: version, branch_base_position: base} = release;
|
|
124
|
+
|
|
125
|
+
if (release.true_branch && release.true_branch.includes("_")) {
|
|
126
|
+
// A wrong base may be caused by a mini-branch (patched) release
|
|
127
|
+
// In that case, the base is taken from the unpatched version
|
|
128
|
+
version = [...version.split(".").slice(0, 3), "0"].join(".");
|
|
129
|
+
base = await Chromium.#getBranchBasePosition(version);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {version, base};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static #getChromiumBinary(dir) {
|
|
136
|
+
switch (process.platform) {
|
|
137
|
+
case "win32":
|
|
138
|
+
return path.join(dir, "chrome-win", "chrome.exe");
|
|
139
|
+
case "linux":
|
|
140
|
+
return path.join(dir, "chrome-linux", "chrome");
|
|
141
|
+
case "darwin":
|
|
142
|
+
return path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
|
|
143
|
+
"Chromium");
|
|
144
|
+
default:
|
|
145
|
+
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static async #downloadChromium(chromiumRevision) {
|
|
150
|
+
const MAX_VERSION_DECREMENTS = 50;
|
|
151
|
+
|
|
152
|
+
let revision = parseInt(chromiumRevision, 10);
|
|
153
|
+
let startingRevision = revision;
|
|
154
|
+
let platform = `${process.platform}-${process.arch}`;
|
|
155
|
+
|
|
156
|
+
let buildTypes = {
|
|
157
|
+
"win32-ia32": ["Win", "chrome-win.zip"],
|
|
158
|
+
"win32-x64": ["Win_x64", "chrome-win.zip"],
|
|
159
|
+
"linux-x64": ["Linux_x64", "chrome-linux.zip"],
|
|
160
|
+
"darwin-x64": ["Mac", "chrome-mac.zip"],
|
|
161
|
+
"dawrin-arm64": ["Mac_Arm", "chrome-mac.zip"]
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
let [platformDir, fileName] = buildTypes[platform];
|
|
165
|
+
let archive = null;
|
|
166
|
+
let browserDir = null;
|
|
167
|
+
let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
|
|
168
|
+
|
|
169
|
+
while (true) {
|
|
170
|
+
browserDir = path.join(snapshotsDir, `chromium-${platform}-${revision}`);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await fs.promises.access(browserDir);
|
|
174
|
+
return {binary: Chromium.#getChromiumBinary(browserDir), revision};
|
|
175
|
+
}
|
|
176
|
+
catch (e) {}
|
|
177
|
+
|
|
178
|
+
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
179
|
+
|
|
180
|
+
archive = path.join(snapshotsDir, "cache", `${revision}-${fileName}`);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
try {
|
|
184
|
+
await fs.promises.access(archive);
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
await download(
|
|
188
|
+
`https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${revision}%2F${fileName}?alt=media`,
|
|
189
|
+
archive);
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
// Chromium advises decrementing the branch_base_position when no
|
|
195
|
+
// matching build was found. See https://www.chromium.org/getting-involved/download-chromium
|
|
196
|
+
revision--;
|
|
197
|
+
if (revision <= startingRevision - MAX_VERSION_DECREMENTS)
|
|
198
|
+
throw new Error(`No Chromium package found for ${startingRevision}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await extractZip(archive, {dir: browserDir});
|
|
203
|
+
return {binary: Chromium.#getChromiumBinary(browserDir), revision};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Downloads the browser binary file.
|
|
208
|
+
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
209
|
+
* number (i.e. "77.0.3865.0"). Defaults to "latest".
|
|
210
|
+
* @return {BrowserBinary}
|
|
211
|
+
*/
|
|
212
|
+
static async downloadBinary(version) {
|
|
213
|
+
let base;
|
|
214
|
+
if (version && !(version == "beta" || version == "dev"))
|
|
215
|
+
base = await Chromium.#getBranchBasePosition(version);
|
|
216
|
+
else
|
|
217
|
+
({version, base} = await Chromium.#getLatestVersion(version));
|
|
218
|
+
|
|
219
|
+
let {binary, revision} = await Chromium.#downloadChromium(base);
|
|
220
|
+
return {binary, downloadedVersion: version, revision};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static async #installDriver(revision, version) {
|
|
224
|
+
let platform = `${process.platform}-${process.arch}`;
|
|
225
|
+
let buildTypes = {
|
|
226
|
+
"win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
227
|
+
"win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
|
|
228
|
+
"linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
|
|
229
|
+
"darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
|
|
230
|
+
"darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
|
|
231
|
+
};
|
|
232
|
+
let [dir, zip, driver] = buildTypes[platform];
|
|
233
|
+
|
|
234
|
+
let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache", version);
|
|
235
|
+
let destinationDir = path.join(process.cwd(), "node_modules",
|
|
236
|
+
Chromium.#DRIVER, "lib", Chromium.#DRIVER);
|
|
237
|
+
let archive = path.join(cacheDir, `${revision}-${zip}`);
|
|
238
|
+
|
|
239
|
+
await download(`https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${revision}/${zip}`,
|
|
240
|
+
archive);
|
|
241
|
+
await extractZip(archive, {dir: cacheDir});
|
|
242
|
+
await fs.promises.mkdir(destinationDir, {recursive: true});
|
|
243
|
+
try {
|
|
244
|
+
await fs.promises.copyFile(path.join(cacheDir, zip.split(".")[0], driver),
|
|
245
|
+
path.join(destinationDir, driver));
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/3
|
|
249
|
+
if (!err.toString().includes("copyfile"))
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** @see Browser.getDriver */
|
|
255
|
+
static async getDriver(version, headless = true, extensionPaths = [],
|
|
256
|
+
incognito = false, insecure = false) {
|
|
257
|
+
if (incognito)
|
|
258
|
+
throw new Error(ERROR_INCOGNITO_NOT_SUPPORTED);
|
|
259
|
+
|
|
260
|
+
let {binary, revision, downloadedVersion} =
|
|
261
|
+
await Chromium.downloadBinary(version);
|
|
262
|
+
await Chromium.#installDriver(revision, downloadedVersion);
|
|
263
|
+
|
|
264
|
+
let options = new chrome.Options().addArguments("no-sandbox");
|
|
265
|
+
if (extensionPaths.length > 0)
|
|
266
|
+
options.addArguments(`load-extension=${extensionPaths.join(",")}`);
|
|
267
|
+
if (headless)
|
|
268
|
+
options.headless();
|
|
269
|
+
if (insecure)
|
|
270
|
+
options.addArguments("ignore-certificate-errors");
|
|
271
|
+
options.setChromeBinaryPath(binary);
|
|
272
|
+
|
|
273
|
+
let builder = new webdriver.Builder();
|
|
274
|
+
builder.forBrowser("chrome");
|
|
275
|
+
builder.setChromeOptions(options);
|
|
276
|
+
|
|
277
|
+
return builder.build();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Download functionality for Firefox. This class can be used statically.
|
|
283
|
+
* @hideconstructor
|
|
284
|
+
* @extends Browser
|
|
285
|
+
*/
|
|
286
|
+
class Firefox {
|
|
287
|
+
static async #getLatestVersion(branch) {
|
|
288
|
+
let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
|
|
289
|
+
return branch == "beta" ?
|
|
290
|
+
data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static #getFirefoxBinary(dir) {
|
|
294
|
+
switch (process.platform) {
|
|
295
|
+
case "win32":
|
|
296
|
+
return path.join(dir, "core", "firefox.exe");
|
|
297
|
+
case "linux":
|
|
298
|
+
return path.join(dir, "firefox", "firefox");
|
|
299
|
+
case "darwin":
|
|
300
|
+
return path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox");
|
|
301
|
+
default:
|
|
302
|
+
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
static #extractFirefoxArchive(archive, dir) {
|
|
307
|
+
switch (process.platform) {
|
|
308
|
+
case "win32":
|
|
309
|
+
return runWinInstaller(archive, dir);
|
|
310
|
+
case "linux":
|
|
311
|
+
return extractTar(archive, dir);
|
|
312
|
+
case "darwin":
|
|
313
|
+
return extractDmg(archive, dir);
|
|
314
|
+
default:
|
|
315
|
+
throw new Error(`Unexpected platform: ${process.platform}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
static async #downloadFirefox(version) {
|
|
320
|
+
let {platform} = process;
|
|
321
|
+
if (platform == "win32")
|
|
322
|
+
platform += "-" + process.arch;
|
|
323
|
+
let buildTypes = {
|
|
324
|
+
"win32-ia32": ["win32", `Firefox Setup ${version}.exe`],
|
|
325
|
+
"win32-x64": ["win64", `Firefox Setup ${version}.exe`],
|
|
326
|
+
"linux": ["linux-x86_64", `firefox-${version}.tar.bz2`],
|
|
327
|
+
"darwin": ["mac", `Firefox ${version}.dmg`]
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
|
|
331
|
+
let browserDir = path.join(snapshotsDir, `firefox-${platform}-${version}`);
|
|
332
|
+
try {
|
|
333
|
+
await fs.promises.access(browserDir);
|
|
334
|
+
return Firefox.#getFirefoxBinary(browserDir);
|
|
335
|
+
}
|
|
336
|
+
catch (e) {}
|
|
337
|
+
|
|
338
|
+
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
339
|
+
|
|
340
|
+
let [buildPlatform, fileName] = buildTypes[platform];
|
|
341
|
+
let archive = path.join(snapshotsDir, "cache", fileName);
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
await fs.promises.access(archive);
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
let url = `https://archive.mozilla.org/pub/firefox/releases/${version}/${buildPlatform}/en-US/${fileName}`;
|
|
348
|
+
await download(url, archive);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
await Firefox.#extractFirefoxArchive(archive, browserDir);
|
|
352
|
+
return Firefox.#getFirefoxBinary(browserDir);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Downloads the browser binary file
|
|
357
|
+
* @param {string} version - Either "latest", "beta" or a full version
|
|
358
|
+
* number (i.e. "68.0"). Defaults to "latest".
|
|
359
|
+
* @return {BrowserBinary}
|
|
360
|
+
*/
|
|
361
|
+
static async downloadBinary(version) {
|
|
362
|
+
if (!version || version == "beta")
|
|
363
|
+
version = await Firefox.#getLatestVersion(version);
|
|
364
|
+
|
|
365
|
+
let binary = await Firefox.#downloadFirefox(version);
|
|
366
|
+
return {binary};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** @see Browser.getDriver */
|
|
370
|
+
static async getDriver(version, headless = true, extensionPaths = [],
|
|
371
|
+
incognito = false, insecure = false) {
|
|
372
|
+
let {binary} = await Firefox.downloadBinary(version);
|
|
373
|
+
|
|
374
|
+
let options = new firefox.Options();
|
|
375
|
+
if (headless)
|
|
376
|
+
options.headless();
|
|
377
|
+
if (incognito)
|
|
378
|
+
options.addArguments("--private");
|
|
379
|
+
if (insecure)
|
|
380
|
+
options.set("acceptInsecureCerts", true);
|
|
381
|
+
options.setBinary(binary);
|
|
382
|
+
|
|
383
|
+
let driver = await new webdriver.Builder()
|
|
384
|
+
.forBrowser("firefox")
|
|
385
|
+
.setFirefoxOptions(options)
|
|
386
|
+
.build();
|
|
387
|
+
|
|
388
|
+
for (let extensionPath of extensionPaths) {
|
|
389
|
+
await driver.execute(
|
|
390
|
+
new command.Command("install addon")
|
|
391
|
+
.setParameter("path", extensionPath)
|
|
392
|
+
.setParameter("temporary", true)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return driver;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** @see Browser.enableExtensionInIncognito */
|
|
399
|
+
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
400
|
+
// Allowing the extension in private browsing can't happen programmatically:
|
|
401
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
|
|
402
|
+
// Therefore, that is done through the UI
|
|
403
|
+
let version = await getBrowserVersion(driver);
|
|
404
|
+
if (version < 87) {
|
|
405
|
+
// The UI workaround assumes web elements only present on Firefox >= 87
|
|
406
|
+
throw new Error(`Only supported on Firefox >= 87. Current version: ${version}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
await driver.navigate().to("about:addons");
|
|
410
|
+
await driver.wait(until.elementLocated(By.name("extension")), 1000).click();
|
|
411
|
+
|
|
412
|
+
for (let elem of await driver.findElements(By.className("card addon"))) {
|
|
413
|
+
let text = await elem.getAttribute("innerHTML");
|
|
414
|
+
if (!text.includes(extensionTitle))
|
|
415
|
+
continue;
|
|
416
|
+
|
|
417
|
+
await elem.click();
|
|
418
|
+
return await driver.findElement(By.name("private-browsing")).click();
|
|
419
|
+
}
|
|
420
|
+
throw new Error(`Extension "${extensionTitle}" not found`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Download functionality for Edge. This class can be used statically.
|
|
426
|
+
* @hideconstructor
|
|
427
|
+
* @extends Browser
|
|
428
|
+
*/
|
|
429
|
+
class Edge {
|
|
430
|
+
static #DRIVER = "msedgedriver";
|
|
431
|
+
|
|
432
|
+
static async #installDriver() {
|
|
433
|
+
let stdout;
|
|
434
|
+
if (process.platform == "win32") {
|
|
435
|
+
let cmd = "(Get-ItemProperty ${Env:ProgramFiles(x86)}\\Microsoft\\" +
|
|
436
|
+
"Edge\\Application\\msedge.exe).VersionInfo.ProductVersion";
|
|
437
|
+
({stdout} = await promisify(exec)(cmd, {shell: "powershell.exe"}));
|
|
438
|
+
}
|
|
439
|
+
else if (process.platform == "darwin") {
|
|
440
|
+
({stdout} = await promisify(execFile)(
|
|
441
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
442
|
+
["--version"]
|
|
443
|
+
));
|
|
444
|
+
}
|
|
445
|
+
else if (process.platform == "linux") {
|
|
446
|
+
({stdout} = await promisify(execFile)("microsoft-edge", ["--version"]));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let version = stdout.trim().replace(/.*\s/, "");
|
|
450
|
+
if (!version)
|
|
451
|
+
throw new Error("Edge is not installed");
|
|
452
|
+
|
|
453
|
+
// Based on "node_modules/msedgedriver/install.js", adding a fallback
|
|
454
|
+
// mechanism when msedgedriver doesn't exist for the latest Edge version.
|
|
455
|
+
let platform = `${process.platform}-${process.arch}`;
|
|
456
|
+
let buildTypes = {
|
|
457
|
+
"win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
|
|
458
|
+
"win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
|
|
459
|
+
"linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
|
|
460
|
+
"darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
|
|
461
|
+
"darwin-arm64": ["edgedriver_arm64.zip", "msedgedriver"]
|
|
462
|
+
};
|
|
463
|
+
let [zip, driver] = buildTypes[platform];
|
|
464
|
+
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache");
|
|
465
|
+
let driverBinDir = path.join(process.cwd(), "node_modules", Edge.#DRIVER,
|
|
466
|
+
"bin");
|
|
467
|
+
let driverLibDir = path.join(process.cwd(), "node_modules", Edge.#DRIVER,
|
|
468
|
+
"lib", Edge.#DRIVER);
|
|
469
|
+
let archive = path.join(cacheDir, `${version}-${zip}`);
|
|
470
|
+
|
|
471
|
+
let vSplit = version.split(".");
|
|
472
|
+
let lastNum = parseInt(vSplit[3], 10);
|
|
473
|
+
while (lastNum >= 0) {
|
|
474
|
+
try {
|
|
475
|
+
let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
|
|
476
|
+
await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
|
|
477
|
+
archive);
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
lastNum--;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (lastNum < 0)
|
|
486
|
+
throw new Error(`msedgedriver was not found for Edge ${version}`);
|
|
487
|
+
|
|
488
|
+
await extractZip(archive, {dir: cacheDir});
|
|
489
|
+
for (let destinationDir of [driverBinDir, driverLibDir]) {
|
|
490
|
+
await fs.promises.mkdir(destinationDir, {recursive: true});
|
|
491
|
+
try {
|
|
492
|
+
await fs.promises.copyFile(path.join(cacheDir, driver),
|
|
493
|
+
path.join(destinationDir, driver));
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/3
|
|
497
|
+
if (!err.toString().includes("copyfile"))
|
|
498
|
+
throw err;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** @see Browser.getDriver */
|
|
504
|
+
static async getDriver(version, headless = true, extensionPaths = [],
|
|
505
|
+
incognito = false, insecure = false) {
|
|
506
|
+
if (incognito)
|
|
507
|
+
throw new Error(ERROR_INCOGNITO_NOT_SUPPORTED);
|
|
508
|
+
|
|
509
|
+
await Edge.#installDriver();
|
|
510
|
+
|
|
511
|
+
let args = ["no-sandbox"];
|
|
512
|
+
if (headless)
|
|
513
|
+
args.push("headless");
|
|
514
|
+
if (extensionPaths.length > 0)
|
|
515
|
+
args.push(`load-extension=${extensionPaths.join(",")}`);
|
|
516
|
+
|
|
517
|
+
let builder = new webdriver.Builder();
|
|
518
|
+
builder.forBrowser("MicrosoftEdge");
|
|
519
|
+
builder.withCapabilities({
|
|
520
|
+
"browserName": "MicrosoftEdge",
|
|
521
|
+
"ms:edgeChromium": true,
|
|
522
|
+
"ms:edgeOptions": {args},
|
|
523
|
+
"acceptInsecureCerts": insecure
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return builder.build();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @type {Object}
|
|
533
|
+
* @property {Chromium} chromium - Download functionality for Chromium.
|
|
534
|
+
* @property {Firefox} firefox - Download functionality for Firefox.
|
|
535
|
+
* @property {Edge} edge - Download functionality for Edge.
|
|
536
|
+
*/
|
|
537
|
+
export const BROWSERS = {
|
|
538
|
+
chromium: Chromium,
|
|
539
|
+
firefox: Firefox,
|
|
540
|
+
edge: Edge
|
|
541
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
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 fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import {pipeline} from "stream";
|
|
21
|
+
import {promisify} from "util";
|
|
22
|
+
import {exec} from "child_process";
|
|
23
|
+
|
|
24
|
+
import got from "got";
|
|
25
|
+
import fsExtra from "fs-extra";
|
|
26
|
+
import dmg from "dmg";
|
|
27
|
+
|
|
28
|
+
export async function download(url, destFile) {
|
|
29
|
+
let cacheDir = path.dirname(destFile);
|
|
30
|
+
|
|
31
|
+
await fs.promises.mkdir(cacheDir, {recursive: true});
|
|
32
|
+
|
|
33
|
+
let tempDest = `${destFile}-${process.pid}`;
|
|
34
|
+
let writable = fs.createWriteStream(tempDest);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await promisify(pipeline)(got.stream(url), writable);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
await fs.promises.rm(tempDest, {recursive: true});
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await fs.promises.rename(tempDest, destFile);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function extractTar(archive, dir) {
|
|
48
|
+
await fs.promises.mkdir(dir);
|
|
49
|
+
await promisify(exec)(["tar", "-jxf", archive, "-C", dir].join(" "));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function extractDmg(archive, dir) {
|
|
53
|
+
let mpath = await promisify(dmg.mount)(archive);
|
|
54
|
+
let files = await fs.promises.readdir(mpath);
|
|
55
|
+
let target = files.find(file => path.extname(file) == ".app");
|
|
56
|
+
let source = path.join(mpath, target);
|
|
57
|
+
await fs.promises.mkdir(dir);
|
|
58
|
+
try {
|
|
59
|
+
await fsExtra.copy(source, path.join(dir, target));
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
try {
|
|
63
|
+
await promisify(dmg.unmount)(mpath);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.error(`Error unmounting DMG: ${err}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function runWinInstaller(archive, dir) {
|
|
72
|
+
// Procedure inspired from mozinstall. Uninstaller will also need to be run.
|
|
73
|
+
// https://hg.mozilla.org/mozilla-central/file/tip/testing/mozbase/mozinstall/mozinstall/mozinstall.py
|
|
74
|
+
await promisify(exec)(`"${archive}" /extractdir=${dir}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function getBrowserVersion(driver) {
|
|
78
|
+
let version = (await driver.getCapabilities()).getBrowserVersion();
|
|
79
|
+
return parseInt(version.split(".")[0], 10);
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1>Browser download test extension</h1>
|
package/test/runner.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
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 fs from "fs";
|
|
19
|
+
import expect from "expect";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import {exec} from "child_process";
|
|
22
|
+
import {promisify} from "util";
|
|
23
|
+
|
|
24
|
+
import {BROWSERS, snapshotsBaseDir} from "../src/browsers.js";
|
|
25
|
+
|
|
26
|
+
// Required to start the driver on some platforms (e.g. Windows).
|
|
27
|
+
import "chromedriver";
|
|
28
|
+
import "geckodriver";
|
|
29
|
+
import "msedgedriver";
|
|
30
|
+
|
|
31
|
+
const VERSIONS = {
|
|
32
|
+
chromium: [void 0, "beta", "dev", "77.0.3865.0"],
|
|
33
|
+
firefox: [void 0, "beta", "68.0"],
|
|
34
|
+
edge: [void 0]
|
|
35
|
+
};
|
|
36
|
+
let extensionPaths = [path.resolve(process.cwd(), "test", "extension")];
|
|
37
|
+
|
|
38
|
+
async function switchToHandle(driver, testFn) {
|
|
39
|
+
for (let handle of await driver.getAllWindowHandles()) {
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
await driver.switchTo().window(handle);
|
|
43
|
+
url = await driver.getCurrentUrl();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (testFn(url))
|
|
50
|
+
return handle;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getHandle(driver, page) {
|
|
55
|
+
let url;
|
|
56
|
+
let handle = await driver.wait(() => switchToHandle(driver, handleUrl => {
|
|
57
|
+
if (!handleUrl)
|
|
58
|
+
return false;
|
|
59
|
+
|
|
60
|
+
url = new URL(handleUrl);
|
|
61
|
+
return url.pathname == page;
|
|
62
|
+
}), 8000, `${page} did not open`);
|
|
63
|
+
|
|
64
|
+
return handle;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function killDriverProcess() {
|
|
68
|
+
for (let name of ["chromedriver", "msedgedriver"]) {
|
|
69
|
+
let stdout;
|
|
70
|
+
if (process.platform == "win32") {
|
|
71
|
+
try {
|
|
72
|
+
({stdout} = await promisify(exec)(
|
|
73
|
+
`(Get-Process -Name ${name}).Id | Stop-Process`,
|
|
74
|
+
{shell: "powershell.exe"}
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err.toString().includes("Command failed"))
|
|
79
|
+
continue; // Command will fail when driver process is not found
|
|
80
|
+
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
({stdout} =
|
|
87
|
+
await promisify(exec)(`ps -a | grep ${name} | grep -v grep`));
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (err.toString().includes("Command failed"))
|
|
91
|
+
continue; // Command will fail when driver process is not found
|
|
92
|
+
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
let pid = /\s*(\d*)/.exec(stdout)[1]; // first number after any spaces
|
|
96
|
+
await promisify(exec)(`kill ${pid}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (let browser of Object.keys(BROWSERS)) {
|
|
102
|
+
for (let version of VERSIONS[browser]) {
|
|
103
|
+
describe(`Browser: ${browser} ${version || "latest"}`, function() {
|
|
104
|
+
this.timeout(15000);
|
|
105
|
+
|
|
106
|
+
before(async() => {
|
|
107
|
+
try {
|
|
108
|
+
await fs.promises.rm(snapshotsBaseDir, {recursive: true});
|
|
109
|
+
}
|
|
110
|
+
catch (e) {}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
let driver = null;
|
|
114
|
+
afterEach(async() => {
|
|
115
|
+
if (!driver)
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
await driver.quit();
|
|
119
|
+
driver = null;
|
|
120
|
+
// Some platforms don't immediately kill the webdriver process
|
|
121
|
+
await killDriverProcess();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("downloads", async function() {
|
|
125
|
+
if (browser == "edge")
|
|
126
|
+
// Edge download is not implemented. It is assumed to be installed.
|
|
127
|
+
this.skip();
|
|
128
|
+
|
|
129
|
+
this.timeout(40000);
|
|
130
|
+
let {binary} = await BROWSERS[browser].downloadBinary(version);
|
|
131
|
+
|
|
132
|
+
await fs.promises.access(binary);
|
|
133
|
+
expect(binary).toEqual(
|
|
134
|
+
expect.stringContaining(path.join(snapshotsBaseDir, browser)));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("runs", async() => {
|
|
138
|
+
let names = {
|
|
139
|
+
chromium: "chrome",
|
|
140
|
+
firefox: "firefox",
|
|
141
|
+
edge: /(MicrosoftEdge|msedge)/
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
driver = await BROWSERS[browser].getDriver(version);
|
|
145
|
+
await driver.navigate().to("about:blank");
|
|
146
|
+
|
|
147
|
+
expect((await driver.getCapabilities()).getBrowserName())
|
|
148
|
+
.toEqual(expect.stringMatching(names[browser]));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("loads an extension", async() => {
|
|
152
|
+
let headless = browser == "firefox";
|
|
153
|
+
|
|
154
|
+
driver = await BROWSERS[browser].getDriver(version, headless,
|
|
155
|
+
extensionPaths);
|
|
156
|
+
await getHandle(driver, "/index.html");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("loads an extension in incognito mode", async function() {
|
|
160
|
+
if (browser != "firefox" || version == "68.0")
|
|
161
|
+
this.skip();
|
|
162
|
+
|
|
163
|
+
driver = await BROWSERS[browser].getDriver(version, false,
|
|
164
|
+
extensionPaths, true);
|
|
165
|
+
await BROWSERS[browser].enableExtensionInIncognito(
|
|
166
|
+
driver, "Browser download test extension"
|
|
167
|
+
);
|
|
168
|
+
await getHandle(driver, "/index.html");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|