@eyeo/get-browser-binary 0.11.0 → 0.13.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/README.md CHANGED
@@ -35,7 +35,8 @@ the right side.
35
35
  - Edge >= 95
36
36
 
37
37
  Note: Installing Edge is not supported on Windows. It is assumed to be installed
38
- because it is the default browser on that platform.
38
+ because it is the default browser on that platform. On macOS, only the latest
39
+ Edge version is supported.
39
40
 
40
41
  ## Development
41
42
 
@@ -82,6 +83,14 @@ Increasing the timeout may be useful on slow connection environments:
82
83
  npm test -- --timeout <ms>
83
84
  ```
84
85
 
86
+ By default, tests delete the `./browser-snapshots` before each `Browser` suite
87
+ runs. To change that behavior you may set the `TEST_KEEP_SNAPSHOTS` environment
88
+ variable to `true`. Example:
89
+
90
+ ```shell
91
+ TEST_KEEP_SNAPSHOTS=true npm test
92
+ ```
93
+
85
94
  ### Running tests on Docker
86
95
 
87
96
  Useful to reproduce the CI environment of the `test:browsers:linux` job.
@@ -100,14 +109,6 @@ parameter:
100
109
  docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest --timeout 100000" -it browsers
101
110
  ```
102
111
 
103
- By default, tests delete the `./browser-snapshots` before each `Browser` suite
104
- runs. To change that behavior you may set the `TEST_KEEP_SNAPSHOTS` environment
105
- variable to `true`. Example:
106
-
107
- ```shell
108
- TEST_KEEP_SNAPSHOTS=true npm test
109
- ```
110
-
111
112
  #### ARM architecture (M1/M2 Apple Silicon)
112
113
 
