@eyeo/get-browser-binary 0.7.0 → 0.9.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 +1 -3
- package/.mocharc.json +1 -1
- package/README.md +2 -2
- package/RELEASE_NOTES.md +28 -0
- package/index.js +1 -1
- package/package.json +14 -1
- package/src/browsers.js +46 -96
- package/src/utils.js +82 -10
- package/test/browsers.js +53 -7
- package/test/utils.js +23 -7
package/.gitlab-ci.yml
CHANGED
|
@@ -32,12 +32,10 @@ test:browsers:linux:
|
|
|
32
32
|
image: docker:20.10.16
|
|
33
33
|
services:
|
|
34
34
|
- docker:20.10.16-dind
|
|
35
|
-
variables:
|
|
36
|
-
DOCKER_DRIVER: overlay2
|
|
37
35
|
before_script:
|
|
38
36
|
- docker build -f test/docker/Dockerfile -t browsers .
|
|
39
37
|
script:
|
|
40
|
-
- docker run --shm-size=
|
|
38
|
+
- docker run --shm-size=512m -t browsers
|
|
41
39
|
|
|
42
40
|
test:browsers:windows:
|
|
43
41
|
stage: test
|
package/.mocharc.json
CHANGED
package/README.md
CHANGED
|
@@ -88,14 +88,14 @@ Useful to reproduce the CI environment of the `test:browsers:linux` job:
|
|
|
88
88
|
|
|
89
89
|
```shell
|
|
90
90
|
docker build -f test/docker/Dockerfile -t browsers .
|
|
91
|
-
docker run --shm-size=
|
|
91
|
+
docker run --shm-size=512m -it browsers
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
The `grep` and `timeout` options can also be used on Docker via the `TEST_ARGS`
|
|
95
95
|
parameter:
|
|
96
96
|
|
|
97
97
|
```shell
|
|
98
|
-
docker run --shm-size=
|
|
98
|
+
docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest --timeout 100000" -it browsers
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
By default, tests delete the `./browser-snapshots` before each `Browser` suite
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
# 0.9.0
|
|
2
|
+
|
|
3
|
+
- Fixed a Chromium install issue by increasing the value of
|
|
4
|
+
`MAX_VERSION_DECREMENTS` (!52)
|
|
5
|
+
- `installBrowser()`, `getDriver()` and `download()` have a new optional timeout
|
|
6
|
+
parameter on file downloads (!51)
|
|
7
|
+
|
|
8
|
+
### Testing
|
|
9
|
+
|
|
10
|
+
- Added a test checking that downloaded browser-snapshot files are actually
|
|
11
|
+
cached (#35)
|
|
12
|
+
- Tests running the minimum firefox version showed occasional failures. That was
|
|
13
|
+
fixed by increasing the shared memory size of the docker image (!50)
|
|
14
|
+
|
|
15
|
+
# 0.8.0
|
|
16
|
+
|
|
17
|
+
- Move `takeFullPageScreenshot` to the utils module (#38)
|
|
18
|
+
- Add keywords to package.json (#36)
|
|
19
|
+
|
|
20
|
+
### Testing
|
|
21
|
+
|
|
22
|
+
- Log the running browser version on test suites (#37)
|
|
23
|
+
|
|
24
|
+
### Notes for integrators
|
|
25
|
+
|
|
26
|
+
- `takeFullPageScreenshot()` has been moved from the browsers module to the
|
|
27
|
+
utils module (!46).
|
|
28
|
+
|
|
1
29
|
# 0.7.0
|
|
2
30
|
|
|
3
31
|
- Implement takeFullPageScreenshot (#32)
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eyeo/get-browser-binary",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Install browser binaries and matching webdrivers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,6 +14,19 @@
|
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "index.js",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"browser",
|
|
19
|
+
"chromium",
|
|
20
|
+
"chrome",
|
|
21
|
+
"firefox",
|
|
22
|
+
"msedge",
|
|
23
|
+
"chromedriver",
|
|
24
|
+
"geckodriver",
|
|
25
|
+
"msedgedriver",
|
|
26
|
+
"selenium",
|
|
27
|
+
"webdriver",
|
|
28
|
+
"selenium-webdriver"
|
|
29
|
+
],
|
|
17
30
|
"dependencies": {
|
|
18
31
|
"dmg": "^0.1.0",
|
|
19
32
|
"extract-zip": "^2.0.1",
|
package/src/browsers.js
CHANGED
|
@@ -27,7 +27,6 @@ import firefox from "selenium-webdriver/firefox.js";
|
|
|
27
27
|
import edge from "selenium-webdriver/edge.js";
|
|
28
28
|
import command from "selenium-webdriver/lib/command.js";
|
|
29
29
|
import extractZip from "extract-zip";
|
|
30
|
-
import Jimp from "jimp";
|
|
31
30
|
|
|
32
31
|
import {download, extractTar, extractDmg, killDriverProcess, wait}
|
|
33
32
|
from "./utils.js";
|
|
@@ -83,11 +82,14 @@ class Browser {
|
|
|
83
82
|
* subclasses.
|
|
84
83
|
* @param {string} version - Either full version number or channel/release.
|
|
85
84
|
* Please find examples on the subclasses.
|
|
85
|
+
* @param {number?} downloadTimeout - Allowed time in ms for the download of
|
|
86
|
+
* install files to complete. When set to 0 there is no time limit. Defaults
|
|
87
|
+
* to 0.
|
|
86
88
|
* @return {BrowserBinary}
|
|
87
89
|
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
88
90
|
* download failed.
|
|
89
91
|
*/
|
|
90
|
-
static async installBrowser(version) {
|
|
92
|
+
static async installBrowser(version, downloadTimeout = 0) {
|
|
91
93
|
// to be implemented by the subclass
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -135,11 +137,14 @@ class Browser {
|
|
|
135
137
|
* @param {string} version - Either full version number or channel/release.
|
|
136
138
|
* Please find examples on the subclasses.
|
|
137
139
|
* @param {driverOptions?} options - Options to start the browser with.
|
|
140
|
+
* @param {number?} downloadTimeout - Allowed time in ms for the download of
|
|
141
|
+
* browser install files to complete. When set to 0 there is no time limit.
|
|
142
|
+
* Defaults to 0.
|
|
138
143
|
* @return {webdriver}
|
|
139
144
|
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
140
145
|
* download failed, Driver download failed, Unable to start driver.
|
|
141
146
|
*/
|
|
142
|
-
static async getDriver(version, options = {}) {
|
|
147
|
+
static async getDriver(version, options = {}, downloadTimeout = 0) {
|
|
143
148
|
// to be implemented by the subclass
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -158,66 +163,6 @@ class Browser {
|
|
|
158
163
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
|
|
159
164
|
// That is done through the UI, to be implemented by the subclass
|
|
160
165
|
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* @typedef {Object} Jimp
|
|
164
|
-
* @see https://github.com/oliver-moran/jimp/tree/master/packages/jimp
|
|
165
|
-
*/
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Takes a screenshot of the full page by scrolling from top to bottom.
|
|
169
|
-
* @param {webdriver} driver - The driver controlling the browser.
|
|
170
|
-
* @property {boolean} hideScrollbars=true - Hides any scrollbars before
|
|
171
|
-
* taking the screenshot, or not.
|
|
172
|
-
* @return {Jimp} A Jimp image object containing the screenshot.
|
|
173
|
-
* @example
|
|
174
|
-
* // Getting a base-64 encoded PNG from the returned screenshot
|
|
175
|
-
* let image = await Browser.takeFullPageScreenshot(driver);
|
|
176
|
-
* let encodedPNG = await image.getBase64Async("image/png");
|
|
177
|
-
*/
|
|
178
|
-
static async takeFullPageScreenshot(driver, hideScrollbars = true) {
|
|
179
|
-
// On macOS scrollbars appear and disappear overlapping the content as
|
|
180
|
-
// scrolling occurs. Hiding the scrollbars helps getting reproducible
|
|
181
|
-
// screenshots.
|
|
182
|
-
if (hideScrollbars) {
|
|
183
|
-
await driver.executeScript(() => {
|
|
184
|
-
if (!document.head)
|
|
185
|
-
return;
|
|
186
|
-
|
|
187
|
-
let style = document.createElement("style");
|
|
188
|
-
style.textContent = "html { overflow-y: scroll; }";
|
|
189
|
-
document.head.appendChild(style);
|
|
190
|
-
if (document.documentElement.clientWidth == window.innerWidth)
|
|
191
|
-
style.textContent = "html::-webkit-scrollbar { display: none; }";
|
|
192
|
-
else
|
|
193
|
-
document.head.removeChild(style);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
let fullScreenshot = new Jimp(0, 0);
|
|
198
|
-
while (true) {
|
|
199
|
-
let [width, height, offset] = await driver.executeScript((...args) => {
|
|
200
|
-
window.scrollTo(0, args[0]);
|
|
201
|
-
// Math.ceil rounds up potential decimal values on window.scrollY,
|
|
202
|
-
// ensuring the loop will not hang due to never reaching enough
|
|
203
|
-
// fullScreenshot's height.
|
|
204
|
-
return [document.documentElement.clientWidth,
|
|
205
|
-
document.documentElement.scrollHeight,
|
|
206
|
-
Math.ceil(window.scrollY)];
|
|
207
|
-
}, fullScreenshot.bitmap.height);
|
|
208
|
-
let data = await driver.takeScreenshot();
|
|
209
|
-
let partialScreenshot = await Jimp.read(Buffer.from(data, "base64"));
|
|
210
|
-
let combinedScreenshot =
|
|
211
|
-
new Jimp(width, offset + partialScreenshot.bitmap.height);
|
|
212
|
-
combinedScreenshot.composite(fullScreenshot, 0, 0);
|
|
213
|
-
combinedScreenshot.composite(partialScreenshot, 0, offset);
|
|
214
|
-
fullScreenshot = combinedScreenshot;
|
|
215
|
-
|
|
216
|
-
if (fullScreenshot.bitmap.height >= height)
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
return fullScreenshot;
|
|
220
|
-
}
|
|
221
166
|
}
|
|
222
167
|
|
|
223
168
|
/**
|
|
@@ -269,13 +214,16 @@ class Chromium extends Browser {
|
|
|
269
214
|
* {@link snapshotsBaseDir} folder, ready to go.
|
|
270
215
|
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
271
216
|
* number (i.e. "77.0.3865.0"). Defaults to "latest".
|
|
217
|
+
* @param {number?} downloadTimeout - Allowed time in ms for the download of
|
|
218
|
+
* install files to complete. When set to 0 there is no time limit. Defaults
|
|
219
|
+
* to 0.
|
|
272
220
|
* @return {BrowserBinary}
|
|
273
221
|
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
274
222
|
* download failed.
|
|
275
223
|
*/
|
|
276
|
-
static async installBrowser(version = "latest") {
|
|
224
|
+
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
277
225
|
const MIN_VERSION = 75;
|
|
278
|
-
const MAX_VERSION_DECREMENTS =
|
|
226
|
+
const MAX_VERSION_DECREMENTS = 200;
|
|
279
227
|
|
|
280
228
|
checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
|
|
281
229
|
let versionNumber = await Chromium.#getVersionForChannel(version);
|
|
@@ -309,23 +257,27 @@ class Chromium extends Browser {
|
|
|
309
257
|
await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
|
|
310
258
|
|
|
311
259
|
archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
|
|
260
|
+
let url = `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`;
|
|
312
261
|
try {
|
|
313
262
|
try {
|
|
314
263
|
await fs.promises.access(archive);
|
|
315
264
|
}
|
|
316
265
|
catch (e) {
|
|
317
|
-
await download(
|
|
318
|
-
`https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`,
|
|
319
|
-
archive);
|
|
266
|
+
await download(url, archive, downloadTimeout);
|
|
320
267
|
}
|
|
321
268
|
break;
|
|
322
269
|
}
|
|
323
|
-
catch (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
270
|
+
catch (err) {
|
|
271
|
+
if (err.name == "HTTPError") {
|
|
272
|
+
// Chromium advises decrementing the branch_base_position when no
|
|
273
|
+
// matching build was found. See https://www.chromium.org/getting-involved/download-chromium
|
|
274
|
+
base--;
|
|
275
|
+
if (base <= startBase - MAX_VERSION_DECREMENTS)
|
|
276
|
+
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
280
|
+
}
|
|
329
281
|
}
|
|
330
282
|
}
|
|
331
283
|
await extractZip(archive, {dir: browserDir});
|
|
@@ -363,10 +315,7 @@ class Chromium extends Browser {
|
|
|
363
315
|
|
|
364
316
|
await killDriverProcess("chromedriver");
|
|
365
317
|
let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
|
|
366
|
-
|
|
367
|
-
await fs.promises.rm(driverPath, {recursive: true});
|
|
368
|
-
}
|
|
369
|
-
catch (e) {} // file does not exist
|
|
318
|
+
await fs.promises.rm(driverPath, {force: true});
|
|
370
319
|
await extractZip(archive, {dir: cacheDir});
|
|
371
320
|
|
|
372
321
|
return driverPath;
|
|
@@ -376,8 +325,9 @@ class Chromium extends Browser {
|
|
|
376
325
|
static async getDriver(version = "latest", {
|
|
377
326
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
378
327
|
extraArgs = []
|
|
379
|
-
} = {}) {
|
|
380
|
-
let {binary, versionNumber, base} =
|
|
328
|
+
} = {}, downloadTimeout = 0) {
|
|
329
|
+
let {binary, versionNumber, base} =
|
|
330
|
+
await Chromium.installBrowser(version, downloadTimeout);
|
|
381
331
|
let driverPath = await Chromium.#installDriver(base, versionNumber);
|
|
382
332
|
let serviceBuilder = new chrome.ServiceBuilder(driverPath);
|
|
383
333
|
let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
|
|
@@ -475,11 +425,14 @@ class Firefox extends Browser {
|
|
|
475
425
|
* {@link snapshotsBaseDir} folder, ready to go.
|
|
476
426
|
* @param {string} version - Either "latest", "beta" or a full version
|
|
477
427
|
* number (i.e. "68.0"). Defaults to "latest".
|
|
428
|
+
* @param {number?} downloadTimeout - Allowed time in ms for the download of
|
|
429
|
+
* install files to complete. When set to 0 there is no time limit. Defaults
|
|
430
|
+
* to 0.
|
|
478
431
|
* @return {BrowserBinary}
|
|
479
432
|
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
480
433
|
* download failed.
|
|
481
434
|
*/
|
|
482
|
-
static async installBrowser(version = "latest") {
|
|
435
|
+
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
483
436
|
const MIN_VERSION = 60;
|
|
484
437
|
|
|
485
438
|
checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
|
|
@@ -511,7 +464,7 @@ class Firefox extends Browser {
|
|
|
511
464
|
catch (e) {
|
|
512
465
|
let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
|
|
513
466
|
try {
|
|
514
|
-
await download(url, archive);
|
|
467
|
+
await download(url, archive, downloadTimeout);
|
|
515
468
|
}
|
|
516
469
|
catch (err) {
|
|
517
470
|
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
@@ -526,8 +479,8 @@ class Firefox extends Browser {
|
|
|
526
479
|
static async getDriver(version = "latest", {
|
|
527
480
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
528
481
|
extraArgs = []
|
|
529
|
-
} = {}) {
|
|
530
|
-
let {binary} = await Firefox.installBrowser(version);
|
|
482
|
+
} = {}, downloadTimeout = 0) {
|
|
483
|
+
let {binary} = await Firefox.installBrowser(version, downloadTimeout);
|
|
531
484
|
|
|
532
485
|
let options = new firefox.Options();
|
|
533
486
|
if (headless)
|
|
@@ -662,11 +615,14 @@ class Edge extends Browser {
|
|
|
662
615
|
* app (not as a system app). Installing Edge on Windows is not supported.
|
|
663
616
|
* @param {string} version - Either "latest", "beta", "dev" or a full version
|
|
664
617
|
* number (i.e. "95.0.1020.40"). Defaults to "latest".
|
|
618
|
+
* @param {number?} downloadTimeout - Allowed time in ms for the download of
|
|
619
|
+
* install files to complete. When set to 0 there is no time limit. Defaults
|
|
620
|
+
* to 0.
|
|
665
621
|
* @return {BrowserBinary}
|
|
666
622
|
* @throws {Error} Unsupported browser version, Unsupported platform, Browser
|
|
667
623
|
* download failed.
|
|
668
624
|
*/
|
|
669
|
-
static async installBrowser(version = "latest") {
|
|
625
|
+
static async installBrowser(version = "latest", downloadTimeout = 0) {
|
|
670
626
|
if (platform == "win32")
|
|
671
627
|
// Edge is mandatory on Windows, can't be uninstalled or downgraded
|
|
672
628
|
// https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
|
|
@@ -703,7 +659,7 @@ class Edge extends Browser {
|
|
|
703
659
|
catch (e) {}
|
|
704
660
|
|
|
705
661
|
try {
|
|
706
|
-
await download(url, archive);
|
|
662
|
+
await download(url, archive, downloadTimeout);
|
|
707
663
|
}
|
|
708
664
|
catch (err) {
|
|
709
665
|
throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
|
|
@@ -714,10 +670,7 @@ class Edge extends Browser {
|
|
|
714
670
|
}
|
|
715
671
|
else if (platform == "darwin") {
|
|
716
672
|
let appName = Edge.#getDarwinAppName(channel);
|
|
717
|
-
|
|
718
|
-
await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {recursive: true});
|
|
719
|
-
}
|
|
720
|
-
catch (e) {}
|
|
673
|
+
await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {force: true, recursive: true});
|
|
721
674
|
await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
|
|
722
675
|
}
|
|
723
676
|
|
|
@@ -734,10 +687,7 @@ class Edge extends Browser {
|
|
|
734
687
|
static async #installDriver() {
|
|
735
688
|
async function extractEdgeZip(archive, cacheDir, driverPath) {
|
|
736
689
|
await killDriverProcess("msedgedriver");
|
|
737
|
-
|
|
738
|
-
await fs.promises.rm(driverPath, {recursive: true});
|
|
739
|
-
}
|
|
740
|
-
catch (e) {} // file does not exist
|
|
690
|
+
await fs.promises.rm(driverPath, {force: true});
|
|
741
691
|
await extractZip(archive, {dir: cacheDir});
|
|
742
692
|
}
|
|
743
693
|
|
|
@@ -788,9 +738,9 @@ class Edge extends Browser {
|
|
|
788
738
|
static async getDriver(version = "latest", {
|
|
789
739
|
headless = true, extensionPaths = [], incognito = false, insecure = false,
|
|
790
740
|
extraArgs = []
|
|
791
|
-
} = {}) {
|
|
741
|
+
} = {}, downloadTimeout = 0) {
|
|
792
742
|
if (platform == "linux" || platform == "darwin")
|
|
793
|
-
await Edge.installBrowser(version);
|
|
743
|
+
await Edge.installBrowser(version, downloadTimeout);
|
|
794
744
|
|
|
795
745
|
let driverPath = await Edge.#installDriver();
|
|
796
746
|
let serviceBuilder = new edge.ServiceBuilder(driverPath);
|
package/src/utils.js
CHANGED
|
@@ -23,32 +23,45 @@ import {exec} from "child_process";
|
|
|
23
23
|
|
|
24
24
|
import got from "got";
|
|
25
25
|
import dmg from "dmg";
|
|
26
|
+
import Jimp from "jimp";
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* Downloads url resources.
|
|
29
31
|
* @param {string} url - The url of the resource to be downloaded.
|
|
30
32
|
* @param {string} destFile - The destination file path.
|
|
31
|
-
* @
|
|
33
|
+
* @param {number?} timeout - Allowed time in ms for the download to complete.
|
|
34
|
+
* When set to 0 there is no time limit. Defaults to 0.
|
|
35
|
+
* @throws {TypeError} Invalid URL, Download timeout.
|
|
32
36
|
*/
|
|
33
|
-
export async function download(url, destFile) {
|
|
37
|
+
export async function download(url, destFile, timeout = 0) {
|
|
34
38
|
let cacheDir = path.dirname(destFile);
|
|
35
|
-
|
|
36
39
|
await fs.promises.mkdir(cacheDir, {recursive: true});
|
|
37
40
|
|
|
38
41
|
let tempDest = `${destFile}-${process.pid}`;
|
|
39
42
|
let writable = fs.createWriteStream(tempDest);
|
|
40
|
-
|
|
43
|
+
let timeoutID;
|
|
41
44
|
try {
|
|
42
|
-
|
|
45
|
+
let downloading = promisify(pipeline)(got.stream(url), writable);
|
|
46
|
+
if (timeout == 0) {
|
|
47
|
+
await downloading;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
let timeoutPromise = new Promise((resolve, reject) => {
|
|
51
|
+
timeoutID = setTimeout(
|
|
52
|
+
() => reject(`Download timeout after ${timeout}ms`), timeout);
|
|
53
|
+
});
|
|
54
|
+
await Promise.race([downloading, timeoutPromise]);
|
|
55
|
+
}
|
|
43
56
|
}
|
|
44
57
|
catch (error) {
|
|
45
|
-
|
|
46
|
-
await fs.promises.rm(tempDest, {recursive: true});
|
|
47
|
-
}
|
|
48
|
-
catch (e) {}
|
|
49
|
-
|
|
58
|
+
await fs.promises.rm(tempDest, {force: true});
|
|
50
59
|
throw error;
|
|
51
60
|
}
|
|
61
|
+
finally {
|
|
62
|
+
if (timeoutID)
|
|
63
|
+
clearTimeout(timeoutID);
|
|
64
|
+
}
|
|
52
65
|
|
|
53
66
|
await fs.promises.rename(tempDest, destFile);
|
|
54
67
|
}
|
|
@@ -143,3 +156,62 @@ export function wait(condition, timeout = 0, message, pollTimeout = 100) {
|
|
|
143
156
|
|
|
144
157
|
return result;
|
|
145
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @typedef {Object} Jimp
|
|
162
|
+
* @see https://github.com/oliver-moran/jimp/tree/master/packages/jimp
|
|
163
|
+
*/
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Takes a screenshot of the full page by scrolling from top to bottom.
|
|
167
|
+
* @param {webdriver} driver - The driver controlling the browser.
|
|
168
|
+
* @property {boolean} hideScrollbars=true - Hides any scrollbars before
|
|
169
|
+
* taking the screenshot, or not.
|
|
170
|
+
* @return {Jimp} A Jimp image object containing the screenshot.
|
|
171
|
+
* @example
|
|
172
|
+
* // Getting a base-64 encoded PNG from the returned Jimp image
|
|
173
|
+
* let image = await takeFullPageScreenshot(driver);
|
|
174
|
+
* let encodedPNG = await image.getBase64Async("image/png");
|
|
175
|
+
*/
|
|
176
|
+
export async function takeFullPageScreenshot(driver, hideScrollbars = true) {
|
|
177
|
+
// On macOS scrollbars appear and disappear overlapping the content as
|
|
178
|
+
// scrolling occurs. Hiding the scrollbars helps getting reproducible
|
|
179
|
+
// screenshots.
|
|
180
|
+
if (hideScrollbars) {
|
|
181
|
+
await driver.executeScript(() => {
|
|
182
|
+
if (!document.head)
|
|
183
|
+
return;
|
|
184
|
+
let style = document.createElement("style");
|
|
185
|
+
style.textContent = "html { overflow-y: scroll; }";
|
|
186
|
+
document.head.appendChild(style);
|
|
187
|
+
if (document.documentElement.clientWidth == window.innerWidth)
|
|
188
|
+
style.textContent = "html::-webkit-scrollbar { display: none; }";
|
|
189
|
+
else
|
|
190
|
+
document.head.removeChild(style);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let fullScreenshot = new Jimp(0, 0);
|
|
195
|
+
while (true) {
|
|
196
|
+
let [width, height, offset] = await driver.executeScript((...args) => {
|
|
197
|
+
window.scrollTo(0, args[0]);
|
|
198
|
+
// Math.ceil rounds up potential decimal values on window.scrollY,
|
|
199
|
+
// ensuring the loop will not hang due to never reaching enough
|
|
200
|
+
// fullScreenshot's height.
|
|
201
|
+
return [document.documentElement.clientWidth,
|
|
202
|
+
document.documentElement.scrollHeight,
|
|
203
|
+
Math.ceil(window.scrollY)];
|
|
204
|
+
}, fullScreenshot.bitmap.height);
|
|
205
|
+
let data = await driver.takeScreenshot();
|
|
206
|
+
let partialScreenshot = await Jimp.read(Buffer.from(data, "base64"));
|
|
207
|
+
let combinedScreenshot =
|
|
208
|
+
new Jimp(width, offset + partialScreenshot.bitmap.height);
|
|
209
|
+
combinedScreenshot.composite(fullScreenshot, 0, 0);
|
|
210
|
+
combinedScreenshot.composite(partialScreenshot, 0, offset);
|
|
211
|
+
fullScreenshot = combinedScreenshot;
|
|
212
|
+
if (fullScreenshot.bitmap.height >= height)
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return fullScreenshot;
|
|
217
|
+
}
|
package/test/browsers.js
CHANGED
|
@@ -19,7 +19,7 @@ import fs from "fs";
|
|
|
19
19
|
import expect from "expect";
|
|
20
20
|
import path from "path";
|
|
21
21
|
|
|
22
|
-
import {BROWSERS, snapshotsBaseDir} from "../index.js";
|
|
22
|
+
import {BROWSERS, snapshotsBaseDir, takeFullPageScreenshot} from "../index.js";
|
|
23
23
|
import {killDriverProcess} from "../src/utils.js";
|
|
24
24
|
import Jimp from "jimp";
|
|
25
25
|
|
|
@@ -104,21 +104,47 @@ function getExtension(browser, version) {
|
|
|
104
104
|
return {extensionPaths, manifest};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
async function getCachedTimes(browser) {
|
|
108
|
+
let cacheDir = path.join(snapshotsBaseDir, browser, "cache");
|
|
109
|
+
let cacheFiles = await fs.promises.readdir(cacheDir);
|
|
110
|
+
|
|
111
|
+
let browserCacheTime = null;
|
|
112
|
+
// Edge gets installed at OS level, that's why it's not tested here
|
|
113
|
+
if (browser != "edge") {
|
|
114
|
+
let installTypes = [".zip", ".dmg", ".bz2"];
|
|
115
|
+
let browserZip =
|
|
116
|
+
cacheFiles.find(elem => installTypes.some(type => elem.includes(type)));
|
|
117
|
+
let browserStat = await fs.promises.stat(path.join(cacheDir, browserZip));
|
|
118
|
+
browserCacheTime = browserStat.ctimeMs;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let driverCacheTime = null;
|
|
122
|
+
// Firefox install file includes the driver, that's why it's not tested here
|
|
123
|
+
if (browser != "firefox") {
|
|
124
|
+
let driverDir = cacheFiles.find(elem => !elem.endsWith(".zip"));
|
|
125
|
+
let driverFiles = await fs.promises.readdir(path.join(cacheDir, driverDir));
|
|
126
|
+
let driverZip = driverFiles.find(elem => elem.endsWith(".zip"));
|
|
127
|
+
let driverStat =
|
|
128
|
+
await fs.promises.stat(path.join(cacheDir, driverDir, driverZip));
|
|
129
|
+
driverCacheTime = driverStat.ctimeMs;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {browserCTime: browserCacheTime, driverCTime: driverCacheTime};
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
for (let browser of Object.keys(BROWSERS)) {
|
|
108
136
|
describe(`Browser: ${browser}`, () => {
|
|
109
137
|
before(async() => {
|
|
110
138
|
if (process.env.TEST_KEEP_SNAPSHOTS == "true")
|
|
111
139
|
return;
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
await fs.promises.rm(snapshotsBaseDir, {recursive: true});
|
|
115
|
-
}
|
|
116
|
-
catch (e) {}
|
|
141
|
+
await fs.promises.rm(snapshotsBaseDir, {force: true, recursive: true});
|
|
117
142
|
});
|
|
118
143
|
|
|
119
144
|
for (let version of VERSIONS[browser]) {
|
|
120
145
|
describe(`Version: ${version}`, () => {
|
|
121
146
|
let driver = null;
|
|
147
|
+
let emptyCacheTimes = null;
|
|
122
148
|
|
|
123
149
|
async function quitDriver() {
|
|
124
150
|
if (!driver)
|
|
@@ -140,7 +166,7 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
140
166
|
this.skip();
|
|
141
167
|
|
|
142
168
|
let {binary, versionNumber} =
|
|
143
|
-
await BROWSERS[browser].installBrowser(version);
|
|
169
|
+
await BROWSERS[browser].installBrowser(version, 60000);
|
|
144
170
|
let browserName = browser == "edge" ? /(edge|Edge)/ : browser;
|
|
145
171
|
expect(binary).toEqual(expect.stringMatching(browserName));
|
|
146
172
|
|
|
@@ -148,6 +174,9 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
148
174
|
await BROWSERS[browser].getInstalledVersion(binary);
|
|
149
175
|
expect(installedVersion).toEqual(
|
|
150
176
|
expect.stringContaining(normalize(versionNumber)));
|
|
177
|
+
|
|
178
|
+
// Adding the version number to the test title for logging purposes
|
|
179
|
+
this.test.title = `${this.test.title} [v${versionNumber}]`;
|
|
151
180
|
});
|
|
152
181
|
|
|
153
182
|
it("runs", async() => {
|
|
@@ -162,6 +191,23 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
162
191
|
|
|
163
192
|
expect((await driver.getCapabilities()).getBrowserName())
|
|
164
193
|
.toEqual(expect.stringMatching(names[browser]));
|
|
194
|
+
|
|
195
|
+
// When running all tests, this saves time on the cache test
|
|
196
|
+
emptyCacheTimes = await getCachedTimes(browser);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("uses cached install files", async() => {
|
|
200
|
+
if (emptyCacheTimes == null) { // Single test case run
|
|
201
|
+
driver = await BROWSERS[browser].getDriver(version);
|
|
202
|
+
emptyCacheTimes = await getCachedTimes(browser);
|
|
203
|
+
await quitDriver();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// assigning `driver` to allow the afterEach hook quit the driver
|
|
207
|
+
driver = await BROWSERS[browser].getDriver(version);
|
|
208
|
+
let existingCacheTimes = await getCachedTimes(browser);
|
|
209
|
+
|
|
210
|
+
expect(existingCacheTimes).toEqual(emptyCacheTimes);
|
|
165
211
|
});
|
|
166
212
|
|
|
167
213
|
it("supports extra args", async() => {
|
|
@@ -186,7 +232,7 @@ for (let browser of Object.keys(BROWSERS)) {
|
|
|
186
232
|
driver = await BROWSERS[browser].getDriver(version);
|
|
187
233
|
await driver.navigate().to(TEST_URL_LONG_PAGE);
|
|
188
234
|
|
|
189
|
-
let fullImg = await
|
|
235
|
+
let fullImg = await takeFullPageScreenshot(driver);
|
|
190
236
|
// Taking a regular webdriver screenshot, which should be shorter
|
|
191
237
|
let data = await driver.takeScreenshot();
|
|
192
238
|
let partImg = await Jimp.read(Buffer.from(data, "base64"));
|
package/test/utils.js
CHANGED
|
@@ -25,18 +25,34 @@ describe("Utils", () => {
|
|
|
25
25
|
it("defines a browser snapshots folder", () => expect(snapshotsBaseDir)
|
|
26
26
|
.toBe(path.join(process.cwd(), "browser-snapshots")));
|
|
27
27
|
|
|
28
|
+
let resourceUrl = "https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/raw/main/package.json";
|
|
28
29
|
let destFile = path.join(snapshotsBaseDir, "download-test.txt");
|
|
30
|
+
let expectedFileContents = "\"name\": \"@eyeo/get-browser-binary\"";
|
|
29
31
|
|
|
30
32
|
it("downloads resources", async() => {
|
|
31
|
-
|
|
32
|
-
await download(
|
|
33
|
+
await fs.promises.rm(destFile, {force: true});
|
|
34
|
+
await download(resourceUrl, destFile);
|
|
33
35
|
let contents = await fs.promises.readFile(destFile, {encoding: "utf8"});
|
|
34
|
-
expect(contents).toEqual(
|
|
35
|
-
expect.stringContaining("\"name\": \"@eyeo/get-browser-binary\""));
|
|
36
|
+
expect(contents).toEqual(expect.stringContaining(expectedFileContents));
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
it("
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
it("downloads resources on long timeout", async() => {
|
|
40
|
+
await fs.promises.rm(destFile, {force: true});
|
|
41
|
+
await download(resourceUrl, destFile, 10000);
|
|
42
|
+
let contents = await fs.promises.readFile(destFile, {encoding: "utf8"});
|
|
43
|
+
expect(contents).toEqual(expect.stringContaining(expectedFileContents));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("does not download on short timeout", async() => {
|
|
47
|
+
try {
|
|
48
|
+
await download(resourceUrl, destFile, 10);
|
|
49
|
+
throw new Error("Download timeout didn't throw");
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
expect(err).toBe("Download timeout after 10ms");
|
|
53
|
+
}
|
|
41
54
|
});
|
|
55
|
+
|
|
56
|
+
it("does not download invalid resources", () =>
|
|
57
|
+
expect(download("invalid", destFile)).rejects.toThrow("Invalid URL"));
|
|
42
58
|
});
|