@eyeo/get-browser-binary 0.18.0 → 0.20.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/.mocharc.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "timeout": 80000
2
+ "timeout": 90000
3
3
  }
package/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,19 @@
1
- # Unreleased
1
+ # Unreleased
2
+
3
+ # 0.20.0
4
+
5
+ - Enable developer mode for incognito tests (!126)
6
+ - Adds support to beta and dev channels for Edge in macOS (!127)
7
+ - Enable developer mode for latest versions of chromium browsers (!128)
8
+
9
+ # 0.19.0
10
+
11
+ - Fixes an issue starting with Firefox version 135 which prevented the binary to be downloaded (#82)
12
+ - `getInstalledVersion` now returns only the version number (#81)
13
+
14
+ ## Testing
15
+
16
+ - Test extensions internal pages can now load normally. The development version of Chromium 133 got the `driver.getAllWindowHandles()` issue fixed, therefore the workaround is no longer needed. (!121)
2
17
 
3
18
  # 0.18.0
4
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Install browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
package/src/browser.js CHANGED
@@ -48,7 +48,8 @@ export class Browser {
48
48
  }
49
49
 
50
50
  /**
51
- * Gets the installed version returned by the browser binary.
51
+ * Gets the installed version returned by the browser binary. Subclasses add
52
+ * specific parsing to this function.
52
53
  * @param {string} binary The path to the browser binary.
53
54
  * @return {string} Installed browser version.
54
55
  * @throws {Error} Browser is not installed.
@@ -124,7 +125,6 @@ export class Browser {
124
125
  * enables the extension when loaded in incognito.
125
126
  * @param {webdriver} driver The driver controlling the browser.
126
127
  * @param {string} extensionTitle Title of the extension to be enabled.
127
- * @return {webdriver}
128
128
  * @throws {Error} Unsupported browser version, Extension not found, HTML
129
129
  * element not found.
130
130
  */
package/src/chromium.js CHANGED
@@ -19,7 +19,7 @@ import path from "path";
19
19
  import fs from "fs";
20
20
 
21
21
  import got from "got";
22
- import webdriver from "selenium-webdriver";
22
+ import {Builder} from "selenium-webdriver";
23
23
  import chrome from "selenium-webdriver/chrome.js";
24
24
  import extractZip from "extract-zip";
25
25
 
@@ -27,6 +27,20 @@ import {Browser} from "./browser.js";
27
27
  import {download, getMajorVersion, checkVersion, checkPlatform, errMsg,
28
28
  snapshotsBaseDir, platformArch, checkExtensionPaths} from "./utils.js";
29
29
 
30
+ async function enableDeveloperMode(driver) {
31
+ await driver.switchTo().newWindow("window");
32
+ await driver.navigate().to("chrome://extensions");
33
+ await driver.executeScript(() => {
34
+ const devModeToggle = document
35
+ .querySelector("extensions-manager").shadowRoot
36
+ .getElementById("toolbar").shadowRoot
37
+ .querySelector("#toolbar #devMode");
38
+
39
+ if (!devModeToggle.checked)
40
+ devModeToggle.click();
41
+ });
42
+ }
43
+
30
44
  /**
31
45
  * Browser and webdriver functionality for Chromium.
32
46
  * @hideconstructor
@@ -110,11 +124,18 @@ export class Chromium extends Browser {
110
124
  return parseInt(chromiumBase, 10);
111
125
  }
112
126
 
113
- static async #getInstalledBrowserInfo(binary) {
114
- let installedVersion = await Chromium.getInstalledVersion(binary);
127
+ /** @see Browser.getInstalledVersion */
128
+ static async getInstalledVersion(binary) {
129
+ const installedVersion = await super.getInstalledVersion(binary);
130
+
115
131
  // Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
116
132
  // Windows example: "114.0.5735.0"
117
- let versionNumber = installedVersion.split(" ")[1] || installedVersion;
133
+ return installedVersion.split(" ")[1] || installedVersion;
134
+ }
135
+
136
+ static async #getInstalledBrowserInfo(binary) {
137
+ const versionNumber = await Chromium.getInstalledVersion(binary);
138
+
118
139
  return {binary, versionNumber};
119
140
  }
120
141
 
@@ -204,8 +225,8 @@ export class Chromium extends Browser {
204
225
  await checkExtensionPaths(extensionPaths);
205
226
  options.addArguments(`load-extension=${extensionPaths.join(",")}`);
206
227
  }
228
+ let majorVersion = getMajorVersion(versionNumber);
207
229
  if (headless) {
208
- let majorVersion = getMajorVersion(versionNumber);
209
230
  // https://www.selenium.dev/blog/2023/headless-is-going-away/
210
231
  if (majorVersion >= 109)
211
232
  options.addArguments("headless=new");
@@ -220,11 +241,18 @@ export class Chromium extends Browser {
220
241
  options.addArguments("incognito");
221
242
  options.setChromeBinaryPath(binary);
222
243
 
223
- let builder = new webdriver.Builder();
244
+ let builder = new Builder();
224
245
  builder.forBrowser("chrome");
225
246
  builder.setChromeOptions(options);
226
247
 
227
- return builder.build();
248
+ const driver = builder.build();
249
+
250
+ // From Chromium 134 on, developer mode needs to be enabled
251
+ // for custom extensions to work properly
252
+ if (majorVersion >= 134)
253
+ await enableDeveloperMode(driver);
254
+
255
+ return driver;
228
256
  }
229
257
 
230
258
  /** @see Browser.enableExtensionInIncognito */
package/src/edge.js CHANGED
@@ -21,7 +21,7 @@ import {promisify} from "util";
21
21
  import fs from "fs";
22
22
 
23
23
  import got from "got";
24
- import webdriver from "selenium-webdriver";
24
+ import {until, By, Builder} from "selenium-webdriver";
25
25
  import edge from "selenium-webdriver/edge.js";
26
26
 
27
27
  import {Browser} from "./browser.js";
@@ -29,9 +29,16 @@ import {download, killDriverProcess, wait, getMajorVersion, checkVersion,
29
29
  checkPlatform, errMsg, snapshotsBaseDir, checkExtensionPaths}
30
30
  from "./utils.js";
31
31
 
32
- let {By} = webdriver;
33
32
  let {platform} = process;
34
33
 
34
+ async function enableDeveloperMode(driver) {
35
+ await driver.navigate().to("edge://extensions/");
36
+ const devModeToggle = driver.findElement(By.id("developer-mode"));
37
+
38
+ if (!devModeToggle.checked)
39
+ devModeToggle.click();
40
+ }
41
+
35
42
  /**
36
43
  * Browser and webdriver functionality for Edge.
37
44
  * @hideconstructor
@@ -83,19 +90,24 @@ export class Edge extends Browser {
83
90
  return {versionNumber, channel};
84
91
  }
85
92
 
86
- static #darwinApp = "Microsoft Edge";
93
+ static #getDarwinApp(channel = "stable") {
94
+ const firstUppercase =
95
+ String(channel).charAt(0).toUpperCase() + String(channel).slice(1);
96
+ return channel === "stable" ? "Microsoft Edge" : `Microsoft Edge ${firstUppercase}`;
97
+ }
87
98
 
88
99
  static #getBinaryPath(channel = "stable") {
89
100
  switch (platform) {
90
101
  case "win32":
91
- let programFiles = process.env["ProgramFiles(x86)"] ?
102
+ const programFiles = process.env["ProgramFiles(x86)"] ?
92
103
  "${Env:ProgramFiles(x86)}" : "${Env:ProgramFiles}";
93
104
  return `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
94
105
  case "linux":
95
106
  return channel == "stable" ?
96
107
  "/usr/bin/microsoft-edge" : `/usr/bin/microsoft-edge-${channel}`;
97
108
  case "darwin":
98
- return `${process.env.HOME}/Applications/${Edge.#darwinApp}.app/Contents/MacOS/${Edge.#darwinApp}`;
109
+ const darwinApp = Edge.#getDarwinApp(channel);
110
+ return `${process.env.HOME}/Applications/${darwinApp}.app/Contents/MacOS/${darwinApp}`;
99
111
  default:
100
112
  checkPlatform();
101
113
  }
@@ -119,9 +131,8 @@ export class Edge extends Browser {
119
131
  // https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
120
132
  throw new Error(`${errMsg.unsupportedPlatform}: ${platform}`);
121
133
 
122
- if (platform == "darwin" && version != "latest")
123
- // Only latest Edge is supported on macOS
124
- throw new Error(`${errMsg.unsupportedVersion}: ${version}. Only "latest" is supported`);
134
+ if (platform == "darwin" && !/latest|beta|dev/.test(version))
135
+ throw new Error(`${errMsg.unsupportedVersion}: ${version}. Only "latest", "beta" or "dev" are supported`);
125
136
 
126
137
  checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
127
138
  let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
@@ -135,7 +146,7 @@ export class Edge extends Browser {
135
146
  let archive = path.join(snapshotsDir, "cache", filename);
136
147
  let binary = Edge.#getBinaryPath(channel);
137
148
  try {
138
- if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
149
+ if (await Edge.getInstalledVersion(binary) == versionNumber)
139
150
  return {binary, versionNumber};
140
151
  }
141
152
  catch (e) {}
@@ -143,16 +154,14 @@ export class Edge extends Browser {
143
154
  let url = `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`;
144
155
  try {
145
156
  if (platform == "darwin") {
146
- let caskUrl = "https://formulae.brew.sh/api/cask/microsoft-edge.json";
147
- let caskJson;
157
+ const caskFile = channel === "stable" ? "microsoft-edge.json" : `microsoft-edge@${channel}.json`;
158
+ const caskUrl = `https://formulae.brew.sh/api/cask/${caskFile}`;
148
159
  try {
149
- caskJson = await got(caskUrl).json();
160
+ ({url} = await got(caskUrl).json());
150
161
  }
151
162
  catch (err) {
152
163
  throw new Error(`${errMsg.browserVersionCheck}: ${caskUrl}\n${err}`);
153
164
  }
154
- ({url} = process.arch == "arm64" ?
155
- caskJson.variations.arm64_ventura : caskJson);
156
165
  }
157
166
  await download(url, archive, downloadTimeout);
158
167
  }
@@ -164,15 +173,16 @@ export class Edge extends Browser {
164
173
  await promisify(exec)(`dpkg -i ${archive}`);
165
174
  }
166
175
  else if (platform == "darwin") {
167
- await fs.promises.rm(`${process.env.HOME}/Applications/${Edge.#darwinApp}.app`, {force: true, recursive: true});
176
+ await fs.promises.rm(`${process.env.HOME}/Applications/${Edge.#getDarwinApp(channel)}.app`, {force: true, recursive: true});
168
177
  await promisify(exec)(`installer -pkg ${archive} -target CurrentUserHomeDirectory`);
169
178
  }
170
179
 
171
180
  return {binary, versionNumber};
172
181
  }
173
182
 
174
- static async #getInstalledVersionNumber(binary) {
175
- let installedVersion = await Edge.getInstalledVersion(binary);
183
+ /** @see Browser.getInstalledVersion */
184
+ static async getInstalledVersion(binary) {
185
+ let installedVersion = await super.getInstalledVersion(binary);
176
186
  for (let word of ["beta", "dev", "Beta", "Dev"])
177
187
  installedVersion = installedVersion.replace(word, "");
178
188
  return installedVersion.trim().replace(/.*\s/, "");
@@ -194,7 +204,7 @@ export class Edge extends Browser {
194
204
  else {
195
205
  binary = customBrowserBinary || Edge.#getBinaryPath();
196
206
  versionNumber =
197
- await Edge.#getInstalledVersionNumber(binary);
207
+ await Edge.getInstalledVersion(binary);
198
208
  }
199
209
 
200
210
  let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
@@ -212,10 +222,10 @@ export class Edge extends Browser {
212
222
  options.addArguments("inprivate");
213
223
  if (insecure)
214
224
  options.addArguments("ignore-certificate-errors");
215
- if (platform == "linux")
225
+ if (platform === "linux" || platform === "darwin")
216
226
  options.setEdgeChromiumBinaryPath(binary);
217
227
 
218
- let builder = new webdriver.Builder();
228
+ let builder = new Builder();
219
229
  builder.forBrowser("MicrosoftEdge");
220
230
  builder.setEdgeOptions(options);
221
231
 
@@ -234,12 +244,17 @@ export class Edge extends Browser {
234
244
  }
235
245
  }, 30000, `${errMsg.driverStart}: msedgedriver`, 1000);
236
246
 
247
+ // Developer mode needs to be enabled
248
+ // for custom extensions to work properly
249
+ await enableDeveloperMode(driver);
250
+
237
251
  return driver;
238
252
  }
239
253
 
240
254
  /** @see Browser.enableExtensionInIncognito */
241
255
  static async enableExtensionInIncognito(driver, extensionTitle) {
242
256
  await driver.navigate().to("edge://extensions/");
257
+
243
258
  for (let elem of await driver.findElements(By.css("[role=listitem]"))) {
244
259
  let text = await elem.getAttribute("innerHTML");
245
260
  if (!text.includes(extensionTitle))
@@ -251,8 +266,23 @@ export class Edge extends Browser {
251
266
  continue;
252
267
 
253
268
  await button.click();
254
- return await driver.findElement(By.id("itemAllowIncognito")).click();
269
+ await driver.findElement(By.id("itemAllowIncognito")).click();
270
+
271
+ // On Edge 134, extension gets turned off when incognito mode is enabled
272
+ // We need to turn it back on by clicking the toggle
273
+ try {
274
+ await driver.wait(until.elementLocated(
275
+ By.css('[aria-label="Extension on"]:not(:checked)')), 3000).click();
276
+ }
277
+ catch (err) {
278
+ // ignoring timeout errors, as the element is not expected
279
+ // to be present for all Edge versions
280
+ if (err.name !== "TimeoutError")
281
+ throw err;
282
+ }
283
+ return;
255
284
  }
285
+
256
286
  throw new Error(`${errMsg.elemNotFound}: Details button`);
257
287
  }
258
288
  throw new Error(`${errMsg.extensionNotFound}: ${extensionTitle}`);
package/src/firefox.js CHANGED
@@ -21,7 +21,7 @@ import {promisify} from "util";
21
21
  import fs from "fs";
22
22
 
23
23
  import got from "got";
24
- import webdriver from "selenium-webdriver";
24
+ import {until, By, Builder} from "selenium-webdriver";
25
25
  import firefox from "selenium-webdriver/firefox.js";
26
26
  import {Command} from "selenium-webdriver/lib/command.js";
27
27
 
@@ -30,8 +30,6 @@ import {download, extractTar, extractDmg, killDriverProcess, wait,
30
30
  getMajorVersion, checkVersion, checkPlatform, errMsg, snapshotsBaseDir,
31
31
  platformArch, checkExtensionPaths} from "./utils.js";
32
32
 
33
- let {until, By} = webdriver;
34
-
35
33
  /**
36
34
  * Browser and webdriver functionality for Firefox.
37
35
  * @hideconstructor
@@ -106,11 +104,12 @@ export class Firefox extends Browser {
106
104
  }
107
105
  }
108
106
 
109
- static async #getInstalledBrowserInfo(binary) {
110
- let installedVersion = await Firefox.getInstalledVersion(binary);
107
+ /** @see Browser.getInstalledVersion */
108
+ static async getInstalledVersion(binary) {
109
+ const installedVersion = await super.getInstalledVersion(binary);
110
+
111
111
  // Linux example: "Mozilla Firefox 102.15.0esr"
112
- let versionNumber = installedVersion.split(" ")[2] || installedVersion;
113
- return {binary, versionNumber};
112
+ return installedVersion.split(" ")[2] || installedVersion;
114
113
  }
115
114
 
116
115
  /**
@@ -130,10 +129,15 @@ export class Firefox extends Browser {
130
129
  checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
131
130
  let versionNumber = await Firefox.#getVersionForChannel(version);
132
131
 
132
+ const majorVersion = getMajorVersion(versionNumber);
133
+ // Starting from Firefox 135 on Linux, the binary file extension
134
+ // was changed from .bz2 to .xz
135
+ const linuxExtension = majorVersion <= 134 ? "tar.bz2" : "tar.xz";
136
+
133
137
  let [buildPlatform, fileName] = {
134
138
  "win32-ia32": ["win32", `Firefox Setup ${versionNumber}.exe`],
135
139
  "win32-x64": ["win64", `Firefox Setup ${versionNumber}.exe`],
136
- "linux-x64": ["linux-x86_64", `firefox-${versionNumber}.tar.bz2`],
140
+ "linux-x64": ["linux-x86_64", `firefox-${versionNumber}.${linuxExtension}`],
137
141
  "darwin-x64": ["mac", `Firefox ${versionNumber}.dmg`],
138
142
  "darwin-arm64": ["mac", `Firefox ${versionNumber}.dmg`]
139
143
  }[platformArch];
@@ -215,7 +219,7 @@ export class Firefox extends Browser {
215
219
  // https://github.com/mozilla/geckodriver/issues/1560
216
220
  await wait(async() => {
217
221
  try {
218
- driver = await new webdriver.Builder()
222
+ driver = await new Builder()
219
223
  .forBrowser("firefox")
220
224
  .setFirefoxOptions(options)
221
225
  .build();
package/src/utils.js CHANGED
@@ -91,7 +91,15 @@ export async function download(url, destFile, timeout = 0) {
91
91
 
92
92
  export async function extractTar(archive, dir) {
93
93
  await fs.promises.mkdir(dir);
94
- await promisify(exec)(`tar -jxf ${archive} -C ${dir}`);
94
+
95
+ let command;
96
+
97
+ if (archive.endsWith(".xz"))
98
+ command = `tar -xf ${archive} -C ${dir}`;
99
+ else
100
+ command = `tar -jxf ${archive} -C ${dir}`;
101
+
102
+ await promisify(exec)(command);
95
103
  }
96
104
 
97
105
  export async function extractDmg(archive, dir) {
package/test/browsers.js CHANGED
@@ -111,7 +111,7 @@ async function getInstallFileCTime(browser) {
111
111
  process.platform == "darwin"))
112
112
  return null;
113
113
 
114
- const installTypes = [".zip", ".dmg", ".bz2", ".deb", ".exe"];
114
+ const installTypes = [".zip", ".dmg", ".bz2", ".xz", ".deb", ".exe"];
115
115
 
116
116
  let cacheDir = path.join(snapshotsBaseDir, browser, "cache");
117
117
  let cacheFiles = await fs.promises.readdir(cacheDir);
@@ -1,16 +1,3 @@
1
1
  "use strict";
2
2
 
3
- // There's an issue with Chrome +133 and selenium webdriver where
4
- // getAllWindowHandles() can't see tabs opened by an extension if the url option
5
- // opens an extension page. Example: chrome.tabs.create({url: "index.html"});
6
- //
7
- // Workaround: Create the tab with empty options, which allows
8
- // getAllWindowHandles() to see it, and afterwards update the tab with the
9
- // intended url.
10
-
11
- // This timeout must be higher than driver.geAllWindowHandles() polling interval
12
- const timeout = 1000;
13
-
14
- chrome.tabs.create({}, ({tabId}) => {
15
- setTimeout(() => chrome.tabs.update(tabId, {url: "index.html"}), timeout);
16
- });
3
+ chrome.tabs.create({url: "index.html"});