@eyeo/get-browser-binary 0.14.0 → 0.16.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
@@ -29,9 +29,9 @@ test:basic:
29
29
 
30
30
  test:browsers:linux:
31
31
  stage: test
32
- image: docker:20.10.16
32
+ image: docker:24.0.5
33
33
  services:
34
- - docker:20.10.16-dind
34
+ - docker:24.0.5-dind
35
35
  before_script:
36
36
  - docker build -f test/docker/Dockerfile -t browsers .
37
37
  script:
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
35
+ - Edge >= 95 (Windows Edge >= 79)
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
@@ -133,20 +133,16 @@ docker run --shm-size=512m -e TEST_ARGS="--grep chromium.*latest --timeout 10000
133
133
 
134
134
  #### ARM architecture (M1/M2 Apple Silicon)
135
135
 
136
- ```shell
137
- docker build -f test/docker/arm64.Dockerfile -t browsers-arm .
138
- docker run --shm-size=512m -e TEST_ARGS="--grep (chromium|firefox).*latest" -it browsers-arm
139
- ```
136
+ The run is done emulating the AMD architecture. Requirements:
140
137
 
141
- Only latest Chromium and latest Firefox ESR versions are supported. Edge is not
142
- supported.
138
+ - macOS >= 13 (Ventura)
139
+ - Rosetta
140
+ - The feature "Use Rosetta for x86/amd64 emulation on Apple Silicon" enabled in Docker
143
141
 
144
- Regarding Firefox, it may be possible to run other versions using AMD emulation
145
- (unstable results):
142
+ The `--platform` option should be used when running the image:
146
143
 
147
144
  ```shell
148
- docker build -f test/docker/Dockerfile -t browsers-amd .
149
- docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep firefox.*68.0" -it browsers-amd
145
+ docker run --platform linux/amd64 --shm-size=512m -e TEST_ARGS="--grep chromium.*latest" -it browsers
150
146
  ```
151
147
 
152
148
  ## Building the documentation
