@eyeo/get-browser-binary 0.5.0 → 0.7.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/src/browsers.js CHANGED
@@ -16,10 +16,9 @@
16
16
  */
17
17
 
18
18
  import path from "path";
19
- import {exec, execFile, spawn} from "child_process";
19
+ import {exec, execFile} from "child_process";
20
20
  import {promisify} from "util";
21
21
  import fs from "fs";
22
- import fsExtra from "fs-extra";
23
22
 
24
23
  import got from "got";
25
24
  import webdriver from "selenium-webdriver";
@@ -28,31 +27,47 @@ import firefox from "selenium-webdriver/firefox.js";
28
27
  import edge from "selenium-webdriver/edge.js";
29
28
  import command from "selenium-webdriver/lib/command.js";
30
29
  import extractZip from "extract-zip";
30
+ import Jimp from "jimp";
31
31
 
32
- import {download, extractTar, extractDmg, getBrowserVersion, killDriverProcess,
33
- wait} from "./utils.js";
32
+ import {download, extractTar, extractDmg, killDriverProcess, wait}
33
+ from "./utils.js";
34
34
 
35
35
  /**
36
- * Root folder where browser and webdriver binaries get downloaded.
36
+ * Root folder where browser and webdriver files get downloaded and extracted.
37
37
  * @type {string}
38
38
  */
39
39
  export let snapshotsBaseDir = path.join(process.cwd(), "browser-snapshots");
40
40
 
41
41
  let {until, By} = webdriver;
