@eyeo/get-browser-binary 0.17.0 → 0.18.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,5 +1,18 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.18.0
4
+
5
+ - Adds support for Firefox dev channel (!116)
6
+ - Drops support for old Edge versions (#79)
7
+ - Fixes `enableExtensionInIncognito()` to keep working with Chromium +131 (!117)
8
+
9
+ ## Testing
10
+
11
+ - Changes the way test extensions load internal pages. With Chromium +133,
12
+ `driver.getAllWindowHandles()` can't see tabs opened by an extension if the url
13
+ option opens an extension page. Please check the example provided by !119 in
14
+ order to workaround that issue.
15
+
3
16
  # 0.17.0
4
17
 
5
18
  - Stops calling `driver.close()` in `enableExtensionInIncognito()`
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.18.0",
4
4
  "description": "Install browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
package/src/chromium.js CHANGED
@@ -229,47 +229,59 @@ export class Chromium extends Browser {
229
229
 
230
230
  /** @see Browser.enableExtensionInIncognito */
231
231
  static async enableExtensionInIncognito(driver, extensionTitle) {
232
- let handle = await driver.getWindowHandle();
232
+ const currentHandle = await driver.getWindowHandle();
233
233
 
234
- let version = getMajorVersion(
235
- (await driver.getCapabilities()).getBrowserVersion());
236
- if (version >= 115)
234
+ const browserVersion =
235
+ getMajorVersion((await driver.getCapabilities()).getBrowserVersion());
236
+ if (browserVersion >= 115)
237
237
  // On Chromium 115 opening chrome://extensions on the default tab causes
238
238
  // WebDriverError: disconnected. Switching to a new window as a workaround
239
239
  await driver.switchTo().newWindow("window");
240
240
 
241
241
  await driver.navigate().to("chrome://extensions");
242
- await driver.executeScript((...args) => {
243
- let enable = () => document.querySelector("extensions-manager").shadowRoot
242
+
243
+ await driver.executeScript((title, errorMsg) => {
244
+ const enableIncognitoMode = () => document
245
+ .querySelector("extensions-manager").shadowRoot
244
246
  .querySelector("extensions-detail-view").shadowRoot
245
247
  .getElementById("allow-incognito").shadowRoot
246
248
  .getElementById("crToggle").click();
247
249
 
248
- let extensions = document.querySelector("extensions-manager").shadowRoot
249
- .getElementById("items-list").shadowRoot
250
- .querySelectorAll("extensions-item");
250
+ const getExtensionDetailsButton = () => {
251
+ const extensions = document
252
+ .querySelector("extensions-manager").shadowRoot
253
+ .getElementById("items-list").shadowRoot
254
+ .querySelectorAll("extensions-item");
251
255
 
252
- return new Promise((resolve, reject) => {
253
- let extensionDetails;
256
+ let detailsButton;
254
257
  for (let {shadowRoot} of extensions) {
255
- if (shadowRoot.getElementById("name").innerHTML != args[0])
258
+ const extensionName = shadowRoot.getElementById("name").innerHTML;
259
+ if (!extensionName.includes(title))
256
260
  continue;
257
261
 
258
- extensionDetails = shadowRoot.getElementById("detailsButton");
262
+ detailsButton = shadowRoot.getElementById("detailsButton");
259
263
  break;
260
264
  }
261
- if (!extensionDetails)
262
- reject(`${args[1]}: ${args[0]}`);
263
265
 
264
- extensionDetails.click();
265
- setTimeout(() => resolve(enable()), 100);
266
+ return detailsButton;
267
+ };
268
+
269
+ return new Promise((resolve, reject) => {
270
+ const detailsButton = getExtensionDetailsButton();
271
+
272
+ if (!detailsButton)
273
+ reject(`${errorMsg}: ${title}`);
274
+
275
+ detailsButton.click();
276
+ setTimeout(() => resolve(enableIncognitoMode()), 100);
266
277
  });
267
278
  }, extensionTitle, errMsg.extensionNotFound);
268
- if (version >= 115 && process.platform == "win32")
279
+
280
+ if (browserVersion >= 115 && process.platform == "win32")
269
281
  // Closing the previously opened new window. Needed on Windows to avoid a
270
282
  // further `WebDriverError: disconnected` error
271
283
  await driver.close();
272
284
 
273
- await driver.switchTo().window(handle);
285
+ await driver.switchTo().window(currentHandle);
274
286
  }
275
287
  }
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 = {
@@ -179,53 +178,13 @@ export class Edge extends Browser {
179
178
  return installedVersion.trim().replace(/.*\s/, "");
180
179
  }
181
180
 
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
181
  /** @see Browser.getDriver */
225
182
  static async getDriver(version = "latest", {
226
183
  headless = true, extensionPaths = [], incognito = false, insecure = false,
227
184
  extraArgs = [], customBrowserBinary
228
185
  } = {}, downloadTimeout = 0) {
186
+ checkVersion(version, Edge.#MIN_VERSION, Edge.#CHANNELS);
187
+
229
188
  let binary;
230
189
  let versionNumber;
231
190
  if (!customBrowserBinary && (platform == "linux" || platform == "darwin")) {
@@ -260,15 +219,6 @@ export class Edge extends Browser {
260
219
  builder.forBrowser("MicrosoftEdge");
261
220
  builder.setEdgeOptions(options);
262
221
 
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
222
  let driver;
273
223
  // On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
274
224
  // 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) {
@@ -113,12 +141,16 @@ export class Firefox extends Browser {
113
141
  let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
114
142
  let browserDir = path.join(snapshotsDir,
115
143
  `firefox-${platformArch}-${versionNumber}`);
116
- let binary = Firefox.#getBinaryPath(browserDir);
144
+
145
+ let binary;
117
146
  try {
118
147
  await fs.promises.access(browserDir);
148
+ binary = Firefox.#getBinaryPath(browserDir);
119
149
  return {binary, versionNumber};
120
150
  }
121
- catch (e) {}
151
+ catch (e) {
152
+ // The binary was not already downloaded
153
+ }
122
154
 
123
155
  let archive = path.join(snapshotsDir, "cache", fileName);
124
156
  await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
@@ -126,7 +158,9 @@ export class Firefox extends Browser {
126
158
  await fs.promises.access(archive);
127
159
  }
128
160
  catch (e) {
129
- let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
161
+ const baseUrl = "https://archive.mozilla.org/pub";
162
+ const channel = version === "dev" ? "devedition" : "firefox";
163
+ let url = `${baseUrl}/${channel}/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
130
164
  try {
131
165
  await download(url, archive, downloadTimeout);
132
166
  }
@@ -135,6 +169,7 @@ export class Firefox extends Browser {
135
169
  }
136
170
  }
137
171
  await Firefox.#extractFirefoxArchive(archive, browserDir);
172
+ binary = Firefox.#getBinaryPath(browserDir);
138
173
 
139
174
  return {binary, versionNumber};
140
175
  }
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";
@@ -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
  }
@@ -1,3 +1,16 @@
1
1
  "use strict";
2
2
 
3
- chrome.tabs.create({url: "index.html"});
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
+ });