113
114
  Chromium (ARM native):
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,34 @@
1
+ # 0.13.0
2
+
3
+ - Fixes an issue that prevented the webdriver from locating the Edge binary on
4
+ Linux (#60)
5
+ - Uses headless new mode on Edge >= 116 (!74)
6
+ - Replaces omahaproxy API (deprecated) with chromiumdash (!73)
7
+ - Throws a `browser not installed` error when `customBinaryPath` doesn't exist
8
+ (!72)
9
+
10
+ ### Testing
11
+
12
+ - Changes the test extension in incognito mode from spanning to split (#59)
13
+ - Runs Linux Edge tests in a different browser version order (!71)
14
+
15
+ # 0.12.0
16
+
17
+ - Adds a new field `customBrowserBinary` to `getDriver()` options parameter that
18
+ accepts the path of a custom (pre-installed) browser binary. When used, that is
19
+ the browser that will run (#56)
20
+ - Fixes Edge install URLs for mac. Unfortunately, the new URLs provided by Edge
21
+ offer a limited number of old versions only. For that reason `latest`
22
+ becomes the only accepted value for Edge on mac (!64). Please see #56 as a
23
+ possible workaround
24
+ - Fixes an issue with Chromium 115 that prevented loading extensions in
25
+ incognito mode (#52)
26
+ - Logs URL info on previously uncaught http errors (!67)
27
+
28
+ ### Testing
29
+
30
+ - Fixes recent failures when running Edge tests (#48)
31
+
1
32
  # 0.11.0
2
33
 
3
34
  - Fixes an issue on arm64 edgedriver links that prevented the driver to run on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Install browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
package/src/browsers.js CHANGED
@@ -47,6 +47,7 @@ const DRIVER_START_ERROR = "Unable to start driver";
47
47
  const EXTENSION_NOT_FOUND_ERROR = "Extension not found";
48
48
  const BROWSER_DOWNLOAD_ERROR = "Browser download failed";
49
49
  const BROWSER_NOT_INSTALLED_ERROR = "Browser is not installed";
50
+ const BROWSER_VERSION_CHECK_ERROR = "Checking the browser version failed";
50
51
  const ELEMENT_NOT_FOUND_ERROR = "HTML element not found";
51
52
 
52
53
  function getMajorVersion(versionNumber) {
@@ -101,19 +102,29 @@ class Browser {
101
102
  * Gets the installed version returned by the browser binary.
102
103
  * @param {string} binary The path to the browser binary.
103
104
  * @return {string} Installed browser version.
105
+ * @throws {Error} Browser is not installed.
104
106
  */
105
107
  static async getInstalledVersion(binary) {
106
- let stdout;
107
- if (platform == "win32") {
108
- ({stdout} = await promisify(exec)(
109
- `(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
110
- {shell: "powershell.exe"})
111
- );
108
+ try {
109
+ let stdout;
110
+ let stderr;
111
+ if (platform == "win32") {
112
+ ({stdout, stderr} = await promisify(exec)(
113
+ `(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
114
+ {shell: "powershell.exe"})
115
+ );
116
+ }
117
+ else {
118
+ ({stdout, stderr} = await promisify(execFile)(binary, ["--version"]));
119
+ }
120
+ if (stderr)
121
+ throw new Error(stderr);
122
+
123
+ return stdout.trim();
112
124
  }
113
- else {
114
- ({stdout} = await promisify(execFile)(binary, ["--version"]));
125
+ catch (err) {
126
+ throw new Error(`${BROWSER_NOT_INSTALLED_ERROR}.\nBinary path: ${binary}\n${err}`);
115
127
  }
116
- return stdout.trim();
117
128
  }
118
129
 
119
130
  /**
@@ -135,6 +146,9 @@ class Browser {
135
146
  * certificates, or not.
136
147
  * @property {Array.<string>} [extraArgs=[]] Additional arguments to start
137
148
  * the browser with.
149
+ * @property {string} [customBrowserBinary] Path to the browser binary to be
150
+ * used, instead of the browser installed by installBrowser(). This option
151
+ * overrides the version parameter in getDriver().
138
152
  */
139
153
 
140
154
  /**
@@ -177,6 +191,7 @@ class Browser {
177
191
  */
178
192
  class Chromium extends Browser {
179
193
  static #CHANNELS = ["latest", "beta", "dev"];
194
+ static #MAX_VERSION_DECREMENTS = 200;
180
195
 
181
196
  static async #getVersionForChannel(channel) {
182
197
  if (!Chromium.#CHANNELS.includes(channel))
@@ -192,14 +207,16 @@ class Chromium extends Browser {
192
207
  "darwin-x64": "mac",
193
208
  "darwin-arm64": "mac_arm64"
194
209
  }[platformArch];
195
- let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
196
- let release = data[0].versions.find(ver => ver.channel == channel);
197
- let {current_version: version} = release;
210
+ let url = `https://versionhistory.googleapis.com/v1/chrome/platforms/${os}/channels/${channel}/versions/all/releases`;
198
211
 
199
- if (release.true_branch && release.true_branch.includes("_"))
200
- // A wrong base may be caused by a mini-branch (patched) release
201
- // In that case, the base is taken from the unpatched version
202
- version = [...version.split(".").slice(0, 3), "0"].join(".");
212
+ let data;
213
+ try {
214
+ data = await got(url).json();
215
+ }
216
+ catch (err) {
217
+ throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
218
+ }
219
+ let {version} = data.releases[0];
203
220
 
204
221
  return version;
205
222
  }
@@ -214,6 +231,35 @@ class Chromium extends Browser {
214
231
  }[platform];
215
232
  }
216
233
 
234
+ static async #getBase(chromiumVersion) {
235
+ let url;
236
+ let chromiumBase;
237
+ try {
238
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/55
239
+ if (getMajorVersion(chromiumVersion) < 91) {
240
+ url = `https://omahaproxy.appspot.com/deps.json?version=${chromiumVersion}`;
241
+ ({chromium_base_position: chromiumBase} = await got(url).json());
242
+ }
243
+ else {
244
+ url = `https://chromiumdash.appspot.com/fetch_version?version=${chromiumVersion}`;
245
+ ({chromium_main_branch_position: chromiumBase} = await got(url).json());
246
+ }
247
+ }
248
+ catch (err) {
249
+ throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
250
+ }
251
+ return parseInt(chromiumBase, 10);
252
+ }
253
+
254
+ static async #getInstalledBrowserInfo(binary) {
255
+ let installedVersion = await Chromium.getInstalledVersion(binary);
256
+ // Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
257
+ // Windows example: "114.0.5735.0"
258
+ let versionNumber = installedVersion.split(" ")[1] || installedVersion;
259
+ let base = await Chromium.#getBase(versionNumber);
260
+ return {binary, versionNumber, base};
261
+ }
262
+
217
263
  /**
218
264
  * Installs the browser. The Chromium executable gets extracted in the
219
265
  * {@link snapshotsBaseDir} folder, ready to go.
@@ -227,32 +273,19 @@ class Chromium extends Browser {
227
273
  */
228
274
  static async installBrowser(version = "latest", downloadTimeout = 0) {
229
275
  const MIN_VERSION = 75;
230
- const MAX_VERSION_DECREMENTS = 200;
231
276
 
232
277
  let binary;
233
278
  let versionNumber;
234
279
  let base;
235
280
 
236
- async function getBase(chromiumVersion) {
237
- let {chromium_base_position: chromiumBase} =
238
- await got(`https://omahaproxy.appspot.com/deps.json?version=${chromiumVersion}`).json();
239
- return parseInt(chromiumBase, 10);
240
- }
241
-
242
281
  // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
243
- if (platformArch == "linux-arm64") {
244
- binary = "/usr/bin/chromium";
245
- let installedVersion = await Chromium.getInstalledVersion(binary);
246
- // installedVersion example: "Chromium 112.0.5615.49 built on Debian 11.6"
247
- versionNumber = installedVersion.split(" ")[1];
248
- base = await getBase(versionNumber);
249
- return {binary, versionNumber, base};
250
- }
282
+ if (platformArch == "linux-arm64")
283
+ return await Chromium.#getInstalledBrowserInfo("/usr/bin/chromium");
251
284
 
252
285
  checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
253
286
  versionNumber = await Chromium.#getVersionForChannel(version);
254
287
 
255
- base = await getBase(versionNumber);
288
+ base = await Chromium.#getBase(versionNumber);
256
289
  let startBase = base;
257
290
  let [platformDir, fileName] = {
258
291
  "win32-ia32": ["Win", "chrome-win.zip"],
@@ -293,7 +326,7 @@ class Chromium extends Browser {
293
326
  // Chromium advises decrementing the branch_base_position when no
294
327
  // matching build was found. See https://www.chromium.org/getting-involved/download-chromium
295
328
  base--;
296
- if (base <= startBase - MAX_VERSION_DECREMENTS)
329
+ if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
297
330
  throw new Error(`${BROWSER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
298
331
  }
299
332
  else {
@@ -306,7 +339,7 @@ class Chromium extends Browser {
306
339
  return {binary, versionNumber, base};
307
340
  }
308
341
 
309
- static async #installDriver(base, versionNumber) {
342
+ static async #installDriver(startBase) {
310
343
  let [dir, zip, driverBinary] = {
311
344
  "win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
312
345
  "win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
@@ -317,27 +350,46 @@ class Chromium extends Browser {
317
350
  }[platformArch];
318
351
 
319
352
  let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
320
- versionNumber);
321
- let archive = path.join(cacheDir, `${base}-${zip}`);
322
-
353
+ "chromedriver");
354
+ let archive = path.join(cacheDir, `${startBase}-${zip}`);
323
355
  try {
324
356
  await fs.promises.access(archive);
325
357
  await extractZip(archive, {dir: cacheDir});
326
358
  }
327
359
  catch (e) { // zip file is either not cached or corrupted
328
- let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
329
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
330
- // It is unclear how electron releases match Chromium versions. Once that
331
- // is figured out, the link below will depend on the Chromium version.
332
- // https://stackoverflow.com/questions/38732822/compile-chromedriver-on-arm
333
- if (platformArch == "linux-arm64")
334
- url = "https://github.com/electron/electron/releases/download/v24.1.2/chromedriver-v24.1.2-linux-arm64.zip";
335
-
336
- try {
337
- await download(url, archive);
360
+ if (platformArch == "linux-arm64") {
361
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
362
+ // It's unclear how electron releases match Chromium versions. Once that
363
+ // is figured out, the link below will depend on the Chromium version.
364
+ // https://stackoverflow.com/questions/38732822/compile-chromedriver-on-arm
365
+ let url = "https://github.com/electron/electron/releases/download/v24.1.2/chromedriver-v24.1.2-linux-arm64.zip";
366
+ try {
367
+ await download(url, archive);
368
+ }
369
+ catch (err) {
370
+ throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
371
+ }
338
372
  }
339
- catch (err) {
340
- throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
373
+ else {
374
+ let base = startBase;
375
+ while (true) {
376
+ let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
377
+ try {
378
+ await download(url, archive);
379
+ break;
380
+ }
381
+ catch (err) {
382
+ if (err.name == "HTTPError") {
383
+ base--;
384
+ archive = path.join(cacheDir, `${base}-${zip}`);
385
+ if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
386
+ throw new Error(`${DRIVER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
387
+ }
388
+ else {
389
+ throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
390
+ }
391
+ }
392
+ }
341
393
  }
342
394
  await extractZip(archive, {dir: cacheDir});
343
395
  }
@@ -355,11 +407,12 @@ class Chromium extends Browser {
355
407
  /** @see Browser.getDriver */
356
408
  static async getDriver(version = "latest", {
357
409
  headless = true, extensionPaths = [], incognito = false, insecure = false,
358
- extraArgs = []
410
+ extraArgs = [], customBrowserBinary
359
411
  } = {}, downloadTimeout = 0) {
360
- let {binary, versionNumber, base} =
412
+ let {binary, versionNumber, base} = customBrowserBinary ?
413
+ await Chromium.#getInstalledBrowserInfo(customBrowserBinary) :
361
414
  await Chromium.installBrowser(version, downloadTimeout);
362
- let driverPath = await Chromium.#installDriver(base, versionNumber);
415
+ let driverPath = await Chromium.#installDriver(base);
363
416
  let serviceBuilder = new chrome.ServiceBuilder(driverPath);
364
417
  let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
365
418
  if (extensionPaths.length > 0)
@@ -388,6 +441,15 @@ class Chromium extends Browser {
388
441
 
389
442
  /** @see Browser.enableExtensionInIncognito */
390
443
  static async enableExtensionInIncognito(driver, extensionTitle) {
444
+ let handle = await driver.getWindowHandle();
445
+
446
+ let version = getMajorVersion(
447
+ (await driver.getCapabilities()).getBrowserVersion());
448
+ if (version >= 115)
449
+ // On Chromium 115 opening chrome://extensions on the default tab causes
450
+ // WebDriverError: disconnected. Switching to a new window as a workaround
451
+ await driver.switchTo().newWindow("window");
452
+
391
453
  await driver.navigate().to("chrome://extensions");
392
454
  await driver.executeScript((...args) => {
393
455
  let enable = () => document.querySelector("extensions-manager").shadowRoot
@@ -415,6 +477,11 @@ class Chromium extends Browser {
415
477
  setTimeout(() => resolve(enable()), 100);
416
478
  });
417
479
  }, extensionTitle, EXTENSION_NOT_FOUND_ERROR);
480
+ if (version >= 115)
481
+ // Closing the previously opened new window
482
+ await driver.close();
483
+
484
+ await driver.switchTo().window(handle);
418
485
  }
419
486
  }
420
487
 
@@ -430,7 +497,14 @@ class Firefox extends Browser {
430
497
  if (!Firefox.#CHANNELS.includes(channel))
431
498
  return channel;
432
499
 
433
- let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
500
+ let url = "https://product-details.mozilla.org/1.0/firefox_versions.json";
501
+ let data;
502
+ try {
503
+ data = await got(url).json();
504
+ }
505
+ catch (err) {
506
+ throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
507
+ }
434
508
  return channel == "beta" ?
435
509
  data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
436
510
  }
@@ -514,9 +588,11 @@ class Firefox extends Browser {
514
588
  /** @see Browser.getDriver */
515
589
  static async getDriver(version = "latest", {
516
590
  headless = true, extensionPaths = [], incognito = false, insecure = false,
517
- extraArgs = []
591
+ extraArgs = [], customBrowserBinary
518
592
  } = {}, downloadTimeout = 0) {
519
- let {binary} = await Firefox.installBrowser(version, downloadTimeout);
593
+ let binary;
594
+ if (!customBrowserBinary)
595
+ ({binary} = await Firefox.installBrowser(version, downloadTimeout));
520
596
 
521
597
  let options = new firefox.Options();
522
598
  if (headless)
@@ -527,7 +603,7 @@ class Firefox extends Browser {
527
603
  options.set("acceptInsecureCerts", true);
528
604
  if (extraArgs.length > 0)
529
605
  options.addArguments(...extraArgs);
530
- options.setBinary(binary);
606
+ options.setBinary(customBrowserBinary || binary);
531
607
 
532
608
  let driver;
533
609
  // The OS may be low on resources, that's why building the driver is retried
@@ -588,7 +664,14 @@ class Edge extends Browser {
588
664
  if (Edge.#CHANNELS.includes(version) && version != "latest")
589
665
  channel = version;
590
666
 
591
- let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`);
667
+ let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`;
668
+ let body;
669
+ try {
670
+ ({body} = await got(url));
671
+ }
672
+ catch (err) {
673
+ throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${url}\n${err}`);
674
+ }
592
675
  let versionNumber;
593
676
  if (Edge.#CHANNELS.includes(version)) {
594
677
  let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
@@ -618,11 +701,7 @@ class Edge extends Browser {
618
701
  return {versionNumber, channel};
619
702
  }
620
703
 
621
- static #getDarwinAppName(channel) {
622
- let extra = channel == "stable" ?
623
- "" : " " + channel.charAt(0).toUpperCase() + channel.slice(1);
624
- return `Microsoft Edge${extra}`;
625
- }
704
+ static #darwinApp = "Microsoft Edge";
626
705
 
627
706
  static #getBinaryPath(channel = "stable") {
628
707
  switch (platform) {
@@ -634,8 +713,7 @@ class Edge extends Browser {
634
713
  return channel == "stable" ?
635
714
  "microsoft-edge" : `microsoft-edge-${channel}`;
636
715
  case "darwin":
637
- let appName = Edge.#getDarwinAppName(channel);
638
- return `${process.env.HOME}/Applications/${appName}.app/Contents/MacOS/${appName}`;
716
+ return `${process.env.HOME}/Applications/${Edge.#darwinApp}.app/Contents/MacOS/${Edge.#darwinApp}`;
639
717
  default:
640
718
  checkPlatform();
641
719
  }
@@ -659,25 +737,17 @@ class Edge extends Browser {
659
737
  // https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
660
738
  throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
661
739
 
740
+ if (platform == "darwin" && version != "latest")
741
+ // Only latest Edge is supported on macOS
742
+ throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}. Only "latest" is supported`);
743
+
662
744
  const MIN_VERSION = 95;
663
745
  checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
664
746
  let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
665
747
 
666
- let darwinName = {
667
- stable: "MicrosoftEdge",
668
- beta: "MicrosoftEdgeBeta",
669
- dev: "MicrosoftEdgeDev"
670
- }[channel];
671
748
  let filename = {
672
749
  linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
673
- darwin: `${darwinName}-${versionNumber}.pkg`
674
- }[platform];
675
- let darwinArch = process.arch == "arm64" ?
676
- "03adf619-38c6-4249-95ff-4a01c0ffc962" :
677
- "C1297A47-86C4-4C1F-97FA-950631F94777";
678
- let url = {
679
- linux: `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
680
- darwin: `https://officecdnmac.microsoft.com/pr/${darwinArch}/MacAutoupdate/${filename}`
750
+ darwin: `MicrosoftEdge-${versionNumber}.pkg`
681
751
  }[platform];
682
752
 
683
753
  let snapshotsDir = path.join(snapshotsBaseDir, "edge");
@@ -689,7 +759,20 @@ class Edge extends Browser {
689
759
  }
690
760
  catch (e) {}
691
761
 
762
+ let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`;
692
763
  try {
764
+ if (platform == "darwin") {
765
+ let caskUrl = "https://formulae.brew.sh/api/cask/microsoft-edge.json";
766
+ let caskJson;
767
+ try {
768
+ caskJson = await got(caskUrl).json();
769
+ }
770
+ catch (err) {
771
+ throw new Error(`${BROWSER_VERSION_CHECK_ERROR}: ${caskUrl}\n${err}`);
772
+ }
773
+ ({url} = process.arch == "arm64" ?
774
+ caskJson.variations.arm64_ventura : caskJson);
775
+ }
693
776
  await download(url, archive, downloadTimeout);
694
777
  }
695
778
  catch (err) {
@@ -700,8 +783,7 @@ class Edge extends Browser {
700
783
  await promisify(exec)(`dpkg -i ${archive}`);
701
784
  }
702
785
  else if (platform == "darwin") {
703
- let appName = Edge.#getDarwinAppName(channel);
704
- await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {force: true, recursive: true});
786
+ await fs.promises.rm(`${process.env.HOME}/Applications/${Edge.#darwinApp}.app`, {force: true, recursive: true});
705
787
  await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
706
788
  }
707
789
 
@@ -715,18 +797,15 @@ class Edge extends Browser {
715
797
  return installedVersion.trim().replace(/.*\s/, "");
716
798
  }
717
799
 
718
- static async #installDriver() {
800
+ static async #installDriver(binary) {
719
801
  async function extractEdgeZip(archive, cacheDir, driverPath) {
720
802
  await killDriverProcess("msedgedriver");
721
803
  await fs.promises.rm(driverPath, {force: true});
722
804
  await extractZip(archive, {dir: cacheDir});
723
805
  }
724
806
 
725
- let binary = Edge.#getBinaryPath();
726
- let versionNumber = await Edge.#getInstalledVersionNumber(binary);
727
- if (!versionNumber)
728
- throw new Error(`${BROWSER_NOT_INSTALLED_ERROR}: Edge`);
729
-
807
+ let binaryPath = binary || Edge.#getBinaryPath();
808
+ let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
730
809
  let [zip, driverBinary] = {
731
810
  "win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
732
811
  "win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
@@ -768,23 +847,38 @@ class Edge extends Browser {
768
847
  /** @see Browser.getDriver */
769
848
  static async getDriver(version = "latest", {
770
849
  headless = true, extensionPaths = [], incognito = false, insecure = false,
771
- extraArgs = []
850
+ extraArgs = [], customBrowserBinary
772
851
  } = {}, downloadTimeout = 0) {
773
- if (platform == "linux" || platform == "darwin")
774
- await Edge.installBrowser(version, downloadTimeout);
852
+ let binary;
853
+ let versionNumber;
854
+ if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
855
+ ({binary, versionNumber} =
856
+ await Edge.installBrowser(version, downloadTimeout));
857
+ }
858
+ else {
859
+ binary = customBrowserBinary || Edge.#getBinaryPath();
860
+ versionNumber =
861
+ await Edge.#getInstalledVersionNumber(binary);
862
+ }
775
863
 
776
- let driverPath = await Edge.#installDriver();
864
+ let driverPath = await Edge.#installDriver(binary);
777
865
  let serviceBuilder = new edge.ServiceBuilder(driverPath);
778
866
 
779
867
  let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
780
- if (headless)
781
- options.headless();
868
+ if (headless) {
869
+ if (versionNumber && getMajorVersion(versionNumber) >= 114)
870
+ options.addArguments("headless=new");
871
+ else
872
+ options.headless();
873
+ }
782
874
  if (extensionPaths.length > 0)
783
875
  options.addArguments(`load-extension=${extensionPaths.join(",")}`);
784
876
  if (incognito)
785
- options.addArguments("incognito");
877
+ options.addArguments("inprivate");
786
878
  if (insecure)
787
879
  options.addArguments("ignore-certificate-errors");
880
+ if (platform == "linux")
881
+ options.setEdgeChromiumBinaryPath(`/usr/bin/${binary}`);
788
882
 
789
883
  let builder = new webdriver.Builder();
790
884
  builder.forBrowser("MicrosoftEdge");
package/test/browsers.js CHANGED
@@ -28,7 +28,8 @@ import "geckodriver"; // Required to set the driver path on Windows
28
28
  const VERSIONS = {
29
29
  chromium: ["latest", "75.0.3770.0", "beta", "dev"],
30
30
  firefox: ["latest", "60.0", "beta"],
31
- edge: ["latest", "95.0.1020.40", "beta", "dev"]
31
+ // Unusual ordering: https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/57
32
+ edge: ["dev", "beta", "latest", "95.0.1020.40"]
32
33
  };
33
34
  const TEST_URL = "https://abptestpages.org/en/exceptions/iframe_subdomains";
34
35
  const TEST_URL_LONG_PAGE = "https://abptestpages.org/";
@@ -121,9 +122,10 @@ async function getCachedTimes(browser) {
121
122
  }
122
123
 
123
124
  let driverCacheTime = null;
124
- // Firefox install file includes the driver, that's why it's not tested here
125
+ // geckodriver is installed by npm, that's why Firefox is not tested here
125
126
  if (browser != "firefox" && !armLinuxChromium) {
126
- let driverDir = cacheFiles.find(elem => !elem.endsWith(".zip"));
127
+ let driverDir = cacheFiles.find(
128
+ elem => !elem.endsWith(".zip") && !elem.endsWith(".pkg"));
127
129
  let driverFiles = await fs.promises.readdir(path.join(cacheDir, driverDir));
128
130
  let driverZip = driverFiles.find(elem => elem.endsWith(".zip"));
129
131
  let driverStat =
@@ -147,6 +149,7 @@ for (let browser of Object.keys(BROWSERS)) {
147
149
  describe(`Version: ${version}`, () => {
148
150
  let driver = null;
149
151
  let emptyCacheTimes = null;
152
+ let customBrowserBinary = null;
150
153
 
151
154
  async function quitDriver() {
152
155
  if (!driver)
@@ -168,7 +171,7 @@ for (let browser of Object.keys(BROWSERS)) {
168
171
  this.skip();
169
172
 
170
173
  let {binary, versionNumber} =
171
- await BROWSERS[browser].installBrowser(version, 60000);
174
+ await BROWSERS[browser].installBrowser(version, this.timeout());
172
175
  let browserName = browser == "edge" ? /(edge|Edge)/ : browser;
173
176
  expect(binary).toEqual(expect.stringMatching(browserName));
174
177
 
@@ -179,6 +182,7 @@ for (let browser of Object.keys(BROWSERS)) {
179
182
 
180
183
  // Adding the version number to the test title for logging purposes
181
184
  this.test.title = `${this.test.title} [v${versionNumber}]`;
185
+ customBrowserBinary = binary;
182
186
  });
183
187
 
184
188
  it("runs", async() => {
@@ -212,13 +216,26 @@ for (let browser of Object.keys(BROWSERS)) {
212
216
  expect(existingCacheTimes).toEqual(emptyCacheTimes);
213
217
  });
214
218
 
215
- it("supports extra args", async function() {
216
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/48
217
- // To be removed when Edge latest version > 112
218
- if (browser == "edge" && version == "latest" &&
219
- process.platform == "linux")
219
+ // This test depends on running the "installs" test
220
+ it("runs a custom browser binary", async function() {
221
+ if (!customBrowserBinary)
220
222
  this.skip();
221
223
 
224
+ let names = {
225
+ chromium: "chrome",
226
+ firefox: "firefox",
227
+ edge: /(MicrosoftEdge|msedge)/
228
+ };
229
+
230
+ driver =
231
+ await BROWSERS[browser].getDriver(version, {customBrowserBinary});
232
+ await driver.navigate().to(TEST_URL);
233
+
234
+ expect((await driver.getCapabilities()).getBrowserName())
235
+ .toEqual(expect.stringMatching(names[browser]));
236
+ });
237
+
238
+ it("supports extra args", async() => {
222
239
  let headless = false;
223
240
  let extraArgs = browser == "firefox" ?
224
241
  ["--devtools"] : ["auto-open-devtools-for-tabs"];
@@ -284,5 +301,11 @@ for (let browser of Object.keys(BROWSERS)) {
284
301
  .rejects.toThrow(`Unsupported browser version: ${unsupported}`);
285
302
  }
286
303
  });
304
+
305
+ it("does not run not installed custom browsers", async() => {
306
+ let customBrowserBinary = "not-installed";
307
+ await expect(BROWSERS[browser].getDriver("latest", {customBrowserBinary}))
308
+ .rejects.toThrow(/(Browser is not installed|binary is not a Firefox executable)/);
309
+ });
287
310
  });
288
311
  }
@@ -4,5 +4,6 @@
4
4
  "manifest_version": 3,
5
5
  "background": {
6
6
  "service_worker": "background.js"
7
- }
7
+ },
8
+ "incognito": "split"
8
9
  }