42
- let platform = `${process.platform}-${process.arch}`;
43
-
44
- function checkVersion(version, minVersion, channels) {
42
+ let {platform, arch} = process;
43
+ let platformArch = `${platform}-${arch}`;
44
+
45
+ const UNSUPPORTED_VERSION_ERROR = "Unsupported browser version";
46
+ const UNSUPPORTED_PLATFORM_ERROR = "Unsupported platform";
47
+ const DRIVER_DOWNLOAD_ERROR = "Driver download failed";
48
+ const DRIVER_START_ERROR = "Unable to start driver";
49
+ const EXTENSION_NOT_FOUND_ERROR = "Extension not found";
50
+ const BROWSER_DOWNLOAD_ERROR = "Browser download failed";
51
+ const BROWSER_NOT_INSTALLED_ERROR = "Browser is not installed";
52
+ const ELEMENT_NOT_FOUND_ERROR = "HTML element not found";
53
+
54
+ function checkVersion(version, minVersion, channels = []) {
45
55
  if (channels.includes(version))
46
56
  return;
47
57
 
48
58
  let mainVersion = parseInt(version && version.split(".")[0], 10);
49
- if (mainVersion < minVersion)
50
- throw new Error(`Unsupported browser version: ${version}`);
59
+ if (isNaN(mainVersion) || mainVersion < minVersion)
60
+ throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
61
+ }
62
+
63
+ function checkPlatform() {
64
+ if (!["win32", "linux", "darwin"].includes(platform))
65
+ throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
51
66
  }
52
67
 
53
68
  /**
54
- * Base class for browser download functionality. Please see subclasses for
55
- * browser specific details.
69
+ * Base class for browser and webdriver functionality. Please see subclasses for
70
+ * browser specific details. All classes can be used statically.
56
71
  * @hideconstructor
57
72
  */
58
73
  class Browser {
@@ -64,13 +79,15 @@ class Browser {
64
79
  */
65
80
 
66
81
  /**
67
- * Downloads the browser binary file.
82
+ * Installs the browser. The installation process is detailed on the
83
+ * subclasses.
68
84
  * @param {string} version - Either full version number or channel/release.
69
85
  * Please find examples on the subclasses.
70
86
  * @return {BrowserBinary}
71
- * @throws {Error} Unsupported browser version.
87
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
88
+ * download failed.
72
89
  */
73
- static async downloadBinary(version) {
90
+ static async installBrowser(version) {
74
91
  // to be implemented by the subclass
75
92
  }
76
93
 
@@ -81,7 +98,7 @@ class Browser {
81
98
  */
82
99
  static async getInstalledVersion(binary) {
83
100
  let stdout;
84
- if (process.platform == "win32") {
101
+ if (platform == "win32") {
85
102
  ({stdout} = await promisify(exec)(
86
103
  `(Get-ItemProperty ${binary}).VersionInfo.ProductVersion`,
87
104
  {shell: "powershell.exe"})
@@ -119,7 +136,8 @@ class Browser {
119
136
  * Please find examples on the subclasses.
120
137
  * @param {driverOptions?} options - Options to start the browser with.
121
138
  * @return {webdriver}
122
- * @throws {Error} Unsupported browser version.
139
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
140
+ * download failed, Driver download failed, Unable to start driver.
123
141
  */
124
142
  static async getDriver(version, options = {}) {
125
143
  // to be implemented by the subclass
@@ -131,7 +149,8 @@ class Browser {
131
149
  * @param {webdriver} driver - The driver controlling the browser.
132
150
  * @param {string} extensionTitle - Title of the extebsion to be enabled.
133
151
  * @return {webdriver}
134
- * @throws {Error} Unsupported browser version.
152
+ * @throws {Error} Unsupported browser version, Extension not found, HTML
153
+ * element not found.
135
154
  */
136
155
  static async enableExtensionInIncognito(driver, extensionTitle) {
137
156
  // Allowing the extension in incognito mode can't happen programmatically:
@@ -139,22 +158,80 @@ class Browser {
139
158
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1729315
140
159
  // That is done through the UI, to be implemented by the subclass
141
160
  }
161
+
162
+ /**
163
+ * @typedef {Object} Jimp
164
+ * @see https://github.com/oliver-moran/jimp/tree/master/packages/jimp
165
+ */
166
+
167
+ /**
168
+ * Takes a screenshot of the full page by scrolling from top to bottom.
169
+ * @param {webdriver} driver - The driver controlling the browser.
170
+ * @property {boolean} hideScrollbars=true - Hides any scrollbars before
171
+ * taking the screenshot, or not.
172
+ * @return {Jimp} A Jimp image object containing the screenshot.
173
+ * @example
174
+ * // Getting a base-64 encoded PNG from the returned screenshot
175
+ * let image = await Browser.takeFullPageScreenshot(driver);
176
+ * let encodedPNG = await image.getBase64Async("image/png");
177
+ */
178
+ static async takeFullPageScreenshot(driver, hideScrollbars = true) {
179
+ // On macOS scrollbars appear and disappear overlapping the content as
180
+ // scrolling occurs. Hiding the scrollbars helps getting reproducible
181
+ // screenshots.
182
+ if (hideScrollbars) {
183
+ await driver.executeScript(() => {
184
+ if (!document.head)
185
+ return;
186
+
187
+ let style = document.createElement("style");
188
+ style.textContent = "html { overflow-y: scroll; }";
189
+ document.head.appendChild(style);
190
+ if (document.documentElement.clientWidth == window.innerWidth)
191
+ style.textContent = "html::-webkit-scrollbar { display: none; }";
192
+ else
193
+ document.head.removeChild(style);
194
+ });
195
+ }
196
+
197
+ let fullScreenshot = new Jimp(0, 0);
198
+ while (true) {
199
+ let [width, height, offset] = await driver.executeScript((...args) => {
200
+ window.scrollTo(0, args[0]);
201
+ // Math.ceil rounds up potential decimal values on window.scrollY,
202
+ // ensuring the loop will not hang due to never reaching enough
203
+ // fullScreenshot's height.
204
+ return [document.documentElement.clientWidth,
205
+ document.documentElement.scrollHeight,
206
+ Math.ceil(window.scrollY)];
207
+ }, fullScreenshot.bitmap.height);
208
+ let data = await driver.takeScreenshot();
209
+ let partialScreenshot = await Jimp.read(Buffer.from(data, "base64"));
210
+ let combinedScreenshot =
211
+ new Jimp(width, offset + partialScreenshot.bitmap.height);
212
+ combinedScreenshot.composite(fullScreenshot, 0, 0);
213
+ combinedScreenshot.composite(partialScreenshot, 0, offset);
214
+ fullScreenshot = combinedScreenshot;
215
+
216
+ if (fullScreenshot.bitmap.height >= height)
217
+ break;
218
+ }
219
+ return fullScreenshot;
220
+ }
142
221
  }
143
222
 
144
223
  /**
145
- * Download functionality for Chromium. This class can be used statically.
224
+ * Browser and webdriver functionality for Chromium.
146
225
  * @hideconstructor
147
226
  * @extends Browser
148
227
  */
149
228
  class Chromium extends Browser {
150
- static #DRIVER = "chromedriver";
151
-
152
- static async #getBranchBasePosition(version) {
153
- let data = await got(`https://omahaproxy.appspot.com/deps.json?version=${version}`).json();
154
- return data.chromium_base_position;
155
- }
229
+ static #CHANNELS = ["latest", "beta", "dev"];
156
230
 
157
231
  static async #getVersionForChannel(channel) {
232
+ if (!Chromium.#CHANNELS.includes(channel))
233
+ return channel;
234
+
158
235
  if (channel == "latest")
159
236
  channel = "stable";
160
237
 
@@ -164,7 +241,7 @@ class Chromium extends Browser {
164
241
  "linux-x64": "linux",
165
242
  "darwin-x64": "mac",
166
243
  "dawrin-arm64": "mac_arm64"
167
- }[platform];
244
+ }[platformArch];
168
245
  let data = await got(`https://omahaproxy.appspot.com/all.json?os=${os}`).json();
169
246
  let release = data[0].versions.find(ver => ver.channel == channel);
170
247
  let {current_version: version} = release;
@@ -178,55 +255,67 @@ class Chromium extends Browser {
178
255
  }
179
256
 
180
257
  static #getBinaryPath(dir) {
181
- switch (process.platform) {
182
- case "win32":
183
- return path.join(dir, "chrome-win", "chrome.exe");
184
- case "linux":
185
- return path.join(dir, "chrome-linux", "chrome");
186
- case "darwin":
187
- return path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
188
- "Chromium");
189
- default:
190
- throw new Error(`Unexpected platform: ${process.platform}`);
191
- }
258
+ checkPlatform();
259
+ return {
260
+ win32: path.join(dir, "chrome-win", "chrome.exe"),
261
+ linux: path.join(dir, "chrome-linux", "chrome"),
262
+ darwin: path.join(dir, "chrome-mac", "Chromium.app", "Contents", "MacOS",
263
+ "Chromium")
264
+ }[platform];
192
265
  }
193
266
 
194
- static async #downloadChromium(chromiumRevision) {
195
- const MAX_VERSION_DECREMENTS = 50;
267
+ /**
268
+ * Installs the browser. The Chromium executable gets extracted in the
269
+ * {@link snapshotsBaseDir} folder, ready to go.
270
+ * @param {string} version - Either "latest", "beta", "dev" or a full version
271
+ * number (i.e. "77.0.3865.0"). Defaults to "latest".
272
+ * @return {BrowserBinary}
273
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
274
+ * download failed.
275
+ */
276
+ static async installBrowser(version = "latest") {
277
+ const MIN_VERSION = 75;
278
+ const MAX_VERSION_DECREMENTS = 80;
196
279
 
197
- let revision = parseInt(chromiumRevision, 10);
198
- let startingRevision = revision;
280
+ checkVersion(version, MIN_VERSION, Chromium.#CHANNELS);
281
+ let versionNumber = await Chromium.#getVersionForChannel(version);
282
+
283
+ let {chromium_base_position: chromiumBase} =
284
+ await got(`https://omahaproxy.appspot.com/deps.json?version=${versionNumber}`).json();
285
+ let base = parseInt(chromiumBase, 10);
286
+ let startBase = base;
199
287
  let [platformDir, fileName] = {
200
288
  "win32-ia32": ["Win", "chrome-win.zip"],
201
289
  "win32-x64": ["Win_x64", "chrome-win.zip"],
202
290
  "linux-x64": ["Linux_x64", "chrome-linux.zip"],
203
291
  "darwin-x64": ["Mac", "chrome-mac.zip"],
204
292
  "dawrin-arm64": ["Mac_Arm", "chrome-mac.zip"]
205
- }[platform];
206
- let archive = null;
207
- let browserDir = null;
293
+ }[platformArch];
294
+ let archive;
295
+ let browserDir;
208
296
  let snapshotsDir = path.join(snapshotsBaseDir, "chromium");
297
+ let binary;
209
298
 
210
299
  while (true) {
211
- browserDir = path.join(snapshotsDir, `chromium-${platform}-${revision}`);
300
+ browserDir = path.join(snapshotsDir, `chromium-${platformArch}-${base}`);
301
+ binary = Chromium.#getBinaryPath(browserDir);
212
302
 
213
303
  try {
214
304
  await fs.promises.access(browserDir);
215
- return {binary: Chromium.#getBinaryPath(browserDir), revision};
305
+ return {binary, versionNumber, base};
216
306
  }
217
307
  catch (e) {}
218
308
 
219
309
  await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
220
310
 
221
- archive = path.join(snapshotsDir, "cache", `${revision}-${fileName}`);
222
-
311
+ archive = path.join(snapshotsDir, "cache", `${base}-${fileName}`);
223
312
  try {
224
313
  try {
225
314
  await fs.promises.access(archive);
226
315
  }
227
316
  catch (e) {
228
317
  await download(
229
- `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${revision}%2F${fileName}?alt=media`,
318
+ `https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${platformDir}%2F${base}%2F${fileName}?alt=media`,
230
319
  archive);
231
320
  }
232
321
  break;
@@ -234,58 +323,53 @@ class Chromium extends Browser {
234
323
  catch (e) {
235
324
  // Chromium advises decrementing the branch_base_position when no
236
325
  // matching build was found. See https://www.chromium.org/getting-involved/download-chromium
237
- revision--;
238
- if (revision <= startingRevision - MAX_VERSION_DECREMENTS)
239
- throw new Error(`No Chromium package found for ${startingRevision}`);
326
+ base--;
327
+ if (base <= startBase - MAX_VERSION_DECREMENTS)
328
+ throw new Error(`${BROWSER_DOWNLOAD_ERROR}: Chromium base ${startBase}`);
240
329
  }
241
330
  }
242
-
243
331
  await extractZip(archive, {dir: browserDir});
244
- return {binary: Chromium.#getBinaryPath(browserDir), revision};
245
- }
246
-
247
- /**
248
- * Downloads the browser binary file.
249
- * @param {string} version - Either "latest", "beta", "dev" or a full version
250
- * number (i.e. "77.0.3865.0"). Defaults to "latest".
251
- * @return {BrowserBinary}
252
- */
253
- static async downloadBinary(version = "latest") {
254
- const MIN_VERSION = 75;
255
- const CHANNELS = ["latest", "beta", "dev"];
256
332
 
257
- checkVersion(version, MIN_VERSION, CHANNELS);
258
-
259
- let versionNumber = CHANNELS.includes(version) ?
260
- await Chromium.#getVersionForChannel(version) : version;
261
-
262
- let base = await Chromium.#getBranchBasePosition(versionNumber);
263
-
264
- let {binary, revision} = await Chromium.#downloadChromium(base);
265
- return {binary, versionNumber, revision};
333
+ return {binary, versionNumber, base};
266
334
  }
267
335
 
268
- static async #installDriver(revision, version) {
269
- let [dir, zip, driver] = {
336
+ static async #installDriver(base, versionNumber) {
337
+ let [dir, zip, driverBinary] = {
270
338
  "win32-ia32": ["Win", "chromedriver_win32.zip", "chromedriver.exe"],
271
339
  "win32-x64": ["Win_x64", "chromedriver_win32.zip", "chromedriver.exe"],
272
340
  "linux-x64": ["Linux_x64", "chromedriver_linux64.zip", "chromedriver"],
273
341
  "darwin-x64": ["Mac", "chromedriver_mac64.zip", "chromedriver"],
274
342
  "darwin-arm64": ["Mac_Arm", "chromedriver_mac64.zip", "chromedriver"]
275
- }[platform];
343
+ }[platformArch];
276
344
 
277
- let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache", version);
278
- let destinationDir = path.join(process.cwd(), "node_modules",
279
- Chromium.#DRIVER, "lib", Chromium.#DRIVER);
280
- let archive = path.join(cacheDir, `${revision}-${zip}`);
345
+ let cacheDir = path.join(snapshotsBaseDir, "chromium", "cache",
346
+ versionNumber);
347
+ let archive = path.join(cacheDir, `${base}-${zip}`);
281
348
 
282
- await download(`https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${revision}/${zip}`,
283
- archive);
349
+ try {
350
+ await fs.promises.access(archive);
351
+ await extractZip(archive, {dir: cacheDir});
352
+ }
353
+ catch (e) { // zip file is either not cached or corrupted
354
+ let url = `https://commondatastorage.googleapis.com/chromium-browser-snapshots/${dir}/${base}/${zip}`;
355
+ try {
356
+ await download(url, archive);
357
+ }
358
+ catch (err) {
359
+ throw new Error(`${DRIVER_DOWNLOAD_ERROR}: ${url}\n${err}`);
360
+ }
361
+ await extractZip(archive, {dir: cacheDir});
362
+ }
363
+
364
+ await killDriverProcess("chromedriver");
365
+ let driverPath = path.join(cacheDir, zip.split(".")[0], driverBinary);
366
+ try {
367
+ await fs.promises.rm(driverPath, {recursive: true});
368
+ }
369
+ catch (e) {} // file does not exist
284
370
  await extractZip(archive, {dir: cacheDir});
285
- await killDriverProcess(Chromium.#DRIVER);
286
- await fs.promises.mkdir(destinationDir, {recursive: true});
287
- await fs.promises.copyFile(path.join(cacheDir, zip.split(".")[0], driver),
288
- path.join(destinationDir, driver));
371
+
372
+ return driverPath;
289
373
  }
290
374
 
291
375
  /** @see Browser.getDriver */
@@ -293,10 +377,9 @@ class Chromium extends Browser {
293
377
  headless = true, extensionPaths = [], incognito = false, insecure = false,
294
378
  extraArgs = []
295
379
  } = {}) {
296
- let {binary, revision, versionNumber} =
297
- await Chromium.downloadBinary(version);
298
- await Chromium.#installDriver(revision, versionNumber);
299
-
380
+ let {binary, versionNumber, base} = await Chromium.installBrowser(version);
381
+ let driverPath = await Chromium.#installDriver(base, versionNumber);
382
+ let serviceBuilder = new chrome.ServiceBuilder(driverPath);
300
383
  let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
301
384
  if (extensionPaths.length > 0)
302
385
  options.addArguments(`load-extension=${extensionPaths.join(",")}`);
@@ -311,6 +394,7 @@ class Chromium extends Browser {
311
394
  let builder = new webdriver.Builder();
312
395
  builder.forBrowser("chrome");
313
396
  builder.setChromeOptions(options);
397
+ builder.setChromeService(serviceBuilder);
314
398
 
315
399
  return builder.build();
316
400
  }
@@ -335,106 +419,106 @@ class Chromium extends Browser {
335
419
  continue;
336
420
 
337
421
  extensionDetails = shadowRoot.getElementById("detailsButton");
422
+ break;
338
423
  }
339
-
340
424
  if (!extensionDetails)
341
- reject("Extension was not found");
425
+ reject(`${args[1]}: ${args[0]}`);
342
426
 
343
427
  extensionDetails.click();
344
428
  setTimeout(() => resolve(enable()), 100);
345
429
  });
346
- }, extensionTitle);
430
+ }, extensionTitle, EXTENSION_NOT_FOUND_ERROR);
347
431
  }
348
432
  }
349
433
 
350
434
  /**
351
- * Download functionality for Firefox. This class can be used statically.
435
+ * Browser and webdriver functionality for Firefox.
352
436
  * @hideconstructor
353
437
  * @extends Browser
354
438
  */
355
439
  class Firefox extends Browser {
356
- static async #getVersionForChannel(branch) {
440
+ static #CHANNELS = ["latest", "beta"];
441
+
442
+ static async #getVersionForChannel(channel) {
443
+ if (!Firefox.#CHANNELS.includes(channel))
444
+ return channel;
445
+
357
446
  let data = await got("https://product-details.mozilla.org/1.0/firefox_versions.json").json();
358
- return branch == "beta" ?
447
+ return channel == "beta" ?
359
448
  data.LATEST_FIREFOX_DEVEL_VERSION : data.LATEST_FIREFOX_VERSION;
360
449
  }
361
450
 
362
451
  static #getBinaryPath(dir) {
363
- switch (process.platform) {
364
- case "win32":
365
- return path.join(dir, "core", "firefox.exe");
366
- case "linux":
367
- return path.join(dir, "firefox", "firefox");
368
- case "darwin":
369
- return path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox");
370
- default:
371
- throw new Error(`Unexpected platform: ${process.platform}`);
372
- }
452
+ checkPlatform();
453
+ return {
454
+ win32: path.join(dir, "core", "firefox.exe"),
455
+ linux: path.join(dir, "firefox", "firefox"),
456
+ darwin: path.join(dir, "Firefox.app", "Contents", "MacOS", "firefox")
457
+ }[platform];
373
458
  }
374
459
 
375
460
  static #extractFirefoxArchive(archive, dir) {
376
- switch (process.platform) {
461
+ switch (platform) {
377
462
  case "win32":
378
- // Procedure inspired from mozinstall:
379
- // https://hg.mozilla.org/mozilla-central/file/tip/testing/mozbase/mozinstall/mozinstall/mozinstall.py
380
463
  return promisify(exec)(`"${archive}" /extractdir=${dir}`);
381
464
  case "linux":
382
465
  return extractTar(archive, dir);
383
466
  case "darwin":
384
467
  return extractDmg(archive, dir);
385
468
  default:
386
- throw new Error(`Unexpected platform: ${process.platform}`);
469
+ checkPlatform();
387
470
  }
388
471
  }
389
472
 
390
- static async #downloadFirefox(version) {
473
+ /**
474
+ * Installs the browser. The Firefox executable gets extracted in the
475
+ * {@link snapshotsBaseDir} folder, ready to go.
476
+ * @param {string} version - Either "latest", "beta" or a full version
477
+ * number (i.e. "68.0"). Defaults to "latest".
478
+ * @return {BrowserBinary}
479
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
480
+ * download failed.
481
+ */
482
+ static async installBrowser(version = "latest") {
483
+ const MIN_VERSION = 60;
484
+
485
+ checkVersion(version, MIN_VERSION, Firefox.#CHANNELS);
486
+ let versionNumber = await Firefox.#getVersionForChannel(version);
487
+
391
488
  let [buildPlatform, fileName] = {
392
- "win32-ia32": ["win32", `Firefox Setup ${version}.exe`],
393
- "win32-x64": ["win64", `Firefox Setup ${version}.exe`],
394
- "linux-x64": ["linux-x86_64", `firefox-${version}.tar.bz2`],
395
- "darwin-x64": ["mac", `Firefox ${version}.dmg`],
396
- "darwin-arm64": ["mac", `Firefox ${version}.dmg`]
397
- }[platform];
489
+ "win32-ia32": ["win32", `Firefox Setup ${versionNumber}.exe`],
490
+ "win32-x64": ["win64", `Firefox Setup ${versionNumber}.exe`],
491
+ "linux-x64": ["linux-x86_64", `firefox-${versionNumber}.tar.bz2`],
492
+ "darwin-x64": ["mac", `Firefox ${versionNumber}.dmg`],
493
+ "darwin-arm64": ["mac", `Firefox ${versionNumber}.dmg`]
494
+ }[platformArch];
398
495
 
399
496
  let snapshotsDir = path.join(snapshotsBaseDir, "firefox");
400
- let browserDir = path.join(snapshotsDir, `firefox-${platform}-${version}`);
401
- let archive = path.join(snapshotsDir, "cache", fileName);
402
-
497
+ let browserDir = path.join(snapshotsDir,
498
+ `firefox-${platformArch}-${versionNumber}`);
499
+ let binary = Firefox.#getBinaryPath(browserDir);
403
500
  try {
404
501
  await fs.promises.access(browserDir);
405
- return Firefox.#getBinaryPath(browserDir);
502
+ return {binary, versionNumber};
406
503
  }
407
504
  catch (e) {}
408
505
 
506
+ let archive = path.join(snapshotsDir, "cache", fileName);
409
507
  await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
410
508
  try {
411
509
  await fs.promises.access(archive);
412
510
  }
413
511
  catch (e) {
414
- let url = `https://archive.mozilla.org/pub/firefox/releases/${version}/${buildPlatform}/en-US/${fileName}`;
415
- await download(url, archive);
512
+ let url = `https://archive.mozilla.org/pub/firefox/releases/${versionNumber}/${buildPlatform}/en-US/${fileName}`;
513
+ try {
514
+ await download(url, archive);
515
+ }
516
+ catch (err) {
517
+ throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
518
+ }
416
519
  }
417
-
418
520
  await Firefox.#extractFirefoxArchive(archive, browserDir);
419
- return Firefox.#getBinaryPath(browserDir);
420
- }
421
-
422
- /**
423
- * Downloads the browser binary file
424
- * @param {string} version - Either "latest", "beta" or a full version
425
- * number (i.e. "68.0"). Defaults to "latest".
426
- * @return {BrowserBinary}
427
- */
428
- static async downloadBinary(version = "latest") {
429
- const MIN_VERSION = 60;
430
- const CHANNELS = ["latest", "beta"];
431
-
432
- checkVersion(version, MIN_VERSION, CHANNELS);
433
521
 
434
- let versionNumber = CHANNELS.includes(version) ?
435
- await Firefox.#getVersionForChannel(version) : version;
436
-
437
- let binary = await Firefox.#downloadFirefox(versionNumber);
438
522
  return {binary, versionNumber};
439
523
  }
440
524
 
@@ -443,7 +527,7 @@ class Firefox extends Browser {
443
527
  headless = true, extensionPaths = [], incognito = false, insecure = false,
444
528
  extraArgs = []
445
529
  } = {}) {
446
- let {binary} = await Firefox.downloadBinary(version);
530
+ let {binary} = await Firefox.installBrowser(version);
447
531
 
448
532
  let options = new firefox.Options();
449
533
  if (headless)
@@ -472,7 +556,7 @@ class Firefox extends Browser {
472
556
  throw err;
473
557
  await killDriverProcess("geckodriver");
474
558
  }
475
- }, 30000, "geckodriver didn't start, likely due to low OS resources", 1000);
559
+ }, 30000, `${DRIVER_START_ERROR}: geckodriver`, 1000);
476
560
 
477
561
  for (let extensionPath of extensionPaths) {
478
562
  await driver.execute(
@@ -486,10 +570,9 @@ class Firefox extends Browser {
486
570
 
487
571
  /** @see Browser.enableExtensionInIncognito */
488
572
  static async enableExtensionInIncognito(driver, extensionTitle) {
489
- let version = await getBrowserVersion(driver);
490
- if (version < 87)
491
- // The UI workaround assumes web elements only present on Firefox >= 87
492
- throw new Error(`Only supported on Firefox >= 87. Current version: ${version}`);
573
+ let version = (await driver.getCapabilities()).getBrowserVersion();
574
+ // The UI workaround assumes web elements only present on Firefox >= 87
575
+ checkVersion(version, 87);
493
576
 
494
577
  await driver.navigate().to("about:addons");
495
578
  await driver.wait(until.elementLocated(By.name("extension")), 1000).click();
@@ -502,33 +585,50 @@ class Firefox extends Browser {
502
585
  await elem.click();
503
586
  return await driver.findElement(By.name("private-browsing")).click();
504
587
  }
505
- throw new Error(`Extension "${extensionTitle}" not found`);
588
+ throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
506
589
  }
507
590
  }
508
591
 
509
592
  /**
510
- * Download functionality for Edge. This class can be used statically.
593
+ * Browser and webdriver functionality for Edge.
511
594
  * @hideconstructor
512
595
  * @extends Browser
513
596
  */
514
597
  class Edge extends Browser {
515
- static #DRIVER = "msedgedriver";
598
+ static #CHANNELS = ["latest", "beta", "dev"];
516
599
 
517
600
  static async #getVersionForChannel(version) {
518
- if (!["latest", "beta", "dev"].includes(version))
519
- return {versionNumber: version, channel: "stable"};
601
+ let channel = "stable";
602
+ if (Edge.#CHANNELS.includes(version) && version != "latest")
603
+ channel = version;
520
604
 
521
- let channel = version == "latest" ? "stable" : version;
522
605
  let {body} = await got(`https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/`);
523
- let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
524
- let matches;
525
- let versionNumbers = [];
526
- while ((matches = regex.exec(body)) !== null)
527
- versionNumbers.push(matches[2]);
528
-
529
- let compareVersions = (v1, v2) =>
530
- parseInt(v1.split(".")[0], 10) < parseInt(v2.split(".")[0], 10) ? 1 : -1;
531
- let versionNumber = versionNumbers.sort(compareVersions)[0];
606
+ let versionNumber;
607
+ if (Edge.#CHANNELS.includes(version)) {
608
+ let regex = /href="microsoft-edge-(stable|beta|dev)_(.*?)-1_/gm;
609
+ let matches;
610
+ let versionNumbers = [];
611
+ while ((matches = regex.exec(body)) !== null)
612
+ versionNumbers.push(matches[2]);
613
+
614
+ let compareVersions = (v1, v2) =>
615
+ parseInt(v1.split(".")[0], 10) < parseInt(v2.split(".")[0], 10) ?
616
+ 1 : -1;
617
+ versionNumber = versionNumbers.sort(compareVersions)[0];
618
+ }
619
+ else {
620
+ let split = version.split(".");
621
+ let minorVersion = split.length == 4 ? parseInt(split.pop(), 10) : -1;
622
+ let majorVersion = split.join(".");
623
+ let found;
624
+ while (!found && minorVersion >= 0) {
625
+ versionNumber = `${majorVersion}.${minorVersion}`;
626
+ found = body.includes(versionNumber);
627
+ minorVersion--;
628
+ }
629
+ if (!found)
630
+ throw new Error(`${UNSUPPORTED_VERSION_ERROR}: ${version}`);
631
+ }
532
632
 
533
633
  return {versionNumber, channel};
534
634
  }
@@ -540,10 +640,11 @@ class Edge extends Browser {
540
640
  }
541
641
 
542
642
  static #getBinaryPath(channel = "stable") {
543
- switch (process.platform) {
643
+ switch (platform) {
544
644
  case "win32":
545
- return "${Env:ProgramFiles(x86)}\\Microsoft\\Edge\\Application\\" +
546
- "msedge.exe";
645
+ let programFiles = process.env["ProgramFiles(x86)"] ?
646
+ "${Env:ProgramFiles(x86)}" : "${Env:ProgramFiles}";
647
+ return `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
547
648
  case "linux":
548
649
  return channel == "stable" ?
549
650
  "microsoft-edge" : `microsoft-edge-${channel}`;
@@ -551,26 +652,28 @@ class Edge extends Browser {
551
652
  let appName = Edge.#getDarwinAppName(channel);
552
653
  return `${process.env.HOME}/Applications/${appName}.app/Contents/MacOS/${appName}`;
553
654
  default:
554
- throw new Error(`Unexpected platform: ${process.platform}`);
655
+ checkPlatform();
555
656
  }
556
657
  }
557
658
 
558
659
  /**
559
- * Downloads the browser binary file.
660
+ * Installs the browser. On Linux, Edge is installed as a system package,
661
+ * which requires root permissions. On MacOS, Edge is installed as a user
662
+ * app (not as a system app). Installing Edge on Windows is not supported.
560
663
  * @param {string} version - Either "latest", "beta", "dev" or a full version
561
- * number (i.e. "95.0.1020.53"). Defaults to "latest". This is only
562
- * available on Linux.
664
+ * number (i.e. "95.0.1020.40"). Defaults to "latest".
563
665
  * @return {BrowserBinary}
666
+ * @throws {Error} Unsupported browser version, Unsupported platform, Browser
667
+ * download failed.
564
668
  */
565
- static async downloadBinary(version = "latest") {
566
- if (process.platform == "win32")
669
+ static async installBrowser(version = "latest") {
670
+ if (platform == "win32")
567
671
  // Edge is mandatory on Windows, can't be uninstalled or downgraded
568
672
  // https://support.microsoft.com/en-us/microsoft-edge/why-can-t-i-uninstall-microsoft-edge-ee150b3b-7d7a-9984-6d83-eb36683d526d
569
- throw new Error("Edge download is not supported in Windows");
673
+ throw new Error(`${UNSUPPORTED_PLATFORM_ERROR}: ${platform}`);
570
674
 
571
675
  const MIN_VERSION = 95;
572
- const CHANNELS = ["latest", "beta", "dev"];
573
- checkVersion(version, MIN_VERSION, CHANNELS);
676
+ checkVersion(version, MIN_VERSION, Edge.#CHANNELS);
574
677
  let {versionNumber, channel} = await Edge.#getVersionForChannel(version);
575
678
 
576
679
  let darwinName = {
@@ -581,14 +684,14 @@ class Edge extends Browser {
581
684
  let filename = {
582
685
  linux: `microsoft-edge-${channel}_${versionNumber}-1_amd64.deb`,
583
686
  darwin: `${darwinName}-${versionNumber}.pkg`
584
- }[process.platform];
687
+ }[platform];
585
688
  let darwinArch = process.arch == "arm64" ?
586
689
  "03adf619-38c6-4249-95ff-4a01c0ffc962" :
587
690
  "C1297A47-86C4-4C1F-97FA-950631F94777";
588
- let downloadUrl = {
691
+ let url = {
589
692
  linux: `https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-${channel}/${filename}`,
590
693
  darwin: `https://officecdnmac.microsoft.com/pr/${darwinArch}/MacAutoupdate/${filename}`
591
- }[process.platform];
694
+ }[platform];
592
695
 
593
696
  let snapshotsDir = path.join(snapshotsBaseDir, "edge");
594
697
  let archive = path.join(snapshotsDir, "cache", filename);
@@ -600,16 +703,16 @@ class Edge extends Browser {
600
703
  catch (e) {}
601
704
 
602
705
  try {
603
- await download(downloadUrl, archive);
706
+ await download(url, archive);
604
707
  }
605
708
  catch (err) {
606
- throw new Error(`Edge download failed: ${err}`);
709
+ throw new Error(`${BROWSER_DOWNLOAD_ERROR}: ${url}\n${err}`);
607
710
  }
608
711
 
609
- if (process.platform == "linux") {
712
+ if (platform == "linux") {
610
713
  await promisify(exec)(`dpkg -i ${archive}`);
611
714
  }
612
- else if (process.platform == "darwin") {
715
+ else if (platform == "darwin") {
613
716
  let appName = Edge.#getDarwinAppName(channel);
614
717
  try {
615
718
  await fs.promises.rm(`${process.env.HOME}/Applications/${appName}.app`, {recursive: true});
@@ -629,47 +732,55 @@ class Edge extends Browser {
629
732
  }
630
733
 
631
734
  static async #installDriver() {
735
+ async function extractEdgeZip(archive, cacheDir, driverPath) {
736
+ await killDriverProcess("msedgedriver");
737
+ try {
738
+ await fs.promises.rm(driverPath, {recursive: true});
739
+ }
740
+ catch (e) {} // file does not exist
741
+ await extractZip(archive, {dir: cacheDir});
742
+ }
743
+
632
744
  let binary = Edge.#getBinaryPath();
633
745
  let versionNumber = await Edge.#getInstalledVersionNumber(binary);
634
746
  if (!versionNumber)
635
- throw new Error("Edge is not installed");
747
+ throw new Error(`${BROWSER_NOT_INSTALLED_ERROR}: Edge`);
636
748
 
637
- let [zip, driver] = {
749
+ let [zip, driverBinary] = {
638
750
  "win32-ia32": ["edgedriver_win32.zip", "msedgedriver.exe"],
639
751
  "win32-x64": ["edgedriver_win64.zip", "msedgedriver.exe"],
640
752
  "linux-x64": ["edgedriver_linux64.zip", "msedgedriver"],
641
753
  "darwin-x64": ["edgedriver_mac64.zip", "msedgedriver"],
642
754
  "darwin-arm64": ["edgedriver_arm64.zip", "msedgedriver"]
643
- }[platform];
755
+ }[platformArch];
644
756
  let cacheDir = path.join(snapshotsBaseDir, "edge", "cache",
645
757
  `edgedriver-${versionNumber}`);
646
758
  let archive = path.join(cacheDir, `${versionNumber}-${zip}`);
759
+ let driverPath = path.join(cacheDir, driverBinary);
647
760
 
648
- let vSplit = versionNumber.split(".");
649
- let lastNum = parseInt(vSplit[3], 10);
650
- while (lastNum >= 0) {
651
- try {
652
- let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
653
- await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
654
- archive);
655
- break;
656
- }
657
- catch (e) {
658
- lastNum--;
659
- }
660
- }
661
-
662
- if (lastNum < 0)
663
- throw new Error(`msedgedriver was not found for Edge ${versionNumber}`);
664
-
665
- await killDriverProcess(Edge.#DRIVER);
666
- let driverPath = path.join(cacheDir, driver);
667
761
  try {
668
- await fs.promises.rm(driverPath, {recursive: true});
762
+ await fs.promises.access(archive);
763
+ await extractEdgeZip(archive, cacheDir, driverPath);
669
764
  }
670
- catch (e) {} // file does not exist
671
- await extractZip(archive, {dir: cacheDir});
765
+ catch (e) { // zip file is either not cached or corrupted
766
+ let vSplit = versionNumber.split(".");
767
+ let lastNum = parseInt(vSplit[3], 10);
768
+ while (lastNum >= 0) {
769
+ try {
770
+ let attempt = `${vSplit[0]}.${vSplit[1]}.${vSplit[2]}.${lastNum}`;
771
+ await download(`https://msedgedriver.azureedge.net/${attempt}/${zip}`,
772
+ archive);
773
+ break;
774
+ }
775
+ catch (e2) {
776
+ lastNum--;
777
+ }
778
+ }
779
+ if (lastNum < 0)
780
+ throw new Error(`${DRIVER_DOWNLOAD_ERROR}: Edge ${versionNumber}`);
672
781
 
782
+ await extractEdgeZip(archive, cacheDir, driverPath);
783
+ }
673
784
  return driverPath;
674
785
  }
675
786
 
@@ -678,8 +789,8 @@ class Edge extends Browser {
678
789
  headless = true, extensionPaths = [], incognito = false, insecure = false,
679
790
  extraArgs = []
680
791
  } = {}) {
681
- if (process.platform == "linux" || process.platform == "darwin")
682
- await Edge.downloadBinary(version);
792
+ if (platform == "linux" || platform == "darwin")
793
+ await Edge.installBrowser(version);
683
794
 
684
795
  let driverPath = await Edge.#installDriver();
685
796
  let serviceBuilder = new edge.ServiceBuilder(driverPath);
@@ -718,257 +829,22 @@ class Edge extends Browser {
718
829
  await button.click();
719
830
  return await driver.findElement(By.id("itemAllowIncognito")).click();
720
831
  }
721
- throw new Error("Details button not found");
722
- }
723
- throw new Error(`Extension "${extensionTitle}" not found`);
724
- }
725
- }
726
-
727
- /**
728
- * Download functionality for Opera. This class can be used statically.
729
- * @hideconstructor
730
- * @extends Browser
731
- */
732
- class Opera extends Browser {
733
- static async #getVersionForChannel(version, platformDir) {
734
- let channelPath = "opera/desktop";
735
- let filePrefix = "Opera";
736
- if (version != "latest")
737
- return {versionNumber: version, channelPath, filePrefix};
738
-
739
- let {body} = await got(`https://ftp.opera.com/pub/${channelPath}`);
740
- let regex = /href="(\d.*)\/"/gm;
741
- let matches = body.match(regex);
742
- let versionNumber;
743
- while (matches.length > 0) {
744
- let result = regex.exec(matches.pop());
745
- if (!result)
746
- continue;
747
-
748
- versionNumber = result[1];
749
- try {
750
- await got(`https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}`);
751
- break;
752
- }
753
- catch (e) {}
754
- }
755
-
756
- return {versionNumber, channelPath, filePrefix};
757
- }
758
-
759
- static #getBinaryPath(dir) {
760
- switch (process.platform) {
761
- case "win32":
762
- return path.join(dir, "launcher.exe");
763
- case "linux":
764
- return path.join("/", "usr", "bin", "opera");
765
- case "darwin":
766
- return path.join(dir, "Opera.app", "Contents", "MacOS", "Opera");
767
- default:
768
- throw new Error(`Unexpected platform: ${process.platform}`);
769
- }
770
- }
771
-
772
- static async #extractDeb(archive) {
773
- let child = spawn("dpkg", ["-i", archive]);
774
-
775
- child.stdout.on("data", data => {
776
- if (data.toString().includes("Do you want to update Opera")) {
777
- process.stdin.pipe(child.stdin);
778
- child.stdin.write("no\r\n");
779
- }
780
- });
781
-
782
- child.stderr.on("data", data => {
783
- let expectedWarnings = [
784
- "debconf: unable to initialize frontend",
785
- "dpkg: warning: downgrading opera-stable",
786
- "update-alternatives",
787
- "using /usr/bin/opera",
788
- "skip creation of",
789
- "\r\n"
790
- ];
791
- if (!expectedWarnings.find(err => data.toString().includes(err.trim())))
792
- console.error(`stderr: ${data.toString()}`);
793
- });
794
-
795
- await new Promise((resolve, reject) => child.on("close", code => {
796
- if (code != 0)
797
- reject(`dpkg process exited with code ${code}`);
798
-
799
- resolve();
800
- }));
801
- }
802
-
803
- static async #installOnWindows(archive, dir, filename) {
804
- let archiveCopy = path.join(dir, filename);
805
- await fsExtra.copy(archive, archiveCopy);
806
- await promisify(exec)(`"${archiveCopy}"`);
807
- }
808
-
809
- static #extractOperaArchive(archive, dir, filename) {
810
- switch (process.platform) {
811
- case "win32":
812
- return Opera.#installOnWindows(archive, dir, filename);
813
- case "linux":
814
- return Opera.#extractDeb(archive, dir);
815
- case "darwin":
816
- return extractTar(archive, dir);
817
- default:
818
- throw new Error(`Unexpected platform: ${process.platform}`);
819
- }
820
- }
821
-
822
- /**
823
- * Downloads the browser binary file.
824
- * @param {string} version - Either "latest" or a full version number
825
- * (i.e. "64.0.3417.92"). Defaults to "latest".
826
- * @return {BrowserBinary}
827
- */
828
- static async downloadBinary(version = "latest") {
829
- const MIN_VERSION = 62;
830
- const CHANNELS = ["latest"];
831
-
832
- checkVersion(version, MIN_VERSION, CHANNELS);
833
-
834
- let [platformDir, fileSuffix] = {
835
- "win32-ia32": ["win", "Autoupdate.exe"],
836
- "win32-x64": ["win", "Autoupdate_x64.exe"],
837
- "linux-x64": ["linux", "amd64.deb"],
838
- "darwin-x64": ["mac", "Autoupdate.tar.xz"],
839
- "dawrin-arm64": ["mac", "Autoupdate_arm64.tar.xz"]
840
- }[platform];
841
-
842
- let {versionNumber, channelPath, filePrefix} =
843
- await Opera.#getVersionForChannel(version, platformDir);
844
-
845
- let snapshotsDir = path.join(snapshotsBaseDir, "opera");
846
- let browserDir = path.join(snapshotsDir, `opera-${platform}-${versionNumber}`);
847
- let filename = `${filePrefix}_${versionNumber}_${fileSuffix}`;
848
- let archive = path.join(snapshotsDir, "cache", filename);
849
-
850
- let binary = Opera.#getBinaryPath(browserDir);
851
- try {
852
- if (process.platform == "linux" &&
853
- await Opera.getInstalledVersion("opera") == versionNumber)
854
- return {binary, versionNumber};
855
-
856
- await fs.promises.access(browserDir);
857
- return {binary, versionNumber};
858
- }
859
- catch (e) {}
860
-
861
- await fs.promises.mkdir(path.dirname(browserDir), {recursive: true});
862
- try {
863
- await fs.promises.access(archive);
864
- }
865
- catch (e) {
866
- let url = `https://ftp.opera.com/pub/${channelPath}/${versionNumber}/${platformDir}/${filename}`;
867
- try {
868
- await download(url, archive);
869
- }
870
- catch (err) {
871
- throw new Error(`Browser download unavailable at ${url}\n${err}`);
872
- }
873
- }
874
-
875
- await Opera.#extractOperaArchive(archive, browserDir, filename);
876
- return {binary, versionNumber};
877
- }
878
-
879
- static async #installDriver(version, originalVersion) {
880
- let [zip, driver] = {
881
- "win32-ia32": ["operadriver_win32.zip", "operadriver.exe"],
882
- "win32-x64": ["operadriver_win64.zip", "operadriver.exe"],
883
- "linux-x64": ["operadriver_linux64.zip", "operadriver"],
884
- "darwin-x64": ["operadriver_mac64.zip", "operadriver"],
885
- "darwin-arm64": ["operadriver_mac64.zip", "operadriver"]
886
- }[platform];
887
-
888
- let {versionNumber} = await Opera.#getVersionForChannel(version);
889
- versionNumber = versionNumber.split(".")[0];
890
-
891
- let cacheDir = path.join(snapshotsBaseDir, "opera", "cache",
892
- `operadriver-${versionNumber}`);
893
- let archive = path.join(cacheDir, zip);
894
-
895
- let {body} = await got(`https://github.com/operasoftware/operachromiumdriver/releases?q=Opera+${versionNumber}&expanded=true`);
896
- let regex = /release-card[\s\S]*Link--primary.*>(.*)<\/a/gm;
897
- let matches = body.match(regex);
898
- if (!matches || matches.length == 0)
899
- throw new Error(`Driver for Opera ${version} was not found`);
900
- let driverVersion = regex.exec(matches[matches.length - 1])[1];
901
-
902
- try {
903
- await download(`https://github.com/operasoftware/operachromiumdriver/releases/download/v.${driverVersion}/${zip}`,
904
- archive);
832
+ throw new Error(`${ELEMENT_NOT_FOUND_ERROR}: Details button`);
905
833
  }
906
- catch (err) {
907
- throw new Error(`Downloading operadriver failed: ${err}`);
908
- }
909
-
910
- await killDriverProcess("operadriver");
911
- let driverPath = path.join(cacheDir, zip.split(".")[0], driver);
912
- try {
913
- await fs.promises.rm(driverPath, {recursive: true});
914
- }
915
- catch (e) {} // file does not exist
916
- await extractZip(archive, {dir: cacheDir});
917
- await fs.promises.chmod(driverPath, 577);
918
-
919
- return driverPath;
920
- }
921
-
922
- /** @see Browser.getDriver */
923
- static async getDriver(version = "latest", {
924
- headless = true, extensionPaths = [], incognito = false, insecure = false,
925
- extraArgs = []
926
- } = {}) {
927
- let {binary, versionNumber} = await Opera.downloadBinary(version);
928
- let driverPath = await Opera.#installDriver(versionNumber, version);
929
- // operadriver uses chrome as a service builder:
930
- // https://github.com/operasoftware/operachromiumdriver/blob/master/docs/desktop.md
931
- let serviceBuilder = new chrome.ServiceBuilder(driverPath);
932
- let service = serviceBuilder.build();
933
- await service.start();
934
-
935
- let options = new chrome.Options().addArguments("no-sandbox", ...extraArgs);
936
- if (extensionPaths.length > 0)
937
- options.addArguments(`load-extension=${extensionPaths.join(",")}`);
938
- if (headless)
939
- options.headless();
940
- if (insecure)
941
- options.addArguments("ignore-certificate-errors");
942
- if (incognito)
943
- options.addArguments("incognito");
944
- options.setChromeBinaryPath(binary);
945
- // https://github.com/operasoftware/operachromiumdriver/issues/61#issuecomment-579331657
946
- options.addArguments("remote-debugging-port=9222");
947
-
948
- let builder = new webdriver.Builder();
949
- builder.forBrowser("chrome");
950
- builder.setChromeOptions(options);
951
- builder.setChromeService(serviceBuilder);
952
-
953
- return builder.build();
954
- }
955
-
956
- /** @see Browser.enableExtensionInIncognito */
957
- static async enableExtensionInIncognito(driver, extensionTitle) {
958
- // Extensions page in Opera has the same web elements as Chromium
959
- await Chromium.enableExtensionInIncognito(driver, extensionTitle);
834
+ throw new Error(`${EXTENSION_NOT_FOUND_ERROR}: ${extensionTitle}`);
960
835
  }
961
836
  }
962
837
 
963
838
  /**
964
839
  * @type {Object}
965
- * @property {Chromium} chromium - Download functionality for Chromium.
966
- * @property {Firefox} firefox - Download functionality for Firefox.
967
- * @property {Edge} edge - Download functionality for Edge.
840
+ * @property {Chromium} chromium - Browser and webdriver functionality for
841
+ * Chromium.
842
+ * @property {Firefox} firefox - Browser and webdriver functionality for
843
+ * Firefox.
844
+ * @property {Edge} edge - Browser and webdriver functionality for Edge.
968
845
  */
969
846
  export const BROWSERS = {
970
847
  chromium: Chromium,
971
848
  firefox: Firefox,
972
- edge: Edge,
973
- opera: Opera
849
+ edge: Edge
974
850
  };