@eyeo/get-browser-binary 0.16.0 → 0.18.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 +28 -18
- package/README.md +17 -1
- package/RELEASE_NOTES.md +27 -2
- package/package.json +1 -1
- package/src/browser.js +2 -0
- package/src/chromium.js +33 -20
- package/src/edge.js +5 -55
- package/src/firefox.js +56 -12
- package/test/browsers.js +74 -18
- package/test/extension/mv2/index.html +1 -1
- package/test/extension/mv2/update.html +1 -0
- package/test/extension/mv3/background.js +14 -1
- package/test/extension/mv3/index.html +1 -1
- package/test/extension/mv3/update.html +1 -0
- package/test/pages/proxy-config.pac +7 -0
package/.gitlab-ci.yml
CHANGED
|
@@ -27,42 +27,52 @@ test:basic:
|
|
|
27
27
|
- npm run lint
|
|
28
28
|
- npm test -- --grep "Utils"
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
.linux:
|
|
31
31
|
stage: test
|
|
32
32
|
image: docker:24.0.5
|
|
33
33
|
services:
|
|
34
34
|
- docker:24.0.5-dind
|
|
35
35
|
before_script:
|
|
36
36
|
- docker build -f test/docker/Dockerfile -t browsers .
|
|
37
|
+
|
|
38
|
+
test:browsers:linux:
|
|
39
|
+
extends: .linux
|
|
37
40
|
script:
|
|
38
|
-
- docker run --shm-size=512m -t browsers
|
|
41
|
+
- docker run --shm-size=512m -t -e TEST_ARGS="--grep ^.*Browser((?!Version:.dev).)*\$" browsers
|
|
39
42
|
|
|
40
|
-
test:browsers:
|
|
43
|
+
test:browsers:linux:dev:
|
|
44
|
+
extends: .linux
|
|
45
|
+
script:
|
|
46
|
+
- docker run --shm-size=512m -t -e TEST_ARGS="--grep Version:.dev" browsers
|
|
47
|
+
allow_failure: true
|
|
48
|
+
|
|
49
|
+
.windows:
|
|
41
50
|
stage: test
|
|
42
|
-
variables:
|
|
43
|
-
CI_PROJECT_ID_MAINSTREAM: 36688302
|
|
44
51
|
before_script:
|
|
45
|
-
-
|
|
46
|
-
-Uri "${Env:CI_API_V4_URL}/projects/${Env:CI_PROJECT_ID_MAINSTREAM}/packages/generic/microsoft-edge/79.0.309/MicrosoftEdgeEnterpriseX64.msi"
|
|
47
|
-
-Headers @{'JOB-TOKEN' = $Env:CI_JOB_TOKEN}
|
|
48
|
-
-OutFile 'MicrosoftEdgeEnterpriseX64.msi'
|
|
49
|
-
- Start-Process msiexec
|
|
50
|
-
-ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
|
|
51
|
-
- choco upgrade -y --no-progress nodejs --version 18.17.1
|
|
52
|
+
- choco install -y microsoft-edge
|
|
52
53
|
- npm install
|
|
54
|
+
tags:
|
|
55
|
+
- eyeo-windows
|
|
56
|
+
cache: {}
|
|
57
|
+
# Retrying to mitigate Edge install issues
|
|
58
|
+
retry: 1
|
|
59
|
+
|
|
60
|
+
test:browsers:windows:
|
|
61
|
+
extends: .windows
|
|
53
62
|
script:
|
|
54
63
|
# Running Edge tests only on the preinstalled version
|
|
55
64
|
# https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/29
|
|
56
65
|
- npm test -- --grep "edge.*latest"
|
|
57
|
-
- npm test -- --grep "chromium"
|
|
66
|
+
- npm test -- --grep "^.*chromium((?!Version:.dev).)*$"
|
|
58
67
|
# Running only a subset of Firefox tests to avoid low OS resources error
|
|
59
68
|
# https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/2
|
|
60
69
|
- npm test -- --grep "firefox.*installs"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
|
|
71
|
+
test:browsers:windows:dev:
|
|
72
|
+
extends: .windows
|
|
73
|
+
script:
|
|
74
|
+
- npm test -- --grep "chromium.*Version:.dev"
|
|
75
|
+
allow_failure: true
|
|
66
76
|
|
|
67
77
|
docs:
|
|
68
78
|
stage: docs
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ the right side.
|
|
|
32
32
|
|
|
33
33
|
- Chromium >= 77 (Chromium ARM >= 92)
|
|
34
34
|
- Firefox >= 68
|
|
35
|
-
- Edge >=
|
|
35
|
+
- Edge >= 114
|
|
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
|
|
@@ -43,6 +43,22 @@ Edge version is supported.
|
|
|
43
43
|
Set the `VERBOSE` environment variable to `"true"` to get verbose logging on
|
|
44
44
|
download requests.
|
|
45
45
|
|
|
46
|
+
### Offline execution
|
|
47
|
+
|
|
48
|
+
It is possible to run browsers offline as long as they have been previously
|
|
49
|
+
installed. Example:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// Online
|
|
53
|
+
let {binary} = await BROWSERS[browser].installBrowser(version);
|
|
54
|
+
console.log(binary); // keep the browser binary location to use it offline
|
|
55
|
+
let driver = await BROWSERS[browser].getDriver(version); // let the driver binary download
|
|
56
|
+
|
|
57
|
+
// Offline
|
|
58
|
+
let customBrowserBinary = "<browser binary location>";
|
|
59
|
+
let driver = await BROWSERS[browser].getDriver(version, {customBrowserBinary});
|
|
60
|
+
```
|
|
61
|
+
|
|
46
62
|
## Development
|
|
47
63
|
|
|
48
64
|
### Prerequisites
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
|
+
# Unreleased
|
|
2
|
+
|
|
3
|
+
# 0.18.0
|
|
4
|
+
|
|
5
|
+
- Adds support for Firefox dev channel (!116)
|
|
6
|
+
- Drops support for old Edge versions (#79)
|
|
7
|
+
- Fixes `enableExtensionInIncognito()` to keep working with Chromium +131 (!117)
|
|
8
|
+
|
|
9
|
+
## Testing
|
|
10
|
+
|
|
11
|
+
- Changes the way test extensions load internal pages. With Chromium +133,
|
|
12
|
+
`driver.getAllWindowHandles()` can't see tabs opened by an extension if the url
|
|
13
|
+
option opens an extension page. Please check the example provided by !119 in
|
|
14
|
+
order to workaround that issue.
|
|
15
|
+
|
|
16
|
+
# 0.17.0
|
|
17
|
+
|
|
18
|
+
- Stops calling `driver.close()` in `enableExtensionInIncognito()`
|
|
19
|
+
on non-Windows platforms (!114)
|
|
20
|
+
- Add instructions for offline execution (!110)
|
|
21
|
+
- Adds option to setup proxy through PAC file for Firefox (!115)
|
|
22
|
+
|
|
1
23
|
# 0.16.0
|
|
2
24
|
|
|
3
|
-
- Starts using selenium's automated driver management
|
|
25
|
+
- Starts using selenium's automated driver management. That means additional
|
|
26
|
+
`chromedriver` and `msedgedriver` packages are no longer needed (#67)
|
|
4
27
|
- Loads the extension in Firefox by local path instead of being copied to a
|
|
5
28
|
temporary folder (!104)
|
|
6
29
|
- Checks if extensions can be loaded beforehand, by ensuring they contain a
|
|
@@ -18,7 +41,9 @@ include parenthesis (#69)
|
|
|
18
41
|
### Notes for integrators
|
|
19
42
|
|
|
20
43
|
- Please make sure that your selenium-webdriver package version is at least
|
|
21
|
-
4.15.0 (the one used in this release)
|
|
44
|
+
4.15.0 (the one used in this release). Also make sure that you are not using the
|
|
45
|
+
`chromedriver` nor `msedgedriver` packages, otherwise the automated driver
|
|
46
|
+
management may not work as expected (#67)
|
|
22
47
|
- If you experience chromedriver issues, try deleting the `/browser-snapshots`
|
|
23
48
|
cache folder.
|
|
24
49
|
|
package/package.json
CHANGED
package/src/browser.js
CHANGED
|
@@ -95,6 +95,8 @@ export class Browser {
|
|
|
95
95
|
* certificates, or not.
|
|
96
96
|
* @property {Array.<string>} [extraArgs=[]] Additional arguments to start
|
|
97
97
|
* the browser with.
|
|
98
|
+
* @property {string} [proxy] Only for Firefox, path to network
|
|
99
|
+
* proxy autoconfig url (PAC file).
|
|
98
100
|
* @property {string} [customBrowserBinary] Path to the browser binary to be
|
|
99
101
|
* used, instead of the browser installed by installBrowser(). This option
|
|
100
102
|
* overrides the version parameter in getDriver().
|
package/src/chromium.js
CHANGED
|
@@ -229,46 +229,59 @@ export class Chromium extends Browser {
|
|
|
229
229
|
|
|
230
230
|
/** @see Browser.enableExtensionInIncognito */
|
|
231
231
|
static async enableExtensionInIncognito(driver, extensionTitle) {
|
|
232
|
-
|
|
232
|
+
const currentHandle = await driver.getWindowHandle();
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
(await driver.getCapabilities()).getBrowserVersion());
|
|
236
|
-
if (
|
|
234
|
+
const browserVersion =
|
|
235
|
+
getMajorVersion((await driver.getCapabilities()).getBrowserVersion());
|
|
236
|
+
if (browserVersion >= 115)
|
|
237
237
|
// On Chromium 115 opening chrome://extensions on the default tab causes
|
|
238
238
|
// WebDriverError: disconnected. Switching to a new window as a workaround
|
|
239
239
|
await driver.switchTo().newWindow("window");
|
|
240
240
|
|
|
241
241
|
await driver.navigate().to("chrome://extensions");
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
|
|
243
|
+
await driver.executeScript((title, errorMsg) => {
|
|
244
|
+
const enableIncognitoMode = () => document
|
|
245
|
+
.querySelector("extensions-manager").shadowRoot
|
|
244
246
|
.querySelector("extensions-detail-view").shadowRoot
|
|
245
247
|
.getElementById("allow-incognito").shadowRoot
|
|
246
248
|
.getElementById("crToggle").click();
|
|
247
249
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
const getExtensionDetailsButton = () => {
|
|
251
|
+
const extensions = document
|
|
252
|
+
.querySelector("extensions-manager").shadowRoot
|
|
253
|
+
.getElementById("items-list").shadowRoot
|
|
254
|
+
.querySelectorAll("extensions-item");
|
|
251
255
|
|
|
252
|
-
|
|
253
|
-
let extensionDetails;
|
|
256
|
+
let detailsButton;
|
|
254
257
|
for (let {shadowRoot} of extensions) {
|
|
255
|
-
|
|
258
|
+
const extensionName = shadowRoot.getElementById("name").innerHTML;
|
|
259
|
+
if (!extensionName.includes(title))
|
|
256
260
|
continue;
|
|
257
261
|
|
|
258
|
-
|
|
262
|
+
detailsButton = shadowRoot.getElementById("detailsButton");
|
|
259
263
|
break;
|
|
260
264
|
}
|
|
261
|
-
if (!extensionDetails)
|
|
262
|
-
reject(`${args[1]}: ${args[0]}`);
|
|
263
265
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
return detailsButton;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return new Promise((resolve, reject) => {
|
|
270
|
+
const detailsButton = getExtensionDetailsButton();
|
|
271
|
+
|
|
272
|
+
if (!detailsButton)
|
|
273
|
+
reject(`${errorMsg}: ${title}`);
|
|
274
|
+
|
|
275
|
+
detailsButton.click();
|
|
276
|
+
setTimeout(() => resolve(enableIncognitoMode()), 100);
|
|
266
277
|
});
|
|
267
278
|
}, extensionTitle, errMsg.extensionNotFound);
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
|
|
280
|
+
if (browserVersion >= 115 && process.platform == "win32")
|
|
281
|
+
// Closing the previously opened new window. Needed on Windows to avoid a
|
|
282
|
+
// further `WebDriverError: disconnected` error
|
|
270
283
|
await driver.close();
|
|
271
284
|
|
|
272
|
-
await driver.switchTo().window(
|
|
285
|
+
await driver.switchTo().window(currentHandle);
|
|
273
286
|
}
|
|
274
287
|
}
|
package/src/edge.js
CHANGED
|
@@ -23,7 +23,6 @@ import fs from "fs";
|
|
|
23
23
|
import got from "got";
|
|
24
24
|
import webdriver from "selenium-webdriver";
|
|
25
25
|
import edge from "selenium-webdriver/edge.js";
|
|
26
|
-
import extractZip from "extract-zip";
|
|
27
26
|
|
|
28
27
|
import {Browser} from "./browser.js";
|
|
29
28
|
import {download, killDriverProcess, wait, getMajorVersion, checkVersion,
|
|
@@ -40,6 +39,7 @@ let {platform} = process;
|
|
|
40
39
|
*/
|
|
41
40
|
export class Edge extends Browser {
|
|
42
41
|
static #CHANNELS = ["latest", "beta", "dev"];
|
|
42
|
+
static #MIN_VERSION = 114;
|
|
43
43
|
|
|
44
44
|
static async #getVersionForChannel(version) {
|
|
45
45
|
let channel = "stable";
|
|
@@ -106,7 +106,7 @@ export class Edge extends Browser {
|
|
|
106
106
|
* which requires root permissions. On MacOS, Edge is installed as a user
|
|
107
107
|
* app (not as a system app). Installing Edge on Windows is not supported.
|
|
108
108
|
* @param {string} [version=latest] Either "latest", "beta", "dev" or a full
|
|
109
|
-
* version number (i.e. "
|
|
109
|
+
* version number (i.e. "114.0.1823.82").
|
|
110
110
|
* @param {number} [downloadTimeout=0] Allowed time in ms for the download of
|
|
111
111
|
* install files to complete. When set to 0 there is no time limit.
|
|
112
112
|
* @return {BrowserBinary}
|
|
@@ -123,8 +123,7 @@ export class Edge extends Browser {
|
|
|
123
123
|
// Only latest Edge is supported on macOS
|
|
124
124
|
throw new Error(`${errMsg.unsupportedVersion}: ${version}. Only "latest" is supported`);
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
|
|
126
|
+
checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
|
|
128
127
|
let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
|
|
129
128
|
|
|
130
129
|
let filename = {
|
|
@@ -179,53 +178,13 @@ export class Edge extends Browser {
|
|
|
179
178
|
return installedVersion.trim().replace(/.*\s/, "");
|
|
180
179
|
}
|
|
181
180
|
|
|
182
|
-
static async #installOldWindowsDriver(binary) {
|
|
183
|
-
async function extractEdgeZip(archive, cacheDir, driverPath) {
|
|
184
|
-
await killDriverProcess("msedgedriver");
|
|
185
|
-
await fs.promises.rm(driverPath, {force: true});
|
|
186
|
-
await extractZip(archive, {dir: cacheDir});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
let binaryPath = binary || Edge.#getBinaryPath();
|
|
190
|
-
let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
|
|
191
|
-
let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
|
|
192
|
-
`edgedriver-${versionNumber}`);
|
|
193
|
-
let zip = process.arch == "ia32" ?
|
|
194
|
-
"edgedriver_win32.zip" : "edgedriver_win64.zip";
|
|
195
|
-
let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
|
|
196
|
-
let driverPath = path.join(cacheDir, "msedgedriver.exe");
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
await fs.promises.access(archive);
|
|
200
|
-
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
201
|
-
}
|
|
202
|
-
catch (e) { // zip file is either not cached or corrupted
|
|
203
|
-
let vSplit = versionNumber.split(".");
|
|
204
|
-
let lastNum = parseInt(vSplit[3], 10);
|
|
205
|
-
while (lastNum >= 0) {
|
|
206
|
-
try {
|
|
207
|
-
let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
|
|
208
|
-
await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
|
|
209
|
-
archive);
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
catch (e2) {
|
|
213
|
-
lastNum--;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (lastNum < 0)
|
|
217
|
-
throw new Error(`${errMsg.driverDownload}: Edge ${versionNumber}`);
|
|
218
|
-
|
|
219
|
-
await extractEdgeZip(archive, cacheDir, driverPath);
|
|
220
|
-
}
|
|
221
|
-
return driverPath;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
181
|
/** @see Browser.getDriver */
|
|
225
182
|
static async getDriver(version = "latest", {
|
|
226
183
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
227
184
|
extraArgs = [], customBrowserBinary
|
|
228
185
|
} = {}, downloadTimeout = 0) {
|
|
186
|
+
checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
|
|
187
|
+
|
|
229
188
|
let binary;
|
|
230
189
|
let versionNumber;
|
|
231
190
|
if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
|
|
@@ -260,15 +219,6 @@ export class Edge extends Browser {
|
|
|
260
219
|
builder.forBrowser("MicrosoftEdge");
|
|
261
220
|
builder.setEdgeOptions(options);
|
|
262
221
|
|
|
263
|
-
if (getMajorVersion(versionNumber) < 95) {
|
|
264
|
-
// Selenium's automated driver download doesn't work on old Edge versions.
|
|
265
|
-
// Support to installing old drivers is only needed by Windows CI jobs.
|
|
266
|
-
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/2#note_1189235804
|
|
267
|
-
let driverPath = await Edge.#installOldWindowsDriver(binary);
|
|
268
|
-
let serviceBuilder = new edge.ServiceBuilder(driverPath);
|
|
269
|
-
builder.setEdgeService(serviceBuilder);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
222
|
let driver;
|
|
273
223
|
// On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
|
|
274
224
|
// due to low OS resources, that's why building the driver is retried
|
package/src/firefox.js
CHANGED
|
@@ -38,12 +38,13 @@ let {until, By} = webdriver;
|
|
|
38
38
|
* @extends Browser
|
|
39
39
|
*/
|
|
40
40
|
export class Firefox extends Browser {
|
|
41
|
-
static #CHANNELS = ["latest", "beta"];
|
|
41
|
+
static #CHANNELS = ["latest", "beta", "dev"];
|
|
42
42
|
|
|
43
43
|
static async #getVersionForChannel(channel) {
|
|
44
44
|
if (!Firefox.#CHANNELS.includes(channel))
|
|
45
45
|
return channel;
|
|
46
46
|
|
|
47
|
+
// https://wiki.mozilla.org/Release_Management/Product_details#firefox_versions.json
|
|
47
48
|
let url = "https://product-details.mozilla.org/1.0/firefox_versions.json";
|
|
48
49
|
let data;
|
|
49
50
|
try {
|
|
@@ -52,17 +53,44 @@ export class Firefox extends Browser {
|
|
|
52
53
|
catch (err) {
|
|
53
54
|
throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
if (channel === "beta") {
|
|
58
|
+
// The validated latest Firefox Beta built, exposed for downloading
|
|
59
|
+
return data.LATEST_FIREFOX_RELEASED_DEVEL_VERSION;
|
|
60
|
+
}
|
|
61
|
+
else if (channel === "dev") {
|
|
62
|
+
// Firefox Developer Edition
|
|
63
|
+
return data.FIREFOX_DEVEDITION;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The Firefox Version shipped on the release channel
|
|
67
|
+
return data.LATEST_FIREFOX_VERSION;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static #getAppDir(dir) {
|
|
71
|
+
const appDirRegexes = {
|
|
72
|
+
win32: /^core$/,
|
|
73
|
+
linux: /^firefox$/,
|
|
74
|
+
darwin: /^Firefox.*\.app$/
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const contents = fs.readdirSync(dir);
|
|
78
|
+
const appDir = contents.find(candidateDir =>
|
|
79
|
+
appDirRegexes[process.platform].test(candidateDir));
|
|
80
|
+
|
|
81
|
+
return path.join(dir, appDir);
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
static #getBinaryPath(dir) {
|
|
60
85
|
checkPlatform();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
|
|
87
|
+
const defaultBinaries = {
|
|
88
|
+
win32: path.join(Firefox.#getAppDir(dir), "firefox.exe"),
|
|
89
|
+
linux: path.join(Firefox.#getAppDir(dir), "firefox"),
|
|
90
|
+
darwin: path.join(Firefox.#getAppDir(dir), "Contents", "MacOS", "firefox")
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return defaultBinaries[process.platform];
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
static #extractFirefoxArchive(archive, dir) {
|
|
@@ -113,12 +141,16 @@ export class Firefox extends Browser {
|
|
|
113
141
|
let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
|
|
114
142
|
let browserDir = path.join(snapshotsDir,
|
|
115
143
|
`firefox-${platformArch}-${versionNumber}`);
|
|
116
|
-
|
|
144
|
+
|
|
145
|
+
let binary;
|
|
117
146
|
try {
|
|
118
147
|
await fs.promises.access(browserDir);
|
|
148
|
+
binary = Firefox.#getBinaryPath(browserDir);
|
|
119
149
|
return {binary, versionNumber};
|
|
120
150
|
}
|
|
121
|
-
catch (e) {
|
|
151
|
+
catch (e) {
|
|
152
|
+
// The binary was not already downloaded
|
|
153
|
+
}
|
|
122
154
|
|
|
123
155
|
let archive = path.join(snapshotsDir, "cache", fileName);
|
|
124
156
|
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
@@ -126,7 +158,9 @@ export class Firefox extends Browser {
|
|
|
126
158
|
await fs.promises.access(archive);
|
|
127
159
|
}
|
|
128
160
|
catch (e) {
|
|
129
|
-
|
|
161
|
+
const baseUrl = "https://archive.mozilla.org/pub";
|
|
162
|
+
const channel = version === "dev" ? "devedition" : "firefox";
|
|
163
|
+
let url = `${baseUrl}/${channel}/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
|
|
130
164
|
try {
|
|
131
165
|
await download(url, archive, downloadTimeout);
|
|
132
166
|
}
|
|
@@ -135,14 +169,20 @@ export class Firefox extends Browser {
|
|
|
135
169
|
}
|
|
136
170
|
}
|
|
137
171
|
await Firefox.#extractFirefoxArchive(archive, browserDir);
|
|
172
|
+
binary = Firefox.#getBinaryPath(browserDir);
|
|
138
173
|
|
|
139
174
|
return {binary, versionNumber};
|
|
140
175
|
}
|
|
141
176
|
|
|
142
177
|
/** @see Browser.getDriver */
|
|
178
|
+
/** In Firefox you can define url to proxy autoconfig file.
|
|
179
|
+
* Proxy Autoconfig file determine whether web requests should go through
|
|
180
|
+
* a proxy server or connect directly. This allow to use localhost mapped
|
|
181
|
+
* to any url (f.ex testpages.adblockplus.org)
|
|
182
|
+
*/
|
|
143
183
|
static async getDriver(version = "latest", {
|
|
144
184
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
145
|
-
extraArgs = [], customBrowserBinary
|
|
185
|
+
extraArgs = [], customBrowserBinary, proxy
|
|
146
186
|
} = {}, downloadTimeout = 0) {
|
|
147
187
|
let binary;
|
|
148
188
|
let versionNumber;
|
|
@@ -163,6 +203,10 @@ export class Firefox extends Browser {
|
|
|
163
203
|
// Enabled by default on Firefox > 68
|
|
164
204
|
if (versionNumber && getMajorVersion(versionNumber) == 68)
|
|
165
205
|
options.setPreference("dom.promise_rejection_events.enabled", true);
|
|
206
|
+
if (proxy) {
|
|
207
|
+
options.setPreference("network.proxy.type", 2);
|
|
208
|
+
options.setPreference("network.proxy.autoconfig_url", proxy);
|
|
209
|
+
}
|
|
166
210
|
|
|
167
211
|
options.setBinary(customBrowserBinary || binary);
|
|
168
212
|
|
package/test/browsers.js
CHANGED
|
@@ -27,10 +27,11 @@ import {TEST_SERVER_URL} from "./test-server.js";
|
|
|
27
27
|
|
|
28
28
|
const VERSIONS = {
|
|
29
29
|
chromium: ["latest", "77.0.3865.0", "beta", "dev"],
|
|
30
|
-
firefox: ["latest", "68.0", "beta"],
|
|
31
|
-
edge: ["latest", "
|
|
30
|
+
firefox: ["latest", "68.0", "beta", "dev"],
|
|
31
|
+
edge: ["latest", "114.0.1823.82", "beta", "dev"]
|
|
32
32
|
};
|
|
33
33
|
const TEST_URL_BASIC = `${TEST_SERVER_URL}/basic.html`;
|
|
34
|
+
const PROXY_URL_BASIC = "http://testpages.adblockplus.org/basic.html";
|
|
34
35
|
const TEST_URL_LONG = `${TEST_SERVER_URL}/long.html`;
|
|
35
36
|
|
|
36
37
|
async function switchToHandle(driver, testFn) {
|
|
@@ -106,7 +107,8 @@ function getExtension(browser, version) {
|
|
|
106
107
|
|
|
107
108
|
async function getInstallFileCTime(browser) {
|
|
108
109
|
// Browsers installed at OS level are not tested
|
|
109
|
-
if (browser == "edge" && process.platform == "win32"
|
|
110
|
+
if (browser == "edge" && (process.platform == "win32" ||
|
|
111
|
+
process.platform == "darwin"))
|
|
110
112
|
return null;
|
|
111
113
|
|
|
112
114
|
const installTypes = [".zip", ".dmg", ".bz2", ".deb", ".exe"];
|
|
@@ -141,6 +143,14 @@ function addVersionToTitle(ctx, version) {
|
|
|
141
143
|
ctx.test.title = `${ctx.test.title} [v${version}]`;
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
function isOldHeadlessMode(browser, version) {
|
|
147
|
+
// Chromium's old headless mode doesn't support loading extensions
|
|
148
|
+
return browser != "firefox" && (
|
|
149
|
+
!["latest", "beta", "dev"].includes(version) ||
|
|
150
|
+
// Edge on the Windows CI is v79, that is old headless
|
|
151
|
+
(browser == "edge" && process.platform == "win32"));
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
for (let browser of Object.keys(BROWSERS)) {
|
|
145
155
|
describe(`Browser: ${browser}`, () => {
|
|
146
156
|
before(async() => {
|
|
@@ -168,6 +178,12 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
168
178
|
await killDriverProcess("chromedriver");
|
|
169
179
|
else if (browser == "firefox")
|
|
170
180
|
await killDriverProcess("geckodriver");
|
|
181
|
+
|
|
182
|
+
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/77
|
|
183
|
+
if (process.platform == "win32" && browser == "chromium")
|
|
184
|
+
await killDriverProcess("chrome");
|
|
185
|
+
else if (process.platform == "win32" && browser == "edge")
|
|
186
|
+
await killDriverProcess("msedge");
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
beforeEach(function() {
|
|
@@ -258,6 +274,22 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
258
274
|
expect(sizeSmall).toMeasureLessThan(sizeDefault);
|
|
259
275
|
});
|
|
260
276
|
|
|
277
|
+
it("supports proxy", async function() {
|
|
278
|
+
if (browser != "firefox")
|
|
279
|
+
this.skip();
|
|
280
|
+
|
|
281
|
+
let headless = true;
|
|
282
|
+
let proxy = "http://localhost:3000/proxy-config.pac";
|
|
283
|
+
let extraArgs = [];
|
|
284
|
+
driver = await BROWSERS[browser].getDriver(
|
|
285
|
+
version, {headless, extraArgs, proxy});
|
|
286
|
+
await driver.navigate().to(PROXY_URL_BASIC);
|
|
287
|
+
|
|
288
|
+
let text = await driver.findElement(By.id("basic")).getText();
|
|
289
|
+
expect(text).toEqual("Test server basic page");
|
|
290
|
+
await quitDriver();
|
|
291
|
+
});
|
|
292
|
+
|
|
261
293
|
it("takes a full page screenshot", async() => {
|
|
262
294
|
driver = await BROWSERS[browser].getDriver(version);
|
|
263
295
|
await driver.navigate().to(TEST_URL_LONG);
|
|
@@ -277,9 +309,7 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
277
309
|
// Selenium's automated driver download should be able to manage it.
|
|
278
310
|
// https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/44
|
|
279
311
|
it("loads an extension", async() => {
|
|
280
|
-
|
|
281
|
-
let headless = browser == "firefox" || (browser == "chromium" &&
|
|
282
|
-
["latest", "beta", "dev"].includes(version));
|
|
312
|
+
let headless = !isOldHeadlessMode(browser, version);
|
|
283
313
|
let {extensionPaths} = getExtension(browser, version);
|
|
284
314
|
|
|
285
315
|
driver = await BROWSERS[browser].getDriver(
|
|
@@ -299,24 +329,50 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
299
329
|
);
|
|
300
330
|
await getHandle(driver, "/index.html");
|
|
301
331
|
});
|
|
332
|
+
|
|
333
|
+
it("updates an extension", async() => {
|
|
334
|
+
let headless = !isOldHeadlessMode(browser, version);
|
|
335
|
+
let {extensionPaths, manifest} = getExtension(browser, version);
|
|
336
|
+
|
|
337
|
+
let tmpExtensionDir = path.join(snapshotsBaseDir, "extension");
|
|
338
|
+
await fs.promises.rm(tmpExtensionDir, {recursive: true, force: true});
|
|
339
|
+
await fs.promises.cp(extensionPaths[0], tmpExtensionDir,
|
|
340
|
+
{recursive: true});
|
|
341
|
+
|
|
342
|
+
driver = await BROWSERS[browser].getDriver(
|
|
343
|
+
version, {headless, extensionPaths: [tmpExtensionDir]});
|
|
344
|
+
await getHandle(driver, "/index.html");
|
|
345
|
+
let text = await driver.findElement(By.id("title")).getText();
|
|
346
|
+
expect(text).toEqual(`Browser test extension - ${manifest}`);
|
|
347
|
+
|
|
348
|
+
// The page is modified and reloaded to emulate an extension update
|
|
349
|
+
await fs.promises.cp(path.join(tmpExtensionDir, "update.html"),
|
|
350
|
+
path.join(tmpExtensionDir, "index.html"));
|
|
351
|
+
await driver.navigate().refresh();
|
|
352
|
+
text = await driver.findElement(By.id("title")).getText();
|
|
353
|
+
expect(text).toEqual(`Updated test extension - ${manifest}`);
|
|
354
|
+
});
|
|
302
355
|
});
|
|
303
356
|
}
|
|
304
357
|
|
|
305
|
-
it("does not install unsupported versions", async function() {
|
|
306
|
-
if (browser == "edge" && process.platform == "win32")
|
|
307
|
-
this.skip();
|
|
308
358
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
});
|
|
359
|
+
describe("Any version", async() => {
|
|
360
|
+
it("does not install unsupported versions", async function() {
|
|
361
|
+
if (browser == "edge" && process.platform == "win32")
|
|
362
|
+
this.skip();
|
|
314
363
|
|
|
315
|
-
|
|
316
|
-
|
|
364
|
+
for (let unsupported of ["0.0", "invalid"]) {
|
|
365
|
+
await expect(BROWSERS[browser].installBrowser(unsupported))
|
|
366
|
+
.rejects.toThrow(`Unsupported browser version: ${unsupported}`);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
317
369
|
|
|
318
|
-
|
|
319
|
-
|
|
370
|
+
it("does not load an invalid extension", async() => {
|
|
371
|
+
let extensionPaths = [process.cwd()];
|
|
372
|
+
|
|
373
|
+
await expect(BROWSERS[browser].getDriver("latest", {extensionPaths}))
|
|
374
|
+
.rejects.toThrow(`Extension manifest file not found: ${extensionPaths[0]}`);
|
|
375
|
+
});
|
|
320
376
|
});
|
|
321
377
|
});
|
|
322
378
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<h1>Browser test extension - mv2</h1>
|
|
1
|
+
<h1 id="title">Browser test extension - mv2</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1 id="title">Updated test extension - mv2</h1>
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// There's an issue with Chrome +133 and selenium webdriver where
|
|
4
|
+
// getAllWindowHandles() can't see tabs opened by an extension if the url option
|
|
5
|
+
// opens an extension page. Example: chrome.tabs.create({url: "index.html"});
|
|
6
|
+
//
|
|
7
|
+
// Workaround: Create the tab with empty options, which allows
|
|
8
|
+
// getAllWindowHandles() to see it, and afterwards update the tab with the
|
|
9
|
+
// intended url.
|
|
10
|
+
|
|
11
|
+
// This timeout must be higher than driver.geAllWindowHandles() polling interval
|
|
12
|
+
const timeout = 1000;
|
|
13
|
+
|
|
14
|
+
chrome.tabs.create({}, ({tabId}) => {
|
|
15
|
+
setTimeout(() => chrome.tabs.update(tabId, {url: "index.html"}), timeout);
|
|
16
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<h1>Browser test extension - mv3</h1>
|
|
1
|
+
<h1 id="title">Browser test extension - mv3</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<h1 id="title">Updated test extension - mv3</h1>
|