@eyeo/get-browser-binary 0.4.0 → 0.5.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/.eslintrc.json CHANGED
@@ -5,7 +5,8 @@
5
5
  "root": true,
6
6
  "env": {
7
7
  "es2022": true,
8
- "node": true
8
+ "node": true,
9
+ "browser": true
9
10
  },
10
11
  "rules": {
11
12
  "brace-style": ["error", "stroustrup"],
package/.gitlab-ci.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  default:
2
- image: node:16-bullseye-slim
2
+ image: registry.gitlab.com/eyeo/docker/get-browser-binary:node16
3
3
  interruptible: true
4
4
 
5
5
  stages:
@@ -37,7 +37,7 @@ test:browsers:linux:
37
37
  before_script:
38
38
  - docker build -f test/docker/Dockerfile -t browsers .
39
39
  script:
40
- - docker run browsers
40
+ - docker run --shm-size=256m browsers
41
41
 
42
42
  test:browsers:windows:
43
43
  stage: test
@@ -51,7 +51,6 @@ test:browsers:windows:
51
51
  - Start-Process msiexec
52
52
  -ArgumentList "/i MicrosoftEdgeEnterpriseX64.msi /norestart /qn" -Wait
53
53
  - choco upgrade -y nodejs --version 16.10.0
54
- - npm install -g npm
55
54
  - npm install
56
55
  script:
57
56
  # Running only a subset of Firefox and Opera tests to avoid low OS resources error
package/README.md CHANGED
@@ -3,11 +3,6 @@
3
3
  Download specific browser versions for Chromium, Firefox, Edge and Opera, and
4
4
  their matching [selenium webdriver](https://www.selenium.dev/selenium/docs/api/javascript/index.html).
5
5
 
6
- Note: Edge download is only supported on Linux. On other platforms it is assumed
7
- to be already installed.
8
-
9
- Note: Using the `operadriver` on Windows is not supported.
10
-
11
6
  ## Getting started
12
7
 
13
8
  The sample below shows how to download the latest Chromium and run it using
@@ -17,12 +12,10 @@ selenium webdriver:
17
12
  import {BROWSERS} from "@eyeo/get-browser-binary";
18
13
 
19
14
  (async function example() {
20
- let {chromium} = BROWSERS;
21
-
22
- let {binary} = await chromium.downloadBinary();
15
+ let {binary} = await BROWSERS.chromium.downloadBinary("latest");
23
16
  console.log(`Chromium binary downloaded to ${binary}`);
24
17
 
25
- let driver = await chromium.getDriver();
18
+ let driver = await BROWSERS.chromium.getDriver("latest");
26
19
  await driver.navigate().to("https://example.com/");
27
20
  await driver.quit();
28
21
  })();
@@ -35,6 +28,18 @@ For more information, please refer to the [API documention](https://gitlab.com/e
35
28
  If you are already on the documentation page, you may find the API contents on
36
29
  the right side.
37
30
 
31
+ ### Supported browser versions
32
+
33
+ - Chromium >= 75
34
+ - Firefox >= 60
35
+ - Edge >= 95
36
+ - Opera >= 62
37
+
38
+ Note: Edge download is not supported on Windows. It is assumed to be installed
39
+ because it is the default browser on that platform.
40
+
41
+ Note: Using the `operadriver` on Windows is not supported.
42
+
38
43
  ## Development
39
44
 
40
45
  ### Prerequisites
@@ -79,13 +84,13 @@ Useful to reproduce the CI environment of the `test:browsers:linux` job:
79
84
 
80
85
  ```shell
81
86
  docker build -f test/docker/Dockerfile -t browsers .
82
- docker run -it browsers
87
+ docker run --shm-size=256m -it browsers
83
88
  ```
84
89
 
85
90
  The `grep` option can also be used on Docker via the `TEST_ARGS` parameter:
86
91
 
87
92
  ```shell
88
- docker run -e TEST_ARGS="--grep chromium.*latest" -it browsers
93
+ docker run --shm-size=256m -e TEST_ARGS="--grep chromium.*latest" -it browsers
89
94
  ```
90
95
 
91
96
  Note: For a full automated run, `opera` tests should not use the interactive
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 0.5.0
2
+
3
+ - Enables Edge binary downloads on MacOS (!25)
4
+ - Sets unsupported browser versions (#16)
5
+ - Fixes the Edge repo url (!27)
6
+ - Fixes an issue when downloading Opera (#21)
7
+ - Fixes redundant Opera installations on Linux (!24)
8
+
1
9
  # 0.4.0
2
10
 
3
11
  - Adds Opera as a supported browser (#7)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Download browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,6 @@
21
21
  "fs-extra": "^10.0.0",
22
22
  "geckodriver": "^3.0.2",
23
23
  "got": "^11.8.2",
24
- "msedgedriver": "^91.0.0",
25
24
  "selenium-webdriver": "^4.2.0"
26
25
  },
27
26
  "devDependencies": {
package/src/browsers.js CHANGED
@@ -25,6 +25,7 @@ import got from "got";
25
25
  import webdriver from "selenium-webdriver";
26
26
  import chrome from "selenium-webdriver/chrome.js";
27
27
  import firefox from "selenium-webdriver/firefox.js";
28
+ import edge from "selenium-webdriver/edge.js";
28
29
  import command from "selenium-webdriver/lib/command.js";
29
30
  import extractZip from "extract-zip";
30
31
 
@@ -38,10 +39,17 @@ import {download, extractTar, extractDmg, getBrowserVersion, killDriverProcess,
38
39
  export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
39
40
 
40
41
  let {until, By} = webdriver;
41
- const ERROR_DOWNLOAD_NOT_SUPPORTED =
42
- "Downloading this browser is not supported";
43
42
  let platform = `${process.platform}-${process.arch}`;
44
43
 
44
+ function checkVersion(version, minVersion, channels) {
45
+ if (channels.includes(version))
46
+ return;
47
+
48
+ let mainVersion = parseInt(version && version.split(".")[0], 10);
49
+ if (mainVersion < minVersion)
50
+ throw new Error(`Unsupported browser version: ${version}`);
51
+ }
52
+
45
53
  /**
46
54
  * Base class for browser download functionality. Please see subclasses for
47
55
  * browser specific details.
@@ -60,9 +68,10 @@ class Browser {
60
68
  * @param {string} version - Either full version number or channel/release.
61
69
  * Please find examples on the subclasses.
62
70
  * @return {BrowserBinary}
71
+ * @throws {Error} Unsupported browser version.
63
72
  */
64
73
  static async downloadBinary(version) {
65
- throw new Error(ERROR_DOWNLOAD_NOT_SUPPORTED);
74
+ // to be implemented by the subclass
66
75
  }
67
76
 
68
77
  /**
@@ -81,7 +90,7 @@ class Browser {
81
90
  else {
82
91
  ({stdout} = await promisify(execFile)(binary, ["--version"]));
83
92
  }
84
- return stdout;
93
+ return stdout.trim();
85
94
  }
86
95
 
87
96
  /**
@@ -107,11 +116,10 @@ class Browser {
107
116
  * Installs the webdriver matching the browser version and runs the
108
117
  * browser. If needed, the browser binary is also installed.
109
118
  * @param {string} version - Either full version number or channel/release.
110
- * Please find examples on the subclasses. On Edge this parameter has
111
- * no effect.
119
+ * Please find examples on the subclasses.
112
120
  * @param {driverOptions?} options - Options to start the browser with.
113
121
  * @return {webdriver}
114
- * @throws {Error} Unsupported webdriver version.
122
+ * @throws {Error} Unsupported browser version.
115
123
  */
116
124
  static async getDriver(version, options = {}) {
117
125
  // to be implemented by the subclass
@@ -123,6 +131,7 @@ class Browser {
123
131
  * @param {webdriver} driver - The driver controlling the browser.
124
132
  * @param {string} extensionTitle - Title of the extebsion to be enabled.
125
133
  * @return {webdriver}
134
+ * @throws {Error} Unsupported browser version.
126
135
  */
127
136
  static async enableExtensionInIncognito(driver, extensionTitle) {
128
137
  // Allowing the extension in incognito mode can't happen programmatically:
@@ -145,28 +154,30 @@ class Chromium extends Browser {
145
154
  return data.chromium_base_position;
146
155
  }
147
156
 
148
- static async #getLatestVersion(channel = "stable") {
149
- let os = process.platform;
150
- if (os == "win32")
151
- os = process.arch == "x64" ? "win64" : "win";
152
- else if (os == "darwin")
153
- os = process.arch == "arm64" ? "mac_arm64" : "mac";
157
+ static async #getVersionForChannel(channel) {
158
+ if (channel == "latest")
159
+ channel = "stable";
154
160
 
161
+ let os = {
162
+ "win32-ia32": "win",
163
+ "win32-x64": "win64",
164
+ "linux-x64": "linux",
165
+ "darwin-x64": "mac",
166
+ "dawrin-arm64": "mac_arm64"
167
+ }[platform];
155
168
  let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
156
169
  let release = data[0].versions.find(ver => ver.channel == channel);
157
- let {current_version: version, branch_base_position: base} = release;
170
+ let {current_version: version} = release;
158
171
 
159
- if (release.true_branch && release.true_branch.includes("_")) {
172
+ if (release.true_branch && release.true_branch.includes("_"))
160
173
  // A wrong base may be caused by a mini-branch (patched) release
161
174
  // In that case, the base is taken from the unpatched version
162
175
  version = [...version.split(".").slice(0, 3), "0"].join(".");
163
- base = await Chromium.#getBranchBasePosition(version);
164
- }
165
176
 
166
- return {version, base};
177
+ return version;
167
178
  }
168
179
 
169
- static #getChromiumBinary(dir) {
180
+ static #getBinaryPath(dir) {
170
181
  switch (process.platform) {
171
182
  case "win32":
172
183
  return path.join(dir, "chrome-win", "chrome.exe");
@@ -201,7 +212,7 @@ class Chromium extends Browser {
201
212
 
202
213
  try {
203
214
  await fs.promises.access(browserDir);
204
- return {binary: Chromium.#getChromiumBinary(browserDir), revision};
215
+ return {binary: Chromium.#getBinaryPath(browserDir), revision};
205
216
  }
206
217
  catch (e) {}
207
218
 
@@ -230,7 +241,7 @@ class Chromium extends Browser {
230
241
  }
231
242
 
232
243
  await extractZip(archive, {dir: browserDir});
233
- return {binary: Chromium.#getChromiumBinary(browserDir), revision};
244
+ return {binary: Chromium.#getBinaryPath(browserDir), revision};
234
245
  }
235
246
 
236
247
  /**
@@ -239,15 +250,19 @@ class Chromium extends Browser {
239
250
  * number (i.e. "77.0.3865.0"). Defaults to "latest".
240
251
  * @return {BrowserBinary}
241
252
  */
242
- static async downloadBinary(version) {
243
- let base;
244
- if (version && !(version == "beta" || version == "dev"))
245
- base = await Chromium.#getBranchBasePosition(version);
246
- else
247
- ({version, base} = await Chromium.#getLatestVersion(version));
253
+ static async downloadBinary(version = "latest") {
254
+ const MIN_VERSION = 75;
255
+ const CHANNELS = ["latest", "beta", "dev"];
256
+
257
+ checkVersion(version, MIN_VERSION, CHANNELS);
258
+
259
+ let versionNumber = CHANNELS.includes(version) ?
260
+ await Chromium.#getVersionForChannel(version) : version;
261
+
262
+ let base = await Chromium.#getBranchBasePosition(versionNumber);
248
263
 
249
264
  let {binary, revision} = await Chromium.#downloadChromium(base);
250
- return {binary, versionNumber: version, revision};
265
+ return {binary, versionNumber, revision};
251
266
  }
252
267
 
253
268
  static async #installDriver(revision, version) {
@@ -274,9 +289,10 @@ class Chromium extends Browser {
274
289
  }
275
290
 
276
291
  /** @see Browser.getDriver */
277
- static async getDriver(version, {headless = true, extensionPaths = [],
278
- incognito = false, insecure = false,
279
- extraArgs = []} = {}) {
292
+ static async getDriver(version = "latest", {
293
+ headless = true, extensionPaths = [], incognito = false, insecure = false,
294
+ extraArgs = []
295
+ } = {}) {
280
296
  let {binary, revision, versionNumber} =
281
297
  await Chromium.downloadBinary(version);
282
298
  await Chromium.#installDriver(revision, versionNumber);
@@ -301,14 +317,8 @@ class Chromium extends Browser {
301
317
 
302
318
  /** @see Browser.enableExtensionInIncognito */
303
319
  static async enableExtensionInIncognito(driver, extensionTitle) {
304
- let version = await getBrowserVersion(driver);
305
- // Webdriver capabilities don't include a browser version for Opera
306
- if (version && version < 75)
307
- // The UI workaround needs a chromedriver >= 75
308
- throw new Error(`Only supported on Chromium >= 75. Current version: ${version}`);
309
-
310
320
  await driver.navigate().to("chrome://extensions");
311
- await driver.executeScript(`
321
+ await driver.executeScript((...args) => {
312
322
  let enable = () => document.querySelector("extensions-manager").shadowRoot
313
323
  .querySelector("extensions-detail-view").shadowRoot
314
324
  .getElementById("allow-incognito").shadowRoot
@@ -321,7 +331,7 @@ class Chromium extends Browser {
321
331
  return new Promise((resolve, reject) => {
322
332
  let extensionDetails;
323
333
  for (let {shadowRoot} of extensions) {
324
- if (shadowRoot.getElementById("name").innerHTML != arguments[0])
334
+ if (shadowRoot.getElementById("name").innerHTML != args[0])
325
335
  continue;
326
336
 
327
337
  extensionDetails = shadowRoot.getElementById("detailsButton");
@@ -332,7 +342,8 @@ class Chromium extends Browser {
332
342
 
333
343
  extensionDetails.click();
334
344
  setTimeout(() => resolve(enable()), 100);
335
- });`, extensionTitle);
345
+ });
346
+ }, extensionTitle);
336
347
  }
337
348
  }
338
349
 
@@ -342,13 +353,13 @@ class Chromium extends Browser {
342
353
  * @extends Browser
343
354
  */
344
355
  class Firefox extends Browser {
345
- static async #getLatestVersion(branch) {
356
+ static async #getVersionForChannel(branch) {
346
357
  let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
347
358
  return branch == "beta" ?
348
359
  data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
349
360
  }
350
361
 
351
- static #getFirefoxBinary(dir) {
362
+ static #getBinaryPath(dir) {
352
363
  switch (process.platform) {
353
364
  case "win32":
354
365
  return path.join(dir, "core", "firefox.exe");
@@ -391,7 +402,7 @@ class Firefox extends Browser {
391
402
 
392
403
  try {
393
404
  await fs.promises.access(browserDir);
394
- return Firefox.#getFirefoxBinary(browserDir);
405
+ return Firefox.#getBinaryPath(browserDir);
395
406
  }
396
407
  catch (e) {}
397
408
 
@@ -405,7 +416,7 @@ class Firefox extends Browser {
405
416
  }
406
417
 
407
418
  await Firefox.#extractFirefoxArchive(archive, browserDir);
408
- return Firefox.#getFirefoxBinary(browserDir);
419
+ return Firefox.#getBinaryPath(browserDir);
409
420
  }
410
421
 
411
422
  /**
@@ -414,18 +425,24 @@ class Firefox extends Browser {
414
425
  * number (i.e. "68.0"). Defaults to "latest".
415
426
  * @return {BrowserBinary}
416
427
  */
417
- static async downloadBinary(version) {
418
- if (!version || version == "beta")
419
- version = await Firefox.#getLatestVersion(version);
428
+ static async downloadBinary(version = "latest") {
429
+ const MIN_VERSION = 60;
430
+ const CHANNELS = ["latest", "beta"];
431
+
432
+ checkVersion(version, MIN_VERSION, CHANNELS);
420
433
 
421
- let binary = await Firefox.#downloadFirefox(version);
422
- return {binary, versionNumber: version};
434
+ let versionNumber = CHANNELS.includes(version) ?
435
+ await Firefox.#getVersionForChannel(version) : version;
436
+
437
+ let binary = await Firefox.#downloadFirefox(versionNumber);
438
+ return {binary, versionNumber};
423
439
  }
424
440
 
425
441
  /** @see Browser.getDriver */
426
- static async getDriver(version, {headless = true, extensionPaths = [],
427
- incognito = false, insecure = false,
428
- extraArgs = []} = {}) {
442
+ static async getDriver(version = "latest", {
443
+ headless = true, extensionPaths = [], incognito = false, insecure = false,
444
+ extraArgs = []
445
+ } = {}) {
429
446
  let {binary} = await Firefox.downloadBinary(version);
430
447
 
431
448
  let options = new firefox.Options();
@@ -502,7 +519,7 @@ class Edge extends Browser {
502
519
  return {versionNumber: version, channel: "stable"};
503
520
 
504
521
  let channel = version == "latest" ? "stable" : version;
505
- let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}`);
522
+ let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`);
506
523
  let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
507
524
  let matches;
508
525
  let versionNumbers = [];
@@ -516,6 +533,28 @@ class Edge extends Browser {
516
533
  return {versionNumber, channel};
517
534
  }
518
535
 
536
+ static #getDarwinAppName(channel) {
537
+ let extra = channel == "stable" ?
538
+ "" : " " + channel.charAt(0).toUpperCase() + channel.slice(1);
539
+ return `Microsoft Edge${extra}`;
540
+ }
541
+
542
+ static #getBinaryPath(channel = "stable") {
543
+ switch (process.platform) {
544
+ case "win32":
545
+ return "${Env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\" +
546
+ "msedge.exe";
547
+ case "linux":
548
+ return channel == "stable" ?
549
+ "microsoft-edge" : `microsoft-edge-${channel}`;
550
+ case "darwin":
551
+ let appName = Edge.#getDarwinAppName(channel);
552
+ return `${process.env.HOME}/Applications/${appName}.app/Contents/MacOS/${appName}`;
553
+ default:
554
+ throw new Error(`Unexpected platform: ${process.platform}`);
555
+ }
556
+ }
557
+
519
558
  /**
520
559
  * Downloads the browser binary file.
521
560
  * @param {string} version - Either "latest", "beta", "dev" or a full version
@@ -524,20 +563,36 @@ class Edge extends Browser {
524
563
  * @return {BrowserBinary}
525
564
  */
526
565
  static async downloadBinary(version = "latest") {
527
- if (process.platform != "linux")
528
- throw new Error("Edge download is only supported on Linux");
529
-
566
+ if (process.platform == "win32")
567
+ // Edge is mandatory on Windows, can't be uninstalled or downgraded
568
+ // https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
569
+ throw new Error("Edge download is not supported in Windows");
570
+
571
+ const MIN_VERSION = 95;
572
+ const CHANNELS = ["latest", "beta", "dev"];
573
+ checkVersion(version, MIN_VERSION, CHANNELS);
530
574
  let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
531
575
 
532
- let snapshotsDir = path.join(snapshotsBaseDir, "edge");
533
- let filename = `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`;
534
- let archive = path.join(snapshotsDir, "cache", filename);
535
- let binary = {
536
- stable: "microsoft-edge",
537
- beta: "microsoft-edge-beta",
538
- dev: "microsoft-edge-dev"
576
+ let darwinName = {
577
+ stable: "MicrosoftEdge",
578
+ beta: "MicrosoftEdgeBeta",
579
+ dev: "MicrosoftEdgeDev"
539
580
  }[channel];
581
+ let filename = {
582
+ linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
583
+ darwin: `${darwinName}-${versionNumber}.pkg`
584
+ }[process.platform];
585
+ let darwinArch = process.arch == "arm64" ?
586
+ "03adf619-38c6-4249-95ff-4a01c0ffc962" :
587
+ "C1297A47-86C4-4C1F-97FA-950631F94777";
588
+ let downloadUrl = {
589
+ linux: `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
590
+ darwin: `https://officecdnmac.microsoft.com/pr/${darwinArch}/MacAutoupdate/${filename}`
591
+ }[process.platform];
540
592
 
593
+ let snapshotsDir = path.join(snapshotsBaseDir, "edge");
594
+ let archive = path.join(snapshotsDir, "cache", filename);
595
+ let binary = Edge.#getBinaryPath(channel);
541
596
  try {
542
597
  if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
543
598
  return {binary, versionNumber};
@@ -545,34 +600,38 @@ class Edge extends Browser {
545
600
  catch (e) {}
546
601
 
547
602
  try {
548
- await download(
549
- `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
550
- archive
551
- );
603
+ await download(downloadUrl, archive);
552
604
  }
553
605
  catch (err) {
554
606
  throw new Error(`Edge download failed: ${err}`);
555
607
  }
556
- await promisify(exec)(`dpkg -i ${archive}`);
608
+
609
+ if (process.platform == "linux") {
610
+ await promisify(exec)(`dpkg -i ${archive}`);
611
+ }
612
+ else if (process.platform == "darwin") {
613
+ let appName = Edge.#getDarwinAppName(channel);
614
+ try {
615
+ await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {recursive: true});
616
+ }
617
+ catch (e) {}
618
+ await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
619
+ }
557
620
 
558
621
  return {binary, versionNumber};
559
622
  }
560
623
 
561
624
  static async #getInstalledVersionNumber(binary) {
562
625
  let installedVersion = await Edge.getInstalledVersion(binary);
563
- return installedVersion.replace("beta", "").replace("dev", "")
564
- .trim().replace(/.*\s/, "");
626
+ for (let word of ["beta", "dev", "Beta", "Dev"])
627
+ installedVersion = installedVersion.replace(word, "");
628
+ return installedVersion.trim().replace(/.*\s/, "");
565
629
  }
566
630
 
567
631
  static async #installDriver() {
568
- let binary = {
569
- win32:
570
- "${Env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\msedge.exe",
571
- darwin: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
572
- linux: "microsoft-edge"
573
- }[process.platform];
574
- let version = await Edge.#getInstalledVersionNumber(binary);
575
- if (!version)
632
+ let binary = Edge.#getBinaryPath();
633
+ let versionNumber = await Edge.#getInstalledVersionNumber(binary);
634
+ if (!versionNumber)
576
635
  throw new Error("Edge is not installed");
577
636
 
578
637
  let [zip, driver] = {
@@ -582,14 +641,11 @@ class Edge extends Browser {
582
641
  "darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
583
642
  "darwin-arm64": ["edgedriver_arm64.zip", "msedgedriver"]
584
643
  }[platform];
585
- let cacheDir = path.join(snapshotsBaseDir, "edge", "cache");
586
- let driverBinDir = path.join(process.cwd(), "node_modules", Edge.#DRIVER,
587
- "bin");
588
- let driverLibDir = path.join(process.cwd(), "node_modules", Edge.#DRIVER,
589
- "lib", Edge.#DRIVER);
590
- let archive = path.join(cacheDir, `${version}-${zip}`);
591
-
592
- let vSplit = version.split(".");
644
+ let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
645
+ `edgedriver-${versionNumber}`);
646
+ let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
647
+
648
+ let vSplit = versionNumber.split(".");
593
649
  let lastNum = parseInt(vSplit[3], 10);
594
650
  while (lastNum >= 0) {
595
651
  try {
@@ -604,53 +660,50 @@ class Edge extends Browser {
604
660
  }
605
661
 
606
662
  if (lastNum < 0)
607
- throw new Error(`msedgedriver was not found for Edge ${version}`);
663
+ throw new Error(`msedgedriver was not found for Edge ${versionNumber}`);
608
664
 
609
- await extractZip(archive, {dir: cacheDir});
610
665
  await killDriverProcess(Edge.#DRIVER);
611
- for (let destinationDir of [driverBinDir, driverLibDir]) {
612
- await fs.promises.mkdir(destinationDir, {recursive: true});
613
- await fs.promises.copyFile(path.join(cacheDir, driver),
614
- path.join(destinationDir, driver));
666
+ let driverPath = path.join(cacheDir, driver);
667
+ try {
668
+ await fs.promises.rm(driverPath, {recursive: true});
615
669
  }
670
+ catch (e) {} // file does not exist
671
+ await extractZip(archive, {dir: cacheDir});
672
+
673
+ return driverPath;
616
674
  }
617
675
 
618
676
  /** @see Browser.getDriver */
619
- static async getDriver(version, {headless = true, extensionPaths = [],
620
- incognito = false, insecure = false,
621
- extraArgs = []} = {}) {
622
- if (process.platform == "linux")
677
+ static async getDriver(version = "latest", {
678
+ headless = true, extensionPaths = [], incognito = false, insecure = false,
679
+ extraArgs = []
680
+ } = {}) {
681
+ if (process.platform == "linux" || process.platform == "darwin")
623
682
  await Edge.downloadBinary(version);
624
683
 
625
- await Edge.#installDriver();
684
+ let driverPath = await Edge.#installDriver();
685
+ let serviceBuilder = new edge.ServiceBuilder(driverPath);
626
686
 
627
- let args = ["no-sandbox", ...extraArgs];
687
+ let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
628
688
  if (headless)
629
- args.push("headless");
689
+ options.headless();
630
690
  if (extensionPaths.length > 0)
631
- args.push(`load-extension=${extensionPaths.join(",")}`);
691
+ options.addArguments(`load-extension=${extensionPaths.join(",")}`);
632
692
  if (incognito)
633
- args.push("incognito");
693
+ options.addArguments("incognito");
694
+ if (insecure)
695
+ options.addArguments("ignore-certificate-errors");
634
696
 
635
697
  let builder = new webdriver.Builder();
636
698
  builder.forBrowser("MicrosoftEdge");
637
- builder.withCapabilities({
638
- "browserName": "MicrosoftEdge",
639
- "ms:edgeChromium": true,
640
- "ms:edgeOptions": {args},
641
- "acceptInsecureCerts": insecure
642
- });
699
+ builder.setEdgeOptions(options);
700
+ builder.setEdgeService(serviceBuilder);
643
701
 
644
702
  return builder.build();
645
703
  }
646
704
 
647
705
  /** @see Browser.enableExtensionInIncognito */
648
706
  static async enableExtensionInIncognito(driver, extensionTitle) {
649
- let version = await getBrowserVersion(driver);
650
- if (version < 79)
651
- // The UI workaround needs a chromium based msedgedriver
652
- throw new Error(`Only supported on Edge >= 79. Current version: ${version}`);
653
-
654
707
  await driver.navigate().to("edge://extensions/");
655
708
  for (let elem of await driver.findElements(By.css("[role=listitem]"))) {
656
709
  let text = await elem.getAttribute("innerHTML");
@@ -677,7 +730,7 @@ class Edge extends Browser {
677
730
  * @extends Browser
678
731
  */
679
732
  class Opera extends Browser {
680
- static async #getVersionForChannel(version) {
733
+ static async #getVersionForChannel(version, platformDir) {
681
734
  let channelPath = "opera/desktop";
682
735
  let filePrefix = "Opera";
683
736
  if (version != "latest")
@@ -686,12 +739,24 @@ class Opera extends Browser {
686
739
  let {body} = await got(`https://ftp.opera.com/pub/${channelPath}`);
687
740
  let regex = /href="(\d.*)\/"/gm;
688
741
  let matches = body.match(regex);
689
- let versionNumber = regex.exec(matches[matches.length - 1])[1];
742
+ let versionNumber;
743
+ while (matches.length > 0) {
744
+ let result = regex.exec(matches.pop());
745
+ if (!result)
746
+ continue;
747
+
748
+ versionNumber = result[1];
749
+ try {
750
+ await got(`https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}`);
751
+ break;
752
+ }
753
+ catch (e) {}
754
+ }
690
755
 
691
756
  return {versionNumber, channelPath, filePrefix};
692
757
  }
693
758
 
694
- static #getOperaBinary(dir) {
759
+ static #getBinaryPath(dir) {
695
760
  switch (process.platform) {
696
761
  case "win32":
697
762
  return path.join(dir, "launcher.exe");
@@ -708,7 +773,7 @@ class Opera extends Browser {
708
773
  let child = spawn("dpkg", ["-i", archive]);
709
774
 
710
775
  child.stdout.on("data", data => {
711
- if (data.toString().startsWith("Do you want to update Opera")) {
776
+ if (data.toString().includes("Do you want to update Opera")) {
712
777
  process.stdin.pipe(child.stdin);
713
778
  child.stdin.write("no\r\n");
714
779
  }
@@ -761,8 +826,10 @@ class Opera extends Browser {
761
826
  * @return {BrowserBinary}
762
827
  */
763
828
  static async downloadBinary(version = "latest") {
764
- let {versionNumber, channelPath, filePrefix} =
765
- await Opera.#getVersionForChannel(version);
829
+ const MIN_VERSION = 62;
830
+ const CHANNELS = ["latest"];
831
+
832
+ checkVersion(version, MIN_VERSION, CHANNELS);
766
833
 
767
834
  let [platformDir, fileSuffix] = {
768
835
  "win32-ia32": ["win", "Autoupdate.exe"],
@@ -772,13 +839,20 @@ class Opera extends Browser {
772
839
  "dawrin-arm64": ["mac", "Autoupdate_arm64.tar.xz"]
773
840
  }[platform];
774
841
 
842
+ let {versionNumber, channelPath, filePrefix} =
843
+ await Opera.#getVersionForChannel(version, platformDir);
844
+
775
845
  let snapshotsDir = path.join(snapshotsBaseDir, "opera");
776
846
  let browserDir = path.join(snapshotsDir, `opera-${platform}-${versionNumber}`);
777
847
  let filename = `${filePrefix}_${versionNumber}_${fileSuffix}`;
778
848
  let archive = path.join(snapshotsDir, "cache", filename);
779
849
 
780
- let binary = Opera.#getOperaBinary(browserDir);
850
+ let binary = Opera.#getBinaryPath(browserDir);
781
851
  try {
852
+ if (process.platform == "linux" &&
853
+ await Opera.getInstalledVersion("opera") == versionNumber)
854
+ return {binary, versionNumber};
855
+
782
856
  await fs.promises.access(browserDir);
783
857
  return {binary, versionNumber};
784
858
  }
@@ -789,10 +863,13 @@ class Opera extends Browser {
789
863
  await fs.promises.access(archive);
790
864
  }
791
865
  catch (e) {
792
- await download(
793
- `https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}/${filename}`,
794
- archive
795
- );
866
+ let url = `https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}/${filename}`;
867
+ try {
868
+ await download(url, archive);
869
+ }
870
+ catch (err) {
871
+ throw new Error(`Browser download unavailable at ${url}\n${err}`);
872
+ }
796
873
  }
797
874
 
798
875
  await Opera.#extractOperaArchive(archive, browserDir, filename);
@@ -812,7 +889,7 @@ class Opera extends Browser {
812
889
  versionNumber = versionNumber.split(".")[0];
813
890
 
814
891
  let cacheDir = path.join(snapshotsBaseDir, "opera", "cache",
815
- `driver-for-opera-${versionNumber}`);
892
+ `operadriver-${versionNumber}`);
816
893
  let archive = path.join(cacheDir, zip);
817
894
 
818
895
  let {body} = await got(`https://github.com/operasoftware/operachromiumdriver/releases?q=Opera+${versionNumber}&expanded=true`);
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "env": {
3
- "mocha": true
3
+ "mocha": true,
4
+ "browser": true
4
5
  }
5
6
  }
package/test/browsers.js CHANGED
@@ -25,13 +25,12 @@ import {killDriverProcess} from "../src/utils.js";
25
25
  // Required to set the driver path on Windows
26
26
  import "chromedriver";
27
27
  import "geckodriver";
28
- import "msedgedriver";
29
28
 
30
29
  const VERSIONS = {
31
- chromium: [void 0, "77.0.3865.0", "beta", "dev"],
32
- firefox: [void 0, "68.0", "beta"],
33
- edge: [void 0, "95.0.1020.53", "beta", "dev"],
34
- opera: [void 0, "64.0.3417.92"]
30
+ chromium: ["latest", "75.0.3770.0", "beta", "dev"],
31
+ firefox: ["latest", "60.0", "beta"],
32
+ edge: ["latest", "95.0.1020.53", "beta", "dev"],
33
+ opera: ["latest", "62.0.3331.66"]
35
34
  };
36
35
  const TEST_URL = "https://gitlab.com/eyeo/developer-experience/get-browser-binary";
37
36
  let extensionPaths = [path.resolve(process.cwd(), "test", "extension")];
@@ -74,9 +73,27 @@ function normalize(version) {
74
73
  return normalized.split("b")[0];
75
74
  }
76
75
 
76
+ function getWindowSize(driver) {
77
+ return driver.executeScript(() => {
78
+ return {height: window.innerHeight, width: window.innerWidth};
79
+ });
80
+ }
81
+
82
+ expect.extend({
83
+ toMeasureLessThan(small, big) {
84
+ let pass = small.width < big.width || small.height < big.height;
85
+ let message = () =>
86
+ `expected small sizes (w: ${small.width}, h: ${small.height}) ` +
87
+ `to be smaller than big sizes (w: ${big.width}, h: ${big.height})`;
88
+ if (pass)
89
+ return {message, pass: true};
90
+ return {message, pass: false};
91
+ }
92
+ });
93
+
77
94
  for (let browser of Object.keys(BROWSERS)) {
78
95
  describe(`Browser: ${browser}`, function() {
79
- this.timeout(40000);
96
+ this.timeout(150000);
80
97
 
81
98
  before(async() => {
82
99
  try {
@@ -86,9 +103,10 @@ for (let browser of Object.keys(BROWSERS)) {
86
103
  });
87
104
 
88
105
  for (let version of VERSIONS[browser]) {
89
- describe(`Version: ${version || "latest"}`, () => {
106
+ describe(`Version: ${version}`, () => {
90
107
  let driver = null;
91
- afterEach(async() => {
108
+
109
+ async function quitDriver() {
92
110
  if (!driver)
93
111
  return;
94
112
 
@@ -99,16 +117,21 @@ for (let browser of Object.keys(BROWSERS)) {
99
117
  await killDriverProcess("chromedriver");
100
118
  else if (browser == "firefox")
101
119
  await killDriverProcess("geckodriver");
102
- });
120
+ }
121
+
122
+ afterEach(quitDriver);
103
123
 
104
124
  it("downloads", async function() {
105
- if (browser == "edge" && process.platform != "linux")
125
+ if (browser == "edge" && process.platform == "win32")
106
126
  this.skip();
107
127
 
108
128
  let {binary, versionNumber} =
109
129
  await BROWSERS[browser].downloadBinary(version);
110
- let browserDir = browser == "opera" ? /(opera|Opera)/ : browser;
111
- expect(binary).toEqual(expect.stringMatching(browserDir));
130
+ let browserName =
131
+ browser == "opera" ? /(opera|Opera)/ :
132
+ browser == "edge" ? /(edge|Edge)/ :
133
+ browser;
134
+ expect(binary).toEqual(expect.stringMatching(browserName));
112
135
 
113
136
  let installedVersion =
114
137
  await BROWSERS[browser].getInstalledVersion(binary);
@@ -132,10 +155,21 @@ for (let browser of Object.keys(BROWSERS)) {
132
155
  });
133
156
 
134
157
  it("supports extra args", async() => {
158
+ let headless = false;
135
159
  let extraArgs = browser == "firefox" ?
136
160
  ["--devtools"] : ["auto-open-devtools-for-tabs"];
137
- driver = await BROWSERS[browser].getDriver(version, {extraArgs});
161
+
162
+ driver = await BROWSERS[browser].getDriver(
163
+ version, {headless, extraArgs});
164
+ await driver.navigate().to(TEST_URL);
165
+ let sizeDevToolsOpen = await getWindowSize(driver);
166
+ await quitDriver();
167
+
168
+ driver = await BROWSERS[browser].getDriver(version, {headless});
138
169
  await driver.navigate().to(TEST_URL);
170
+ let sizeDevToolsClosed = await getWindowSize(driver);
171
+
172
+ expect(sizeDevToolsOpen).toMeasureLessThan(sizeDevToolsClosed);
139
173
  });
140
174
 
141
175
  it("loads an extension", async() => {
@@ -147,7 +181,7 @@ for (let browser of Object.keys(BROWSERS)) {
147
181
  });
148
182
 
149
183
  it("loads an extension in incognito mode", async function() {
150
- if (browser == "firefox" && version == "68.0")
184
+ if (browser == "firefox" && version == "60.0")
151
185
  this.skip();
152
186
 
153
187
  driver = await BROWSERS[browser].getDriver(
@@ -159,5 +193,15 @@ for (let browser of Object.keys(BROWSERS)) {
159
193
  });
160
194
  });
161
195
  }
196
+
197
+ it("does not download unsupported versions", async function() {
198
+ if (browser == "edge" && process.platform == "win32")
199
+ this.skip();
200
+
201
+ const UNSUPPORTED = "0.0";
202
+
203
+ await expect(BROWSERS[browser].downloadBinary(UNSUPPORTED))
204
+ .rejects.toThrow(`Unsupported browser version: ${UNSUPPORTED}`);
205
+ });
162
206
  });
163
207
  }
@@ -1,15 +1,4 @@
1
- FROM node:16-bullseye-slim
2
-
3
- # General packages
4
- RUN apt-get update && apt-get install -y git procps wget unzip bzip2 gnupg
5
- # xvfb (headful browser run)
6
- RUN apt-get install -y libgtk-3-0 libxt6 xvfb libnss3 libxss1
7
- # General browser dependencies
8
- RUN apt-get install -y libgconf-2-4 libasound2 libgbm1
9
- # Edge dependencies
10
- RUN apt-get install -y fonts-liberation libatomic1 xdg-utils
11
- # Opera dependencies
12
- RUN apt-get install -y libcurl4 libgdk-pixbuf2.0-0
1
+ FROM registry.gitlab.com/eyeo/docker/get-browser-binary:node16
13
2
 
14
3
  COPY package*.json get-browser-binary/
15
4
  RUN cd get-browser-binary && npm install