@eyeo/get-browser-binary 0.17.0 → 0.19.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 CHANGED
@@ -52,8 +52,10 @@ test:browsers:linux:dev:
52
52
  - choco install -y microsoft-edge
53
53
  - npm install
54
54
  tags:
55
- - saas-windows-medium-amd64
55
+ - eyeo-windows
56
56
  cache: {}
57
+ # Retrying to mitigate Edge install issues
58
+ retry: 1
57
59
 
58
60
  test:browsers:windows:
59
61
  extends: .windows
package/README.md CHANGED
@@ -32,7 +32,7 @@ the right side.
32
32
 
33
33
  - Chromium >= 77 (Chromium ARM >= 92)
34
34
  - Firefox >= 68
35
- - Edge >= 95 (Windows Edge >= 79)
35
+ - Edge >= 114
36
36
 
37
37
  Note: Installing Edge is not supported on Windows. It is assumed to be installed
38
38
  because it is the default browser on that platform. On macOS, only the latest
package/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,26 @@
1
- # Unreleased
1
+ # Unreleased
2
+
3
+ # 0.19.0
4
+
5
+ - Fixes an issue starting with Firefox version 135 which prevented the binary to be downloaded (#82)
6
+ - `getInstalledVersion` now returns only the version number (#81)
7
+
8
+ ## Testing
9
+
10
+ - 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)
11
+
12
+ # 0.18.0
13
+
14
+ - Adds support for Firefox dev channel (!116)
15
+ - Drops support for old Edge versions (#79)
16
+ - Fixes `enableExtensionInIncognito()` to keep working with Chromium +131 (!117)
17
+
18
+ ## Testing
19
+
20
+ - Changes the way test extensions load internal pages. With Chromium +133,
21
+ `driver.getAllWindowHandles()` can't see tabs opened by an extension if the url
22
+ option opens an extension page. Please check the example provided by !119 in
23
+ order to workaround that issue.
2
24
 
3
25
  # 0.17.0
4
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.17.0",
3
+ "version": "0.19.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.
package/src/chromium.js CHANGED
@@ -110,11 +110,18 @@ export class Chromium extends Browser {
110
110
  return parseInt(chromiumBase, 10);
111
111
  }
112
112
 
113
- static async #getInstalledBrowserInfo(binary) {
114
- let installedVersion = await Chromium.getInstalledVersion(binary);
113
+ /** @see Browser.getInstalledVersion */
114
+ static async getInstalledVersion(binary) {
115
+ const installedVersion = await super.getInstalledVersion(binary);
116
+
115
117
  // Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
116
118
  // Windows example: "114.0.5735.0"
117
- let versionNumber = installedVersion.split(" ")[1] || installedVersion;
119
+ return installedVersion.split(" ")[1] || installedVersion;
120
+ }
121
+
122
+ static async #getInstalledBrowserInfo(binary) {
123
+ const versionNumber = await Chromium.getInstalledVersion(binary);
124
+
118
125
  return {binary, versionNumber};
119
126
  }
120
127
 
@@ -229,47 +236,59 @@ export class Chromium extends Browser {
229
236
 
230
237
  /** @see Browser.enableExtensionInIncognito */
231
238
  static async enableExtensionInIncognito(driver, extensionTitle) {
232
- let handle = await driver.getWindowHandle();
239
+ const currentHandle = await driver.getWindowHandle();
233
240
 
234
- let version = getMajorVersion(
235
- (await driver.getCapabilities()).getBrowserVersion());
236
- if (version >= 115)
241
+ const browserVersion =
242
+ getMajorVersion((await driver.getCapabilities()).getBrowserVersion());
243
+ if (browserVersion >= 115)
237
244
  // On Chromium 115 opening chrome://extensions on the default tab causes
238
245
  // WebDriverError: disconnected. Switching to a new window as a workaround
239
246
  await driver.switchTo().newWindow("window");
240
247
 
241
248
  await driver.navigate().to("chrome://extensions");
242
- await driver.executeScript((...args) => {
243
- let enable = () => document.querySelector("extensions-manager").shadowRoot
249
+
250
+ await driver.executeScript((title, errorMsg) => {
251
+ const enableIncognitoMode = () => document
252
+ .querySelector("extensions-manager").shadowRoot
244
253
  .querySelector("extensions-detail-view").shadowRoot
245
254
  .getElementById("allow-incognito").shadowRoot
246
255
  .getElementById("crToggle").click();
247
256
 
248
- let extensions = document.querySelector("extensions-manager").shadowRoot
249
- .getElementById("items-list").shadowRoot
250
- .querySelectorAll("extensions-item");
257
+ const getExtensionDetailsButton = () => {
258
+ const extensions = document
259
+ .querySelector("extensions-manager").shadowRoot
260
+ .getElementById("items-list").shadowRoot
261
+ .querySelectorAll("extensions-item");
251
262
 
252
- return new Promise((resolve, reject) => {
253
- let extensionDetails;
263
+ let detailsButton;
254
264
  for (let {shadowRoot} of extensions) {
255
- if (shadowRoot.getElementById("name").innerHTML != args[0])
265
+ const extensionName = shadowRoot.getElementById("name").innerHTML;
266
+ if (!extensionName.includes(title))
256
267
  continue;
257
268
 
258
- extensionDetails = shadowRoot.getElementById("detailsButton");
269
+ detailsButton = shadowRoot.getElementById("detailsButton");
259
270
  break;
260
271
  }
261
- if (!extensionDetails)
262
- reject(`${args[1]}: ${args[0]}`);
263
272
 
264
- extensionDetails.click();
265
- setTimeout(() => resolve(enable()), 100);
273
+ return detailsButton;
274
+ };
275
+
276
+ return new Promise((resolve, reject) => {
277
+ const detailsButton = getExtensionDetailsButton();
278
+
279
+ if (!detailsButton)
280
+ reject(`${errorMsg}: ${title}`);
281
+
282
+ detailsButton.click();
283
+ setTimeout(() => resolve(enableIncognitoMode()), 100);
266
284
  });
267
285
  }, extensionTitle, errMsg.extensionNotFound);
268
- if (version >= 115 && process.platform == "win32")
286
+
287
+ if (browserVersion >= 115 && process.platform == "win32")
269
288
  // Closing the previously opened new window. Needed on Windows to avoid a
270
289
  // further `WebDriverError: disconnected` error
271
290
  await driver.close();
272
291
 
273
- await driver.switchTo().window(handle);
292
+ await driver.switchTo().window(currentHandle);
274
293
  }
275
294
  }
package/src/edge.js CHANGED
@@ -23,7 +23,6 @@ import fs from "fs";
23
23
  import got from "got";
24
24
  import webdriver from "selenium-webdriver";
25
25
  import edge from "selenium-webdriver/edge.js";
26
- import extractZip from "extract-zip";
27
26
 
28
27
  import {Browser} from "./browser.js";
29
28
  import {download, killDriverProcess, wait, getMajorVersion, checkVersion,
@@ -40,6 +39,7 @@ let {platform} = process;
40
39
  */
41
40
  export class Edge extends Browser {
42
41
  static #CHANNELS = ["latest", "beta", "dev"];
42
+ static #MIN_VERSION = 114;
43
43
 
44
44
  static async #getVersionForChannel(version) {
45
45
  let channel = "stable";
@@ -106,7 +106,7 @@ export class Edge extends Browser {
106
106
  * which requires root permissions. On MacOS, Edge is installed as a user
107
107
  * app (not as a system app). Installing Edge on Windows is not supported.
108
108
  * @param {string} [version=latest] Either "latest", "beta", "dev" or a full
109
- * version number (i.e. "95.0.1020.40").
109
+ * version number (i.e. "114.0.1823.82").
110
110
  * @param {number} [downloadTimeout=0] Allowed time in ms for the download of
111
111
  * install files to complete. When set to 0 there is no time limit.
112
112
  * @return {BrowserBinary}
@@ -123,8 +123,7 @@ export class Edge extends Browser {
123
123
  // Only latest Edge is supported on macOS
124
124
  throw new Error(`${errMsg.unsupportedVersion}: ${version}. Only "latest" is supported`);
125
125
 
126
- const MIN_VERSION = 95;
127
- checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
126
+ checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
128
127
  let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
129
128
 
130
129
  let filename = {
@@ -136,7 +135,7 @@ export class Edge extends Browser {
136
135
  let archive = path.join(snapshotsDir, "cache", filename);
137
136
  let binary = Edge.#getBinaryPath(channel);
138
137
  try {
139
- if (await Edge.#getInstalledVersionNumber(binary) == versionNumber)
138
+ if (await Edge.getInstalledVersion(binary) == versionNumber)
140
139
  return {binary, versionNumber};
141
140
  }
142
141
  catch (e) {}
@@ -172,60 +171,21 @@ export class Edge extends Browser {
172
171
  return {binary, versionNumber};
173
172
  }
174
173
 
175
- static async #getInstalledVersionNumber(binary) {
176
- let installedVersion = await Edge.getInstalledVersion(binary);
174
+ /** @see Browser.getInstalledVersion */
175
+ static async getInstalledVersion(binary) {
176
+ let installedVersion = await super.getInstalledVersion(binary);
177
177
  for (let word of ["beta", "dev", "Beta", "Dev"])
178
178
  installedVersion = installedVersion.replace(word, "");
179
179
  return installedVersion.trim().replace(/.*\s/, "");
180
180
  }
181
181
 
182
- static async #installOldWindowsDriver(binary) {
183
- async function extractEdgeZip(archive, cacheDir, driverPath) {
184
- await killDriverProcess("msedgedriver");
185
- await fs.promises.rm(driverPath, {force: true});
186
- await extractZip(archive, {dir: cacheDir});
187
- }
188
-
189
- let binaryPath = binary || Edge.#getBinaryPath();
190
- let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
191
- let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
192
- `edgedriver-${versionNumber}`);
193
- let zip = process.arch == "ia32" ?
194
- "edgedriver_win32.zip" : "edgedriver_win64.zip";
195
- let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
196
- let driverPath = path.join(cacheDir, "msedgedriver.exe");
197
-
198
- try {
199
- await fs.promises.access(archive);
200
- await extractEdgeZip(archive, cacheDir, driverPath);
201
- }
202
- catch (e) { // zip file is either not cached or corrupted
203
- let vSplit = versionNumber.split(".");
204
- let lastNum = parseInt(vSplit[3], 10);
205
- while (lastNum >= 0) {
206
- try {
207
- let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
208
- await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
209
- archive);
210
- break;
211
- }
212
- catch (e2) {
213
- lastNum--;
214
- }
215
- }
216
- if (lastNum < 0)
217
- throw new Error(`${errMsg.driverDownload}: Edge ${versionNumber}`);
218
-
219
- await extractEdgeZip(archive, cacheDir, driverPath);
220
- }
221
- return driverPath;
222
- }
223
-
224
182
  /** @see Browser.getDriver */
225
183
  static async getDriver(version = "latest", {
226
184
  headless = true, extensionPaths = [], incognito = false, insecure = false,
227
185
  extraArgs = [], customBrowserBinary
228
186
  } = {}, downloadTimeout = 0) {
187
+ checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
188
+
229
189
  let binary;
230
190
  let versionNumber;
231
191
  if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
@@ -235,7 +195,7 @@ export class Edge extends Browser {
235
195
  else {
236
196
  binary = customBrowserBinary || Edge.#getBinaryPath();
237
197
  versionNumber =
238
- await Edge.#getInstalledVersionNumber(binary);
198
+ await Edge.getInstalledVersion(binary);
239
199
  }
240
200
 
241
201
  let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
@@ -260,15 +220,6 @@ export class Edge extends Browser {
260
220
  builder.forBrowser("MicrosoftEdge");
261
221
  builder.setEdgeOptions(options);
262
222
 
263
- if (getMajorVersion(versionNumber) < 95) {
264
- // Selenium's automated driver download doesn't work on old Edge versions.
265
- // Support to installing old drivers is only needed by Windows CI jobs.
266
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/2#note_1189235804
267
- let driverPath = await Edge.#installOldWindowsDriver(binary);
268
- let serviceBuilder = new edge.ServiceBuilder(driverPath);
269
- builder.setEdgeService(serviceBuilder);
270
- }
271
-
272
223
  let driver;
273
224
  // On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
274
225
  // due to low OS resources, that's why building the driver is retried
package/src/firefox.js CHANGED
@@ -38,12 +38,13 @@ let {until, By} = webdriver;
38
38
  * @extends Browser
39
39
  */
40
40
  export class Firefox extends Browser {
41
- static #CHANNELS = ["latest", "beta"];
41
+ static #CHANNELS = ["latest", "beta", "dev"];
42
42
 
43
43
  static async #getVersionForChannel(channel) {
44
44
  if (!Firefox.#CHANNELS.includes(channel))
45
45
  return channel;
46
46
 
47
+ // https://wiki.mozilla.org/Release_Management/Product_details#firefox_versions.json
47
48
  let url = "https://product-details.mozilla.org/1.0/firefox_versions.json";
48
49
  let data;
49
50
  try {
@@ -52,17 +53,44 @@ export class Firefox extends Browser {
52
53
  catch (err) {
53
54
  throw new Error(`${errMsg.browserVersionCheck}: ${url}\n${err}`);
54
55
  }
55
- return channel == "beta" ?
56
- data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
56
+
57
+ if (channel === "beta") {
58
+ // The validated latest Firefox Beta built, exposed for downloading
59
+ return data.LATEST_FIREFOX_RELEASED_DEVEL_VERSION;
60
+ }
61
+ else if (channel === "dev") {
62
+ // Firefox Developer Edition
63
+ return data.FIREFOX_DEVEDITION;
64
+ }
65
+
66
+ // The Firefox Version shipped on the release channel
67
+ return data.LATEST_FIREFOX_VERSION;
68
+ }
69
+
70
+ static #getAppDir(dir) {
71
+ const appDirRegexes = {
72
+ win32: /^core$/,
73
+ linux: /^firefox$/,
74
+ darwin: /^Firefox.*\.app$/
75
+ };
76
+
77
+ const contents = fs.readdirSync(dir);
78
+ const appDir = contents.find(candidateDir =>
79
+ appDirRegexes[process.platform].test(candidateDir));
80
+
81
+ return path.join(dir, appDir);
57
82
  }
58
83
 
59
84
  static #getBinaryPath(dir) {
60
85
  checkPlatform();
61
- return {
62
- win32: path.join(dir, "core", "firefox.exe"),
63
- linux: path.join(dir, "firefox", "firefox"),
64
- darwin: path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox")
65
- }[process.platform];
86
+
87
+ const defaultBinaries = {
88
+ win32: path.join(Firefox.#getAppDir(dir), "firefox.exe"),
89
+ linux: path.join(Firefox.#getAppDir(dir), "firefox"),
90
+ darwin: path.join(Firefox.#getAppDir(dir), "Contents", "MacOS", "firefox")
91
+ };
92
+
93
+ return defaultBinaries[process.platform];
66
94
  }
67
95
 
68
96
  static #extractFirefoxArchive(archive, dir) {
@@ -78,11 +106,12 @@ export class Firefox extends Browser {
78
106
  }
79
107
  }
80
108
 
81
- static async #getInstalledBrowserInfo(binary) {
82
- let installedVersion = await Firefox.getInstalledVersion(binary);
109
+ /** @see Browser.getInstalledVersion */
110
+ static async getInstalledVersion(binary) {
111
+ const installedVersion = await super.getInstalledVersion(binary);
112
+
83
113
  // Linux example: "Mozilla Firefox 102.15.0esr"
84
- let versionNumber = installedVersion.split(" ")[2] || installedVersion;
85
- return {binary, versionNumber};
114
+ return installedVersion.split(" ")[2] || installedVersion;
86
115
  }
87
116
 
88
117
  /**
@@ -102,10 +131,15 @@ export class Firefox extends Browser {
102
131
  checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
103
132
  let versionNumber = await Firefox.#getVersionForChannel(version);
104
133
 
134
+ const majorVersion = getMajorVersion(versionNumber);
135
+ // Starting from Firefox 135 on Linux, the binary file extension
136
+ // was changed from .bz2 to .xz
137
+ const linuxExtension = majorVersion <= 134 ? "tar.bz2" : "tar.xz";
138
+
105
139
  let [buildPlatform, fileName] = {
106
140
  "win32-ia32": ["win32", `Firefox Setup ${versionNumber}.exe`],
107
141
  "win32-x64": ["win64", `Firefox Setup ${versionNumber}.exe`],
108
- "linux-x64": ["linux-x86_64", `firefox-${versionNumber}.tar.bz2`],
142
+ "linux-x64": ["linux-x86_64", `firefox-${versionNumber}.${linuxExtension}`],
109
143
  "darwin-x64": ["mac", `Firefox ${versionNumber}.dmg`],
110
144
  "darwin-arm64": ["mac", `Firefox ${versionNumber}.dmg`]
111
145
  }[platformArch];
@@ -113,12 +147,16 @@ export class Firefox extends Browser {
113
147
  let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
114
148
  let browserDir = path.join(snapshotsDir,
115
149
  `firefox-${platformArch}-${versionNumber}`);
116
- let binary = Firefox.#getBinaryPath(browserDir);
150
+
151
+ let binary;
117
152
  try {
118
153
  await fs.promises.access(browserDir);
154
+ binary = Firefox.#getBinaryPath(browserDir);
119
155
  return {binary, versionNumber};
120
156
  }
121
- catch (e) {}
157
+ catch (e) {
158
+ // The binary was not already downloaded
159
+ }
122
160
 
123
161
  let archive = path.join(snapshotsDir, "cache", fileName);
124
162
  await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
@@ -126,7 +164,9 @@ export class Firefox extends Browser {
126
164
  await fs.promises.access(archive);
127
165
  }
128
166
  catch (e) {
129
- let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
167
+ const baseUrl = "https://archive.mozilla.org/pub";
168
+ const channel = version === "dev" ? "devedition" : "firefox";
169
+ let url = `${baseUrl}/${channel}/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
130
170
  try {
131
171
  await download(url, archive, downloadTimeout);
132
172
  }
@@ -135,6 +175,7 @@ export class Firefox extends Browser {
135
175
  }
136
176
  }
137
177
  await Firefox.#extractFirefoxArchive(archive, browserDir);
178
+ binary = Firefox.#getBinaryPath(browserDir);
138
179
 
139
180
  return {binary, versionNumber};
140
181
  }
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
@@ -27,8 +27,8 @@ import {TEST_SERVER_URL} from "./test-server.js";
27
27
 
28
28
  const VERSIONS = {
29
29
  chromium: ["latest", "77.0.3865.0", "beta", "dev"],
30
- firefox: ["latest", "68.0", "beta"],
31
- edge: ["latest", "95.0.1020.40", "beta", "dev"]
30
+ firefox: ["latest", "68.0", "beta", "dev"],
31
+ edge: ["latest", "114.0.1823.82", "beta", "dev"]
32
32
  };
33
33
  const TEST_URL_BASIC = `${TEST_SERVER_URL}/basic.html`;
34
34
  const PROXY_URL_BASIC = "http://testpages.adblockplus.org/basic.html";
@@ -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);
@@ -355,21 +355,24 @@ for (let browser of Object.keys(BROWSERS)) {
355
355
  });
356
356
  }
357
357
 
358
- it("does not install unsupported versions", async function() {
359
- if (browser == "edge" && process.platform == "win32")
360
- this.skip();
361
358
 
362
- for (let unsupported of ["0.0", "invalid"]) {
363
- await expect(BROWSERS[browser].installBrowser(unsupported))
364
- .rejects.toThrow(`Unsupported browser version: ${unsupported}`);
365
- }
366
- });
359
+ describe("Any version", async() => {
360
+ it("does not install unsupported versions", async function() {
361
+ if (browser == "edge" && process.platform == "win32")
362
+ this.skip();
367
363
 
368
- it("does not load an invalid extension", async() => {
369
- let extensionPaths = [process.cwd()];
364
+ for (let unsupported of ["0.0", "invalid"]) {
365
+ await expect(BROWSERS[browser].installBrowser(unsupported))
366
+ .rejects.toThrow(`Unsupported browser version: ${unsupported}`);
367
+ }
368
+ });
370
369
 
371
- await expect(BROWSERS[browser].getDriver("latest", {extensionPaths}))
372
- .rejects.toThrow(`Extension manifest file not found: ${extensionPaths[0]}`);
370
+ it("does not load an invalid extension", async() => {
371
+ let extensionPaths = [process.cwd()];
372
+
373
+ await expect(BROWSERS[browser].getDriver("latest", {extensionPaths}))
374
+ .rejects.toThrow(`Extension manifest file not found: ${extensionPaths[0]}`);
375
+ });
373
376
  });
374
377
  });
375
378
  }