package/RELEASE_NOTES.md CHANGED
@@ -1,3 +1,37 @@
1
+ # 0.16.0
2
+
3
+ - Starts using selenium's automated driver management (#67)
4
+ - Loads the extension in Firefox by local path instead of being copied to a
5
+ temporary folder (!104)
6
+ - Checks if extensions can be loaded beforehand, by ensuring they contain a
7
+ `manifest.json` file. If not, a new `Extension manifest file not found` error
8
+ will be thrown (#71)
9
+ - Refactors headless runs by not using webdriver's deprecated headless() method
10
+ (#72)
11
+
12
+ ### Testing
13
+
14
+ - Fixes an issue with `npm test` which would fail when the `grep` option would
15
+ include parenthesis (#69)
16
+ - Adds the browser version information in the "runs" test (#74)
17
+
18
+ ### Notes for integrators
19
+
20
+ - Please make sure that your selenium-webdriver package version is at least
21
+ 4.15.0 (the one used in this release) (#67)
22
+ - If you experience chromedriver issues, try deleting the `/browser-snapshots`
23
+ cache folder.
24
+
25
+ # 0.15.0
26
+
27
+ - Fixes the downloads of Chromium versions < 91 by replacing the remaining
28
+ usages of omahaproxy API (now removed) with chromiumdash (#55)
29
+ - Updates to selenium webdriver 4.15.0 (#66)
30
+
31
+ ### Testing
32
+
33
+ - Uses AMD emulation on ARM docker test runs (!93)
34
+
1
35
  # 0.14.0
2
36
 
3
37
  - Increases minimum supported browser versions (#64)
package/index.js CHANGED
@@ -19,7 +19,7 @@ import {Chromium} from "./src/chromium.js";
19
19
  import {Firefox} from "./src/firefox.js";
20
20
  import {Edge} from "./src/edge.js";
21
21
 
22
- export {download, takeFullPageScreenshot, snapshotsBaseDir}
22
+ export {download, takeFullPageScreenshot, snapshotsBaseDir, getMajorVersion}
23
23
  from "./src/utils.js";
24
24
 
25
25
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeo/get-browser-binary",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Install browser binaries and matching webdrivers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "geckodriver": "3.1.0",
34
34
  "got": "^12.5.3",
35
35
  "jimp": "^0.22.4",
36
- "selenium-webdriver": "^4.8.0"
36
+ "selenium-webdriver": "^4.15.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "eslint": "^8.33.0",
package/src/browser.js CHANGED
@@ -84,11 +84,11 @@ export class Browser {
84
84
  /**
85
85
  * @typedef {Object} driverOptions
86
86
  * @property {boolean} [headless=true] Run the browser in headless mode,
87
- * or not. In Chromium >= 111, the
87
+ * or not. In Chromium >= 111 and Edge >= 114 the
88
88
  * {@link https://developer.chrome.com/articles/new-headless/ new headless mode}
89
89
  * is used.
90
- * @property {Array.<string>} [extensionPaths=[]] Loads extensions to the
91
- * browser.
90
+ * @property {Array.<string>} [extensionPaths=[]] Paths to folders containing
91
+ * unpacked extensions which will be loaded to the browser.
92
92
  * @property {boolean} [incognito=false] Runs the browser in incognito mode,
93
93
  * or not.
94
94
  * @property {boolean} [insecure=false] Forces the browser to accept insecure
@@ -110,7 +110,8 @@ export class Browser {
110
110
  * browser install files to complete. When set to 0 there is no time limit.
111
111
  * @return {webdriver}
112
112
  * @throws {Error} Unsupported browser version, Unsupported platform, Browser
113
- * download failed, Driver download failed, Unable to start driver.
113
+ * download failed, Driver download failed, Unable to start driver, Manifest
114
+ * file not found.
114
115
  */
115
116
  static async getDriver(version, options = {}, downloadTimeout = 0) {
116
117
  // to be implemented by the subclass
package/src/chromium.js CHANGED
@@ -24,9 +24,8 @@ import chrome from "selenium-webdriver/chrome.js";
24
24
  import extractZip from "extract-zip";
25
25
 
26
26
  import {Browser} from "./browser.js";
27
- import {download, killDriverProcess, getMajorVersion, checkVersion,
28
- checkPlatform, errMsg, snapshotsBaseDir, platformArch}
29
- from "./utils.js";
27
+ import {download, getMajorVersion, checkVersion, checkPlatform, errMsg,
28
+ snapshotsBaseDir, platformArch, checkExtensionPaths} from "./utils.js";
30
29
 
31
30
  /**
32
31
  * Browser and webdriver functionality for Chromium.
@@ -85,10 +84,20 @@ export class Chromium extends Browser {
85
84
  let url;
86
85
  let chromiumBase;
87
86
  try {
88
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/55
89
- if (getMajorVersion(chromiumVersion) < 91) {
90
- url = `https://omahaproxy.appspot.com/deps.json?version=${chromiumVersion}`;
91
- ({chromium_base_position: chromiumBase} = await got(url).json());
87
+ let majorVersion = getMajorVersion(chromiumVersion);
88
+ if (majorVersion < 91) {
89
+ // Below v91, base branch position only exists once per milestone in
90
+ // the /fetch_milestones endpoint
91
+ url = "https://chromiumdash.appspot.com/fetch_milestones?only_branched=true";
92
+ let data = await got(url).json();
93
+ for (let {milestone, chromium_main_branch_position: base} of data) {
94
+ if (milestone == majorVersion) {
95
+ chromiumBase = base;
96
+ break;
97
+ }
98
+ }
99
+ if (!chromiumBase)
100
+ throw new Error(`${errMsg.browserVersionCheck}: ${url}`);
92
101
  }
93
102
  else {
94
103
  url = `https://chromiumdash.appspot.com/fetch_version?version=${chromiumVersion}`;
@@ -106,8 +115,7 @@ export class Chromium extends Browser {
106
115
  // Linux example: "Chromium 112.0.5615.49 built on Debian 11.6"
107
116
  // Windows example: "114.0.5735.0"
108
117
  let versionNumber = installedVersion.split(" ")[1] || installedVersion;
109
- let base = await Chromium.#getBase(versionNumber);
110
- return {binary, versionNumber, base};
118
+ return {binary, versionNumber};
111
119
  }
112
120
 
113
121
  /**
@@ -124,18 +132,10 @@ export class Chromium extends Browser {
124
132
  static async installBrowser(version = "latest", downloadTimeout = 0) {
125
133
  const MIN_VERSION = process.arch == "arm64" ? 92 : 77;
126
134
 
127
- let binary;
128
- let versionNumber;
129
- let base;
130
-
131
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
132
- if (platformArch == "linux-arm64")
133
- return await Chromium.#getInstalledBrowserInfo("/usr/bin/chromium");
134
-
135
135
  checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
136
- versionNumber = await Chromium.#getVersionForChannel(version);
136
+ let versionNumber = await Chromium.#getVersionForChannel(version);
137
137
 
138
- base = await Chromium.#getBase(versionNumber);
138
+ let base = await Chromium.#getBase(versionNumber);
139
139
  let startBase = base;
140
140
  let [platformDir, fileName] = {
141
141
  "win32-ia32": ["Win", "chrome-win.zip"],
@@ -148,6 +148,7 @@ export class Chromium extends Browser {
148
148
  let browserDir;
149
149
  let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
150
150
 
151
+ let binary;
151
152
  while (true) {
152
153
  browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
153
154
  binary = Chromium.#getBinaryPath(browserDir);
@@ -186,72 +187,7 @@ export class Chromium extends Browser {
186
187
  }
187
188
  await extractZip(archive, {dir: browserDir});
188
189
 
189
- return {binary, versionNumber, base};
190
- }
191
-
192
- static async #installDriver(startBase) {
193
- let [dir, zip, driverBinary] = {
194
- "win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
195
- "win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
196
- "linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
197
- "linux-arm64": ["", "", "chromedriver"],
198
- "darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
199
- "darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
200
- }[platformArch];
201
-
202
- let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
203
- "chromedriver");
204
- let archive = path.join(cacheDir, `${startBase}-${zip}`);
205
- try {
206
- await fs.promises.access(archive);
207
- await extractZip(archive, {dir: cacheDir});
208
- }
209
- catch (e) { // zip file is either not cached or corrupted
210
- if (platformArch == "linux-arm64") {
211
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
212
- // It's unclear how electron releases match Chromium versions. Once that
213
- // is figured out, the link below will depend on the Chromium version.
214
- // https://stackoverflow.com/questions/38732822/compile-chromedriver-on-arm
215
- let url = "https://github.com/electron/electron/releases/download/v26.2.0/chromedriver-v26.2.0-linux-arm64.zip";
216
- try {
217
- await download(url, archive);
218
- }
219
- catch (err) {
220
- throw new Error(`${errMsg.driverDownload}: ${url}\n${err}`);
221
- }
222
- }
223
- else {
224
- let base = startBase;
225
- while (true) {
226
- let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
227
- try {
228
- await download(url, archive);
229
- break;
230
- }
231
- catch (err) {
232
- if (err.name == "HTTPError") {
233
- base--;
234
- archive = path.join(cacheDir, `${base}-${zip}`);
235
- if (base <= startBase - Chromium.#MAX_VERSION_DECREMENTS)
236
- throw new Error(`${errMsg.driverDownload}: Chromium base ${startBase}`);
237
- }
238
- else {
239
- throw new Error(`${errMsg.driverDownload}: ${url}\n${err}`);
240
- }
241
- }
242
- }
243
- }
244
- await extractZip(archive, {dir: cacheDir});
245
- }
246
-
247
- await killDriverProcess("chromedriver");
248
- let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
249
- if (platformArch == "linux-arm64")
250
- driverPath = path.join(cacheDir, driverBinary);
251
- await fs.promises.rm(driverPath, {force: true});
252
- await extractZip(archive, {dir: cacheDir});
253
-
254
- return driverPath;
190
+ return {binary, versionNumber};
255
191
  }
256
192
 
257
193
  /** @see Browser.getDriver */
@@ -259,21 +195,24 @@ export class Chromium extends Browser {
259
195
  headless = true, extensionPaths = [], incognito = false, insecure = false,
260
196
  extraArgs = [], customBrowserBinary
261
197
  } = {}, downloadTimeout = 0) {
262
- let {binary, versionNumber, base} = customBrowserBinary ?
198
+ let {binary, versionNumber} = customBrowserBinary ?
263
199
  await Chromium.#getInstalledBrowserInfo(customBrowserBinary) :
264
200
  await Chromium.installBrowser(version, downloadTimeout);
265
- let driverPath = await Chromium.#installDriver(base);
266
- let serviceBuilder = new chrome.ServiceBuilder(driverPath);
201
+
267
202
  let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
268
- if (extensionPaths.length > 0)
203
+ if (extensionPaths.length > 0) {
204
+ await checkExtensionPaths(extensionPaths);
269
205
  options.addArguments(`load-extension=${extensionPaths.join(",")}`);
206
+ }
270
207
  if (headless) {
271
- // New headless mode introduced in Chrome 111
272
- // https://developer.chrome.com/articles/new-headless/
273
- if (getMajorVersion(versionNumber) >= 111)
208
+ let majorVersion = getMajorVersion(versionNumber);
209
+ // https://www.selenium.dev/blog/2023/headless-is-going-away/
210
+ if (majorVersion >= 109)
274
211
  options.addArguments("headless=new");
212
+ else if (majorVersion >= 96)
213
+ options.addArguments("headless=chrome");
275
214
  else
276
- options.headless();
215
+ options.addArguments("headless");
277
216
  }
278
217
  if (insecure)
279
218
  options.addArguments("ignore-certificate-errors");
@@ -284,7 +223,6 @@ export class Chromium extends Browser {
284
223
  let builder = new webdriver.Builder();
285
224
  builder.forBrowser("chrome");
286
225
  builder.setChromeOptions(options);
287
- builder.setChromeService(serviceBuilder);
288
226
 
289
227
  return builder.build();
290
228
  }
package/src/edge.js CHANGED
@@ -27,7 +27,7 @@ import extractZip from "extract-zip";
27
27
 
28
28
  import {Browser} from "./browser.js";
29
29
  import {download, killDriverProcess, wait, getMajorVersion, checkVersion,
30
- checkPlatform, errMsg, snapshotsBaseDir, platformArch}
30
+ checkPlatform, errMsg, snapshotsBaseDir, checkExtensionPaths}
31
31
  from "./utils.js";
32
32
 
33
33
  let {By} = webdriver;
@@ -93,7 +93,7 @@ export class Edge extends Browser {
93
93
  return `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
94
94
  case "linux":
95
95
  return channel == "stable" ?
96
- "microsoft-edge" : `microsoft-edge-${channel}`;
96
+ "/usr/bin/microsoft-edge" : `/usr/bin/microsoft-edge-${channel}`;
97
97
  case "darwin":
98
98
  return `${process.env.HOME}/Applications/${Edge.#darwinApp}.app/Contents/MacOS/${Edge.#darwinApp}`;
99
99
  default:
@@ -179,7 +179,7 @@ export class Edge extends Browser {
179
179
  return installedVersion.trim().replace(/.*\s/, "");
180
180
  }
181
181
 
182
- static async #installDriver(binary) {
182
+ static async #installOldWindowsDriver(binary) {
183
183
  async function extractEdgeZip(archive, cacheDir, driverPath) {
184
184
  await killDriverProcess("msedgedriver");
185
185
  await fs.promises.rm(driverPath, {force: true});
@@ -188,17 +188,12 @@ export class Edge extends Browser {
188
188
 
189
189
  let binaryPath = binary || Edge.#getBinaryPath();
190
190
  let versionNumber = await Edge.#getInstalledVersionNumber(binaryPath);
191
- let [zip, driverBinary] = {
192
- "win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
193
- "win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
194
- "linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
195
- "darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
196
- "darwin-arm64": ["edgedriver_mac64_m1.zip", "msedgedriver"]
197
- }[platformArch];
198
191
  let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
199
192
  `edgedriver-${versionNumber}`);
193
+ let zip = process.arch == "ia32" ?
194
+ "edgedriver_win32.zip" : "edgedriver_win64.zip";
200
195
  let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
201
- let driverPath = path.join(cacheDir, driverBinary);
196
+ let driverPath = path.join(cacheDir, "msedgedriver.exe");
202
197
 
203
198
  try {
204
199
  await fs.promises.access(archive);
@@ -243,29 +238,36 @@ export class Edge extends Browser {
243
238
  await Edge.#getInstalledVersionNumber(binary);
244
239
  }
245
240
 
246
- let driverPath = await Edge.#installDriver(binary);
247
- let serviceBuilder = new edge.ServiceBuilder(driverPath);
248
-
249
241
  let options = new edge.Options().addArguments("no-sandbox", ...extraArgs);
250
242
  if (headless) {
251
243
  if (versionNumber && getMajorVersion(versionNumber) >= 114)
252
244
  options.addArguments("headless=new");
253
245
  else
254
- options.headless();
246
+ options.addArguments("headless");
255
247
  }
256
- if (extensionPaths.length > 0)
248
+ if (extensionPaths.length > 0) {
249
+ await checkExtensionPaths(extensionPaths);
257
250
  options.addArguments(`load-extension=${extensionPaths.join(",")}`);
251
+ }
258
252
  if (incognito)
259
253
  options.addArguments("inprivate");
260
254
  if (insecure)
261
255
  options.addArguments("ignore-certificate-errors");
262
256
  if (platform == "linux")
263
- options.setEdgeChromiumBinaryPath(`/usr/bin/${binary}`);
257
+ options.setEdgeChromiumBinaryPath(binary);
264
258
 
265
259
  let builder = new webdriver.Builder();
266
260
  builder.forBrowser("MicrosoftEdge");
267
261
  builder.setEdgeOptions(options);
268
- builder.setEdgeService(serviceBuilder);
262
+
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
+ }
269
271
 
270
272
  let driver;
271
273
  // On Windows CI, occasionally a SessionNotCreatedError is thrown, likely
package/src/firefox.js CHANGED
@@ -23,11 +23,12 @@ import fs from "fs";
23
23
  import got from "got";
24
24
  import webdriver from "selenium-webdriver";
25
25
  import firefox from "selenium-webdriver/firefox.js";
26
+ import {Command} from "selenium-webdriver/lib/command.js";
26
27
 
27
28
  import {Browser} from "./browser.js";
28
29
  import {download, extractTar, extractDmg, killDriverProcess, wait,
29
30
  getMajorVersion, checkVersion, checkPlatform, errMsg, snapshotsBaseDir,
30
- platformArch} from "./utils.js";
31
+ platformArch, checkExtensionPaths} from "./utils.js";
31
32
 
32
33
  let {until, By} = webdriver;
33
34
 
@@ -98,10 +99,6 @@ export class Firefox extends Browser {
98
99
  static async installBrowser(version = "latest", downloadTimeout = 0) {
99
100
  const MIN_VERSION = 68;
100
101
 
101
- // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
102
- if (platformArch == "linux-arm64")
103
- return await Firefox.#getInstalledBrowserInfo("/usr/bin/firefox");
104
-
105
102
  checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
106
103
  let versionNumber = await Firefox.#getVersionForChannel(version);
107
104
 
@@ -156,7 +153,7 @@ export class Firefox extends Browser {
156
153
 
157
154
  let options = new firefox.Options();
158
155
  if (headless)
159
- options.headless();
156
+ options.addArguments("--headless");
160
157
  if (incognito)
161
158
  options.addArguments("--private");
162
159
  if (insecure)
@@ -188,8 +185,18 @@ export class Firefox extends Browser {
188
185
  }, 30000, `${errMsg.driverStart}: geckodriver`, 1000);
189
186
 
190
187
  for (let extensionPath of extensionPaths) {
191
- let temporary = true; // Parameter not documented on the webdriver docs
192
- await driver.installAddon(extensionPath, temporary);
188
+ await checkExtensionPaths([extensionPath]);
189
+
190
+ // This is based on Selenium's Firefox `installAddon` function. Rather
191
+ // than setting the "addon" parameter, which is the actual extension
192
+ // base64 encoded, we need to set the "path" which is the filepath to the
193
+ // extension. This allows downstream to test upgrade paths by updating the
194
+ // extension source code and reloading it.
195
+ // See https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/node/selenium-webdriver/firefox.js
196
+ await driver.execute(
197
+ new Command("install addon")
198
+ .setParameter("path", extensionPath)
199
+ .setParameter("temporary", true));
193
200
  }
194
201
  return driver;
195
202
  }
package/src/utils.js CHANGED
@@ -34,7 +34,8 @@ export const errMsg = {
34
34
  browserDownload: "Browser download failed",
35
35
  browserNotInstalled: "Browser is not installed",
36
36
  browserVersionCheck: "Checking the browser version failed",
37
- elemNotFound: "HTML element not found"
37
+ elemNotFound: "HTML element not found",
38
+ manifestNotFound: "Extension manifest file not found"
38
39
  };
39
40
  export const platformArch = `${process.platform}-${process.arch}`;
40
41
 
@@ -238,6 +239,12 @@ export async function takeFullPageScreenshot(driver, hideScrollbars = true) {
238
239
  return fullScreenshot;
239
240
  }
240
241
 
242
+ /**
243
+ * Returns the major version of a browser version number.
244
+ * @param {string} versionNumber Full browser version number.
245
+ * @return {number} Major version number.
246
+ * @throws {Error} Unsupported browser version.
247
+ */
241
248
  export function getMajorVersion(versionNumber) {
242
249
  let majorVersion = parseInt(versionNumber && versionNumber.split(".")[0], 10);
243
250
  if (isNaN(majorVersion))
@@ -258,3 +265,14 @@ export function checkPlatform() {
258
265
  if (!["win32", "linux", "darwin"].includes(process.platform))
259
266
  throw new Error(`${errMsg.unsupportedPlatform}: ${process.platform}`);
260
267
  }
268
+
269
+ export async function checkExtensionPaths(extensionPaths) {
270
+ for (let extensionPath of extensionPaths) {
271
+ try {
272
+ await fs.promises.access(path.join(extensionPath, "manifest.json"));
273
+ }
274
+ catch (err) {
275
+ throw new Error(`${errMsg.manifestNotFound}: ${extensionPath}`);
276
+ }
277
+ }
278
+ }
package/test/browsers.js CHANGED
@@ -25,8 +25,6 @@ import {BROWSERS, snapshotsBaseDir, takeFullPageScreenshot} from "../index.js";
25
25
  import {killDriverProcess} from "../src/utils.js";
26
26
  import {TEST_SERVER_URL} from "./test-server.js";
27
27
 
28
- import "geckodriver"; // Required to set the driver path on Windows
29
-
30
28
  const VERSIONS = {
31
29
  chromium: ["latest", "77.0.3865.0", "beta", "dev"],
32
30
  firefox: ["latest", "68.0", "beta"],
@@ -106,42 +104,21 @@ function getExtension(browser, version) {
106
104
  return {extensionPaths, manifest};
107
105
  }
108
106
 
109
- async function getCachedTimes(browser) {
110
- let armLinux = process.platform == "linux" && process.arch == "arm64";
111
- let cacheDir = path.join(snapshotsBaseDir, browser, "cache");
112
- let cacheFiles;
113
- try {
114
- cacheFiles = await fs.promises.readdir(cacheDir);
115
- }
116
- catch (err) {
117
- if (!armLinux)
118
- throw err;
119
- }
120
-
121
- let browserCacheTime = null;
107
+ async function getInstallFileCTime(browser) {
122
108
  // Browsers installed at OS level are not tested
123
- if (browser != "edge" && !armLinux) {
124
- let installTypes = [".zip", ".dmg", ".bz2"];
125
- let browserZip =
126
- cacheFiles.find(elem => installTypes.some(type => elem.includes(type)));
127
- let browserStat = await fs.promises.stat(path.join(cacheDir, browserZip));
128
- browserCacheTime = browserStat.ctimeMs;
129
- }
109
+ if (browser == "edge" && process.platform == "win32")
110
+ return null;
130
111
 
131
- let driverCacheTime = null;
132
- // geckodriver is installed by npm, that's why Firefox is not tested here
133
- // for chromiumdriver on arm linux, see https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
134
- if (browser != "firefox" && !armLinux) {
135
- let driverDir = cacheFiles.find(
136
- elem => !elem.endsWith(".zip") && !elem.endsWith(".pkg"));
137
- let driverFiles = await fs.promises.readdir(path.join(cacheDir, driverDir));
138
- let driverZip = driverFiles.find(elem => elem.endsWith(".zip"));
139
- let driverStat =
140
- await fs.promises.stat(path.join(cacheDir, driverDir, driverZip));
141
- driverCacheTime = driverStat.ctimeMs;
142
- }
112
+ const installTypes = [".zip", ".dmg", ".bz2", ".deb", ".exe"];
113
+
114
+ let cacheDir = path.join(snapshotsBaseDir, browser, "cache");
115
+ let cacheFiles = await fs.promises.readdir(cacheDir);
116
+ let browserZip =
117
+ cacheFiles.find(elem => installTypes.some(type => elem.includes(type)));
118
+ if (!browserZip)
119
+ throw new Error(`Files in ${cacheDir} don't belong to any known install file types: ${installTypes}`);
143
120
 
144
- return {browserCTime: browserCacheTime, driverCTime: driverCacheTime};
121
+ return (await fs.promises.stat(path.join(cacheDir, browserZip))).ctimeMs;
145
122
  }
146
123
 
147
124
  const browserNames = {
@@ -159,6 +136,11 @@ async function basicUrlTest(driver, browser) {
159
136
  expect(text).toEqual("Test server basic page");
160
137
  }
161
138
 
139
+ // Adding the browser version to the test title for logging purposes
140
+ function addVersionToTitle(ctx, version) {
141
+ ctx.test.title = `${ctx.test.title} [v${version}]`;
142
+ }
143
+
162
144
  for (let browser of Object.keys(BROWSERS)) {
163
145
  describe(`Browser: ${browser}`, () => {
164
146
  before(async() => {
@@ -171,7 +153,7 @@ for (let browser of Object.keys(BROWSERS)) {
171
153
  for (let version of VERSIONS[browser]) {
172
154
  describe(`Version: ${version}`, () => {
173
155
  let driver = null;
174
- let emptyCacheTimes = null;
156
+ let firstInstallFileCTime = null;
175
157
  let customBrowserBinary = null;
176
158
  let failedInstall = false;
177
159
 
@@ -217,31 +199,32 @@ for (let browser of Object.keys(BROWSERS)) {
217
199
  expect(installedVersion).toEqual(
218
200
  expect.stringContaining(normalize(versionNumber)));
219
201
 
220
- // Adding the version number to the test title for logging purposes
221
- this.test.title = `${this.test.title} [v${versionNumber}]`;
202
+ addVersionToTitle(this, versionNumber);
203
+
204
+ // Data used in further tests
222
205
  customBrowserBinary = binary;
206
+ firstInstallFileCTime = await getInstallFileCTime(browser);
223
207
  });
224
208
 
225
- it("runs", async() => {
209
+ it("runs", async function() {
226
210
  driver = await BROWSERS[browser].getDriver(version);
227
211
  await basicUrlTest(driver, browser);
228
212
 
229
- // When running all tests, this saves time on the cache test
230
- emptyCacheTimes = await getCachedTimes(browser);
213
+ let browserVersion =
214
+ (await driver.getCapabilities()).getBrowserVersion();
215
+ addVersionToTitle(this, browserVersion);
231
216
  });
232
217
 
233
- it("uses cached install files", async() => {
234
- if (emptyCacheTimes == null) { // Single test case run
235
- driver = await BROWSERS[browser].getDriver(version);
236
- emptyCacheTimes = await getCachedTimes(browser);
237
- await quitDriver();
238
- }
218
+ // This test depends on running the "installs" test
219
+ it("uses cached install files", async function() {
220
+ if (!firstInstallFileCTime)
221
+ this.skip();
239
222
 
240
223
  // assigning `driver` to allow the afterEach hook quit the driver
241
224
  driver = await BROWSERS[browser].getDriver(version);
242
- let existingCacheTimes = await getCachedTimes(browser);
225
+ let secondInstallFileCTime = await getInstallFileCTime(browser);
243
226
 
244
- expect(existingCacheTimes).toEqual(emptyCacheTimes);
227
+ expect(secondInstallFileCTime).toEqual(firstInstallFileCTime);
245
228
  });
246
229
 
247
230
  // This test depends on running the "installs" test
@@ -288,6 +271,11 @@ for (let browser of Object.keys(BROWSERS)) {
288
271
  expect(fullImg.bitmap.height).toBeGreaterThan(partImg.bitmap.height);
289
272
  });
290
273
 
274
+ // The only reason the geckodriver package is required in package.json
275
+ // is to install a specific version needed by Firefox 68.0, otherwise it
276
+ // fails to load extensions. When the oldest Firefox version is bumped,
277
+ // Selenium's automated driver download should be able to manage it.
278
+ // https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/44
291
279
  it("loads an extension", async() => {
292
280
  // Chromium's old headless mode doesn't support loading extensions
293
281
  let headless = browser == "firefox" || (browser == "chromium" &&
@@ -324,10 +312,11 @@ for (let browser of Object.keys(BROWSERS)) {
324
312
  }
325
313
  });
326
314
 
327
- it("does not run not installed custom browsers", async() => {
328
- let customBrowserBinary = "not-installed";
329
- await expect(BROWSERS[browser].getDriver("latest", {customBrowserBinary}))
330
- .rejects.toThrow(/(Browser is not installed|binary is not a Firefox executable)/);
315
+ it("does not load an invalid extension", async() => {
316
+ let extensionPaths = [process.cwd()];
317
+
318
+ await expect(BROWSERS[browser].getDriver("latest", {extensionPaths}))
319
+ .rejects.toThrow(`Extension manifest file not found: ${extensionPaths[0]}`);
331
320
  });
332
321
  });
333
322
  }
package/test/runner.js CHANGED
@@ -53,10 +53,12 @@ export async function killTestServer() {
53
53
  }
54
54
 
55
55
  async function run() {
56
- let args = process.argv.join(" ").split("-- ")[1];
56
+ let args = process.argv.slice(2); // Keep npm args from "--" onwards
57
+ args = args.map(v => v.startsWith("-") ? v : `"${v}"`);
58
+
57
59
  await runTestServer();
58
60
  try {
59
- execSync(`npm run test-suite -- ${args}`, {stdio: "inherit"});
61
+ execSync(`npm run test-suite ${args.join(" ")}`, {stdio: "inherit"});
60
62
  }
61
63
  finally {
62
64
  await killTestServer();
@@ -1,27 +0,0 @@
1
- # Duplicated from registry.gitlab.com/eyeo/docker/get-browser-binary:node18
2
- # https://gitlab.com/eyeo/developer-experience/get-browser-binary/-/issues/46
3
- FROM node:18-bullseye-slim
4
- # General packages
5
- RUN apt-get update && apt-get install -y git procps wget unzip bzip2 gnupg
6
- # xvfb (headful browser run)
7
- RUN apt-get install -y libgtk-3-0 libxt6 xvfb libnss3 libxss1
8
- # General browser dependencies
9
- RUN apt-get install -y libgconf-2-4 libasound2 libgbm1
10
- # Edge dependencies
11
- RUN apt-get install -y fonts-liberation libatomic1 xdg-utils libu2f-udev
12
-
13
- # Chromium ARM
14
- RUN apt-get install -y chromium
15
- # https://askubuntu.com/questions/1360864/google-chrome-not-launching
16
- RUN mkdir -p "/root/.config/chromium/Crash Reports/pending/"
17
-
18
- # Firefox ARM
19
- RUN apt-get install -y firefox-esr
20
-
21
- COPY package*.json get-browser-binary/
22
- RUN cd get-browser-binary && npm install
23
-
24
- COPY . get-browser-binary/
25
-
26
- ENV TEST_ARGS="--grep Browser"
27
- ENTRYPOINT get-browser-binary/test/docker/entrypoint.sh