selenium-webdriver 4.48.0 → 4.50.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/CHANGES.md +26 -0
- package/bidi/generated/bluetooth.d.ts +199 -37
- package/bidi/generated/bluetooth.js +212 -106
- package/bidi/generated/browser.d.ts +81 -28
- package/bidi/generated/browser.js +97 -69
- package/bidi/generated/browsing_context.d.ts +274 -87
- package/bidi/generated/browsing_context.js +328 -241
- package/bidi/generated/common.d.ts +105 -21
- package/bidi/generated/common.js +64 -15
- package/bidi/generated/digital_credentials.d.ts +41 -0
- package/bidi/generated/digital_credentials.js +54 -0
- package/bidi/generated/emulation.d.ts +354 -58
- package/bidi/generated/emulation.js +262 -101
- package/bidi/generated/input.d.ts +63 -35
- package/bidi/generated/input.js +77 -51
- package/bidi/generated/log.d.ts +39 -18
- package/bidi/generated/log.js +23 -43
- package/bidi/generated/network.d.ts +248 -76
- package/bidi/generated/network.js +247 -181
- package/bidi/generated/permissions.d.ts +22 -4
- package/bidi/generated/permissions.js +31 -17
- package/bidi/generated/script.d.ts +166 -70
- package/bidi/generated/script.js +203 -116
- package/bidi/generated/session.d.ts +76 -36
- package/bidi/generated/session.js +81 -48
- package/bidi/generated/speculation.d.ts +15 -5
- package/bidi/generated/speculation.js +20 -16
- package/bidi/generated/storage.d.ts +41 -17
- package/bidi/generated/storage.js +58 -40
- package/bidi/generated/user_agent_client_hints.d.ts +25 -13
- package/bidi/generated/user_agent_client_hints.js +29 -18
- package/bidi/generated/webextension.d.ts +24 -10
- package/bidi/generated/webextension.js +39 -25
- package/bidi/serialization/enum.js +6 -3
- package/bidi/serialization/record.js +34 -29
- package/bin/linux-arm64/selenium-manager +0 -0
- package/bin/{linux → linux-x86_64}/selenium-manager +0 -0
- package/bin/macos/selenium-manager +0 -0
- package/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +327 -51
- package/bin/selenium-manager.cdx.json +491 -491
- package/bin/windows/selenium-manager.exe +0 -0
- package/chromium.js +1 -1
- package/common/seleniumManager.js +2 -2
- package/eslint.config.js +2 -1
- package/example/chrome_mobile_emulation.js +1 -1
- package/firefox.js +7 -104
- package/index.js +8 -0
- package/lib/atoms/find-elements.js +14 -2
- package/package.json +10 -10
- package/remote/index.js +12 -3
- package/remote/util.js +5 -5
- package/testing/index.js +84 -1
|
Binary file
|
package/chromium.js
CHANGED
|
@@ -506,7 +506,7 @@ class Options extends Capabilities {
|
|
|
506
506
|
* __Example 1: Using a Pre-configured Device__
|
|
507
507
|
*
|
|
508
508
|
* let options = new chrome.Options().setMobileEmulation(
|
|
509
|
-
* {deviceName: '
|
|
509
|
+
* {deviceName: 'Pixel 10'});
|
|
510
510
|
*
|
|
511
511
|
* let driver = chrome.Driver.createSession(options);
|
|
512
512
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Wrapper for getting information from the Selenium Manager binaries
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
const { platform } = require('node:process')
|
|
24
|
+
const { platform, arch } = require('node:process')
|
|
25
25
|
const path = require('node:path')
|
|
26
26
|
const fs = require('node:fs')
|
|
27
27
|
const spawnSync = require('node:child_process').spawnSync
|
|
@@ -39,7 +39,7 @@ function getBinary() {
|
|
|
39
39
|
darwin: 'macos',
|
|
40
40
|
win32: 'windows',
|
|
41
41
|
cygwin: 'windows',
|
|
42
|
-
linux: 'linux',
|
|
42
|
+
linux: arch === 'arm64' ? 'linux-arm64' : 'linux-x86_64',
|
|
43
43
|
}[platform]
|
|
44
44
|
|
|
45
45
|
const file = directory === 'windows' ? 'selenium-manager.exe' : 'selenium-manager'
|
package/eslint.config.js
CHANGED
|
@@ -20,7 +20,8 @@ const noOnlyTests = require('eslint-plugin-no-only-tests')
|
|
|
20
20
|
const js = require('@eslint/js')
|
|
21
21
|
const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended')
|
|
22
22
|
const mochaPlugin = require('eslint-plugin-mocha').default
|
|
23
|
-
|
|
23
|
+
// eslint-plugin-n 18 is an ES module; require() returns its 'module.exports' export, which has no .default.
|
|
24
|
+
const nodePlugin = require('eslint-plugin-n')
|
|
24
25
|
|
|
25
26
|
module.exports = [
|
|
26
27
|
js.configs.recommended,
|
|
@@ -30,7 +30,7 @@ const { Options } = require('../chrome')
|
|
|
30
30
|
try {
|
|
31
31
|
driver = await new Builder()
|
|
32
32
|
.forBrowser('chrome')
|
|
33
|
-
.setChromeOptions(new Options().setMobileEmulation({ deviceName: '
|
|
33
|
+
.setChromeOptions(new Options().setMobileEmulation({ deviceName: 'Pixel 10' }))
|
|
34
34
|
.build()
|
|
35
35
|
await driver.get('http://www.google.com/ncr')
|
|
36
36
|
await driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN)
|
package/firefox.js
CHANGED
|
@@ -27,15 +27,13 @@
|
|
|
27
27
|
* __Customizing the Firefox Profile__
|
|
28
28
|
*
|
|
29
29
|
* The profile used for each WebDriver session may be configured using the
|
|
30
|
-
* {@linkplain Options} class. For example, you may
|
|
31
|
-
* Firebug:
|
|
30
|
+
* {@linkplain Options} class. For example, you may set a preference:
|
|
32
31
|
*
|
|
33
32
|
* const {Builder} = require('selenium-webdriver');
|
|
34
33
|
* const firefox = require('selenium-webdriver/firefox');
|
|
35
34
|
*
|
|
36
35
|
* let options = new firefox.Options()
|
|
37
|
-
* .
|
|
38
|
-
* .setPreference('extensions.firebug.showChromeErrors', true);
|
|
36
|
+
* .setPreference('browser.download.folderList', 2);
|
|
39
37
|
*
|
|
40
38
|
* let driver = new Builder()
|
|
41
39
|
* .forBrowser('firefox')
|
|
@@ -117,81 +115,16 @@ const http = require('./http')
|
|
|
117
115
|
const io = require('./io')
|
|
118
116
|
const remote = require('./remote')
|
|
119
117
|
const webdriver = require('./lib/webdriver')
|
|
120
|
-
const zip = require('./io/zip')
|
|
121
118
|
const { Browser, Capabilities, Capability } = require('./lib/capabilities')
|
|
122
119
|
const { Zip } = require('./io/zip')
|
|
123
120
|
const { getBinaryPaths } = require('./common/driverFinder')
|
|
124
121
|
const { findFreePort } = require('./net/portprober')
|
|
125
122
|
const FIREFOX_CAPABILITY_KEY = 'moz:firefoxOptions'
|
|
126
123
|
|
|
127
|
-
/**
|
|
128
|
-
* Thrown when there an add-on is malformed.
|
|
129
|
-
* @final
|
|
130
|
-
*/
|
|
131
|
-
class AddonFormatError extends Error {
|
|
132
|
-
/** @param {string} msg The error message. */
|
|
133
|
-
constructor(msg) {
|
|
134
|
-
super(msg)
|
|
135
|
-
/** @override */
|
|
136
|
-
this.name = this.constructor.name
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Installs an extension to the given directory.
|
|
142
|
-
* @param {string} extension Path to the xpi extension file to install.
|
|
143
|
-
* @param {string} dir Path to the directory to install the extension in.
|
|
144
|
-
* @return {!Promise<string>} A promise for the add-on ID once
|
|
145
|
-
* installed.
|
|
146
|
-
*/
|
|
147
|
-
async function installExtension(extension, dir) {
|
|
148
|
-
const ext = extension.slice(-4)
|
|
149
|
-
if (ext !== '.xpi' && ext !== '.zip') {
|
|
150
|
-
throw Error('File name does not end in ".zip" or ".xpi": ' + ext)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
let archive = await zip.load(extension)
|
|
154
|
-
if (!archive.has('manifest.json')) {
|
|
155
|
-
throw new AddonFormatError(`Couldn't find manifest.json in ${extension}`)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let buf = await archive.getFile('manifest.json')
|
|
159
|
-
let parsedJSON = JSON.parse(buf.toString('utf8'))
|
|
160
|
-
|
|
161
|
-
let { browser_specific_settings } =
|
|
162
|
-
/** @type {{browser_specific_settings:{gecko:{id:string}}}} */
|
|
163
|
-
parsedJSON
|
|
164
|
-
|
|
165
|
-
if (browser_specific_settings && browser_specific_settings.gecko) {
|
|
166
|
-
/* browser_specific_settings is an alternative to applications
|
|
167
|
-
* It is meant to facilitate cross-browser plugins since Firefox48
|
|
168
|
-
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1262005
|
|
169
|
-
*/
|
|
170
|
-
parsedJSON.applications = browser_specific_settings
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let { applications } =
|
|
174
|
-
/** @type {{applications:{gecko:{id:string}}}} */
|
|
175
|
-
parsedJSON
|
|
176
|
-
if (!(applications && applications.gecko && applications.gecko.id)) {
|
|
177
|
-
throw new AddonFormatError(`Could not find add-on ID for ${extension}`)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
await io.copy(extension, `${path.join(dir, applications.gecko.id)}.xpi`)
|
|
181
|
-
return applications.gecko.id
|
|
182
|
-
}
|
|
183
|
-
|
|
184
124
|
class Profile {
|
|
185
125
|
constructor() {
|
|
186
126
|
/** @private {?string} */
|
|
187
127
|
this.template_ = null
|
|
188
|
-
|
|
189
|
-
/** @private {!Array<string>} */
|
|
190
|
-
this.extensions_ = []
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
addExtensions(/** !Array<string> */ paths) {
|
|
194
|
-
this.extensions_ = this.extensions_.concat(...paths)
|
|
195
128
|
}
|
|
196
129
|
|
|
197
130
|
/**
|
|
@@ -199,39 +132,21 @@ class Profile {
|
|
|
199
132
|
* profile, or undefined if there's no data to include.
|
|
200
133
|
*/
|
|
201
134
|
[Symbols.serialize]() {
|
|
202
|
-
if (this.template_
|
|
203
|
-
return buildProfile(this.template_
|
|
135
|
+
if (this.template_) {
|
|
136
|
+
return buildProfile(this.template_)
|
|
204
137
|
}
|
|
205
138
|
return undefined
|
|
206
139
|
}
|
|
207
140
|
}
|
|
208
141
|
|
|
209
142
|
/**
|
|
210
|
-
* @param {
|
|
211
|
-
* @param {!Array<string>} extensions paths to extensions to install in the new
|
|
212
|
-
* profile.
|
|
143
|
+
* @param {string} template path to an existing profile to use as a template.
|
|
213
144
|
* @return {!Promise<string>} a promise for the base64 encoded profile.
|
|
214
145
|
*/
|
|
215
|
-
async function buildProfile(template
|
|
216
|
-
let dir = template
|
|
217
|
-
|
|
218
|
-
if (extensions.length) {
|
|
219
|
-
dir = await io.tmpDir()
|
|
220
|
-
if (template) {
|
|
221
|
-
await io.copyDir(/** @type {string} */ (template), dir, /(parent\.lock|lock|\.parentlock)/)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const extensionsDir = path.join(dir, 'extensions')
|
|
225
|
-
await io.mkdir(extensionsDir)
|
|
226
|
-
|
|
227
|
-
for (let i = 0; i < extensions.length; i++) {
|
|
228
|
-
await installExtension(extensions[i], extensionsDir)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
146
|
+
async function buildProfile(template) {
|
|
232
147
|
let zip = new Zip()
|
|
233
148
|
return zip
|
|
234
|
-
.addDir(
|
|
149
|
+
.addDir(template)
|
|
235
150
|
.then(() => zip.toBuffer())
|
|
236
151
|
.then((buf) => buf.toString('base64'))
|
|
237
152
|
}
|
|
@@ -312,18 +227,6 @@ class Options extends Capabilities {
|
|
|
312
227
|
return this.addArguments(`--width=${width}`, `--height=${height}`)
|
|
313
228
|
}
|
|
314
229
|
|
|
315
|
-
/**
|
|
316
|
-
* Add extensions that should be installed when starting Firefox.
|
|
317
|
-
*
|
|
318
|
-
* @param {...string} paths The paths to the extension XPI files to install.
|
|
319
|
-
* @return {!Options} A self reference.
|
|
320
|
-
* @deprecated Use {@link Driver#installAddon} instead.
|
|
321
|
-
*/
|
|
322
|
-
addExtensions(...paths) {
|
|
323
|
-
this.profile_().addExtensions(paths)
|
|
324
|
-
return this
|
|
325
|
-
}
|
|
326
|
-
|
|
327
230
|
/**
|
|
328
231
|
* @param {string} key the preference key.
|
|
329
232
|
* @param {(string|number|boolean)} value the preference value.
|
package/index.js
CHANGED
|
@@ -540,6 +540,14 @@ class Builder {
|
|
|
540
540
|
return this
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
+
/**
|
|
544
|
+
* @return {edge.Options} the Edge specific options currently configured
|
|
545
|
+
* for this builder.
|
|
546
|
+
*/
|
|
547
|
+
getEdgeOptions() {
|
|
548
|
+
return this.edgeOptions_
|
|
549
|
+
}
|
|
550
|
+
|
|
543
551
|
/**
|
|
544
552
|
* Sets the {@link edge.ServiceBuilder} to use to manage the
|
|
545
553
|
* MicrosoftEdgeDriver child process when creating sessions locally.
|
|
@@ -83,7 +83,16 @@ module.exports = (function findElements(target, root) {
|
|
|
83
83
|
if (target === '') {
|
|
84
84
|
throw botError(INVALID_SELECTOR, 'Unable to locate an element with the tagName ""');
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
if ('getElementsByTagName' in root) {
|
|
87
|
+
return Array.from(root.getElementsByTagName(target));
|
|
88
|
+
}
|
|
89
|
+
const elements = Array.from(root.querySelectorAll('*'));
|
|
90
|
+
if (target === '*') {
|
|
91
|
+
return elements;
|
|
92
|
+
}
|
|
93
|
+
const html = 'http://www.w3.org/1999/xhtml';
|
|
94
|
+
const folded = target.toLowerCase();
|
|
95
|
+
return elements.filter(el => el.namespaceURI === html ? el.localName === folded : el.localName === target);
|
|
87
96
|
}
|
|
88
97
|
const DEFAULT_NS_RESOLVER = (function () {
|
|
89
98
|
const ns = { svg: 'http://www.w3.org/2000/svg' };
|
|
@@ -166,7 +175,10 @@ module.exports = (function findElements(target, root) {
|
|
|
166
175
|
return resolveAnchor(selector());
|
|
167
176
|
}
|
|
168
177
|
if (selector && typeof selector === 'object') {
|
|
169
|
-
|
|
178
|
+
let found = root ? findElements(selector, root) : [];
|
|
179
|
+
if (!found.length) {
|
|
180
|
+
found = findElements(selector);
|
|
181
|
+
}
|
|
170
182
|
if (!found.length) {
|
|
171
183
|
throw botError(NO_SUCH_ELEMENT, 'No element has been found by ' + JSON.stringify(selector));
|
|
172
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selenium-webdriver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.50.0",
|
|
4
4
|
"description": "The official WebDriver JavaScript bindings from the Selenium project",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -24,29 +24,29 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@bazel/runfiles": "^6.5.0",
|
|
27
|
-
"jszip": "^3.10.
|
|
27
|
+
"jszip": "^3.10.2",
|
|
28
28
|
"tmp": "^0.2.7",
|
|
29
|
-
"ws": "^8.
|
|
29
|
+
"ws": "^8.22.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@eslint/js": "^10.0.1",
|
|
33
|
-
"cddl": "^0.21.
|
|
34
|
-
"cddl2ts": "^0.10.
|
|
33
|
+
"cddl": "^0.21.2",
|
|
34
|
+
"cddl2ts": "^0.10.2",
|
|
35
35
|
"clean-jsdoc-theme": "^4.3.3",
|
|
36
|
-
"eslint": "^10.
|
|
36
|
+
"eslint": "^10.11.0",
|
|
37
37
|
"eslint-config-prettier": "^10.1.8",
|
|
38
38
|
"eslint-plugin-mocha": "^11.3.0",
|
|
39
|
-
"eslint-plugin-n": "^18.
|
|
39
|
+
"eslint-plugin-n": "^18.4.0",
|
|
40
40
|
"eslint-plugin-no-only-tests": "^3.4.0",
|
|
41
41
|
"eslint-plugin-prettier": "^5.5.6",
|
|
42
42
|
"express": "^5.2.1",
|
|
43
|
-
"globals": "^17.
|
|
43
|
+
"globals": "^17.12.0",
|
|
44
44
|
"has-flag": "^5.0.1",
|
|
45
45
|
"jsdoc": "^4.0.5",
|
|
46
46
|
"mocha": "^11.8.0",
|
|
47
47
|
"mocha-junit-reporter": "^2.2.1",
|
|
48
|
-
"multer": "^2.
|
|
49
|
-
"prettier": "^3.9.
|
|
48
|
+
"multer": "^2.4.0",
|
|
49
|
+
"prettier": "^3.9.9",
|
|
50
50
|
"serve-index": "^1.9.2",
|
|
51
51
|
"sinon": "^22.1.0",
|
|
52
52
|
"supports-color": "^10.2.2"
|
package/remote/index.js
CHANGED
|
@@ -471,6 +471,8 @@ class SeleniumServer extends DriverService {
|
|
|
471
471
|
throw Error('Port must be >= 0: ' + options.port)
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
const java = options.java || getJavaPath()
|
|
475
|
+
|
|
474
476
|
let port = options.port || portprober.findFreePort()
|
|
475
477
|
let args = Promise.all([port, options.jvmArgs || [], options.args || []]).then((resolved) => {
|
|
476
478
|
let port = resolved[0]
|
|
@@ -479,11 +481,9 @@ class SeleniumServer extends DriverService {
|
|
|
479
481
|
|
|
480
482
|
const fullArgsList = jvmArgs.concat('-jar', jar, '-port', port).concat(args)
|
|
481
483
|
|
|
482
|
-
return formatSpawnArgs(jar, fullArgsList)
|
|
484
|
+
return formatSpawnArgs(jar, fullArgsList, java)
|
|
483
485
|
})
|
|
484
486
|
|
|
485
|
-
const java = getJavaPath()
|
|
486
|
-
|
|
487
487
|
super(java, {
|
|
488
488
|
loopback: options.loopback,
|
|
489
489
|
port: port,
|
|
@@ -536,6 +536,15 @@ SeleniumServer.Options = class {
|
|
|
536
536
|
*/
|
|
537
537
|
this.jvmArgs
|
|
538
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Path to the java executable that runs the server. Defaults to
|
|
541
|
+
* `$JAVA_HOME/bin/java` when `JAVA_HOME` is set, otherwise `java` on the
|
|
542
|
+
* `PATH`.
|
|
543
|
+
*
|
|
544
|
+
* @type {(string|undefined)}
|
|
545
|
+
*/
|
|
546
|
+
this.java
|
|
547
|
+
|
|
539
548
|
/**
|
|
540
549
|
* The environment variables that should be visible to the server
|
|
541
550
|
* process. Defaults to inheriting the current process's environment.
|
package/remote/util.js
CHANGED
|
@@ -31,11 +31,10 @@ function getJavaPath() {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* @param {string} seleniumStandalonePath path to standalone server
|
|
34
|
+
* @param {string=} javaPath the java executable; defaults to {@link getJavaPath}
|
|
34
35
|
* @returns {boolean}
|
|
35
36
|
*/
|
|
36
|
-
function isSelenium3x(seleniumStandalonePath) {
|
|
37
|
-
const javaPath = getJavaPath()
|
|
38
|
-
|
|
37
|
+
function isSelenium3x(seleniumStandalonePath, javaPath = getJavaPath()) {
|
|
39
38
|
const execRes = cp.execFileSync(javaPath, ['-jar', seleniumStandalonePath, '--version'])
|
|
40
39
|
|
|
41
40
|
return execRes.toString().trim().startsWith('Selenium server version: 3')
|
|
@@ -44,11 +43,12 @@ function isSelenium3x(seleniumStandalonePath) {
|
|
|
44
43
|
/**
|
|
45
44
|
* @param {string} seleniumStandalonePath path to standalone server
|
|
46
45
|
* @param {Array.<string>} args spawn arguments array
|
|
46
|
+
* @param {string=} javaPath the java executable; defaults to {@link getJavaPath}
|
|
47
47
|
* returns formatted args based on selenium standalone server version
|
|
48
48
|
* @returns {Array.<string>}
|
|
49
49
|
*/
|
|
50
|
-
function formatSpawnArgs(seleniumStandalonePath, args) {
|
|
51
|
-
if (isSelenium3x(seleniumStandalonePath)) {
|
|
50
|
+
function formatSpawnArgs(seleniumStandalonePath, args, javaPath = getJavaPath()) {
|
|
51
|
+
if (isSelenium3x(seleniumStandalonePath, javaPath)) {
|
|
52
52
|
logging
|
|
53
53
|
.getLogger(logging.Type.SERVER)
|
|
54
54
|
.warning('Deprecation: Support for Standalone Server 3.x will be removed soon. Please update to version 4.x')
|
package/testing/index.js
CHANGED
|
@@ -218,6 +218,7 @@ function init(force = false) {
|
|
|
218
218
|
seleniumJar = process.env['SELENIUM_SERVER_JAR']
|
|
219
219
|
seleniumUrl = process.env['SELENIUM_REMOTE_URL']
|
|
220
220
|
if (seleniumJar) {
|
|
221
|
+
seleniumJar = locate(seleniumJar)
|
|
221
222
|
info(`Using Selenium server jar: ${seleniumJar}`)
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -289,6 +290,19 @@ class Environment {
|
|
|
289
290
|
return () => browsersToIgnore.indexOf(this.browser.name) !== -1
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Returns a predicate function that will suppress tests in this environment
|
|
295
|
+
* when it runs them through a Selenium server (such as a Grid) rather than a
|
|
296
|
+
* browser driver started locally.
|
|
297
|
+
*
|
|
298
|
+
* ignore(env.remote()).it('reads a browser preference', ...)
|
|
299
|
+
*
|
|
300
|
+
* @return {function(): boolean} a new predicate function.
|
|
301
|
+
*/
|
|
302
|
+
remote() {
|
|
303
|
+
return () => URL_MAP.get(this) !== null
|
|
304
|
+
}
|
|
305
|
+
|
|
292
306
|
/**
|
|
293
307
|
* @return {!Builder} a new WebDriver builder configured to target this
|
|
294
308
|
* environment's {@linkplain #browser browser}.
|
|
@@ -315,6 +329,19 @@ class Environment {
|
|
|
315
329
|
builder.setChromeOptions(options)
|
|
316
330
|
}
|
|
317
331
|
// Edge
|
|
332
|
+
if ('SE_EDGEDRIVER' in process.env) {
|
|
333
|
+
const found = locate(process.env.SE_EDGEDRIVER)
|
|
334
|
+
const service = new edge.ServiceBuilder(found)
|
|
335
|
+
builder.setEdgeService(service)
|
|
336
|
+
}
|
|
337
|
+
if ('SE_EDGE' in process.env) {
|
|
338
|
+
const binary = locate(process.env.SE_EDGE)
|
|
339
|
+
const options = new edge.Options()
|
|
340
|
+
options.setBinaryPath(binary)
|
|
341
|
+
options.setAcceptInsecureCerts(true)
|
|
342
|
+
options.addArguments('disable-infobars', 'disable-breakpad', 'disable-dev-shm-usage', 'no-sandbox')
|
|
343
|
+
builder.setEdgeOptions(options)
|
|
344
|
+
}
|
|
318
345
|
// Firefox
|
|
319
346
|
if ('SE_GECKODRIVER' in process.env) {
|
|
320
347
|
const found = locate(process.env.SE_GECKODRIVER)
|
|
@@ -444,7 +471,11 @@ function suite(fn, options = undefined) {
|
|
|
444
471
|
|
|
445
472
|
describe(`[${browser.name}]`, function () {
|
|
446
473
|
if (!seleniumUrl && seleniumJar && !seleniumServer) {
|
|
447
|
-
seleniumServer = new remote.SeleniumServer(seleniumJar
|
|
474
|
+
seleniumServer = new remote.SeleniumServer(seleniumJar, {
|
|
475
|
+
java: bazelJava(),
|
|
476
|
+
loopback: true,
|
|
477
|
+
args: ['--host', '127.0.0.1', ...pinnedGridArgs(targetBrowsers.map((b) => b.name))],
|
|
478
|
+
})
|
|
448
479
|
|
|
449
480
|
const startTimeout = 65 * 1000
|
|
450
481
|
|
|
@@ -547,6 +578,58 @@ function getTestHook(name) {
|
|
|
547
578
|
return fn
|
|
548
579
|
}
|
|
549
580
|
|
|
581
|
+
/**
|
|
582
|
+
* Bazel's hermetic java, from `SE_BAZEL_JAVA_LOCATION` (a file holding the `$(JAVA)` path).
|
|
583
|
+
* @return {(string|undefined)} the java executable, or undefined to use the server's default.
|
|
584
|
+
*/
|
|
585
|
+
function bazelJava() {
|
|
586
|
+
const javaLocation = process.env['SE_BAZEL_JAVA_LOCATION']
|
|
587
|
+
if (!javaLocation) {
|
|
588
|
+
return undefined
|
|
589
|
+
}
|
|
590
|
+
// $(JAVA) is an exec path (external/<repo>/...); without the prefix it is a runfiles path.
|
|
591
|
+
const execPath = fs.readFileSync(locate(javaLocation), { encoding: 'utf8' }).trim()
|
|
592
|
+
const java = locate(execPath.replace(/^external\//, ''))
|
|
593
|
+
// Resolve the JDK symlink to its real path to dodge a Windows JVM bug mapping lib\modules.
|
|
594
|
+
return process.platform === 'win32' ? fs.realpathSync(java) : java
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Browser name -> [driver env var, browser env var, vendor options key], for pinned browsers. */
|
|
598
|
+
const PINNED_BROWSERS = {
|
|
599
|
+
[Browser.CHROME]: ['SE_CHROMEDRIVER', 'SE_CHROME', 'goog:chromeOptions'],
|
|
600
|
+
[Browser.FIREFOX]: ['SE_GECKODRIVER', 'SE_FIREFOX', 'moz:firefoxOptions'],
|
|
601
|
+
[Browser.EDGE]: ['SE_EDGEDRIVER', 'SE_EDGE', 'ms:edgeOptions'],
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Pins the Grid node to the Bazel-provided driver and browser; on CI, Selenium Manager would find
|
|
606
|
+
* no installed browser.
|
|
607
|
+
* @param {!Array<string>} browserNames the browsers sharing the server.
|
|
608
|
+
* @return {!Array<string>} empty to keep driver detection.
|
|
609
|
+
*/
|
|
610
|
+
function pinnedGridArgs(browserNames) {
|
|
611
|
+
// The command line merges repeated --driver-configuration flags into one, so only a single
|
|
612
|
+
// browser can be pinned; with several, detection must stay on to give each one a slot.
|
|
613
|
+
if (browserNames.length !== 1) {
|
|
614
|
+
return []
|
|
615
|
+
}
|
|
616
|
+
const [browserName] = browserNames
|
|
617
|
+
const [driverVar, browserVar, vendorKey] = PINNED_BROWSERS[browserName] ?? []
|
|
618
|
+
if (!process.env[driverVar] || !process.env[browserVar]) {
|
|
619
|
+
return []
|
|
620
|
+
}
|
|
621
|
+
const stereotype = JSON.stringify({ browserName, [vendorKey]: { binary: locate(process.env[browserVar]) } })
|
|
622
|
+
return [
|
|
623
|
+
'--detect-drivers',
|
|
624
|
+
'false',
|
|
625
|
+
'--driver-configuration',
|
|
626
|
+
`display-name=${browserName}`,
|
|
627
|
+
'max-sessions=1',
|
|
628
|
+
`webdriver-executable=${locate(process.env[driverVar])}`,
|
|
629
|
+
`stereotype=${stereotype}`,
|
|
630
|
+
]
|
|
631
|
+
}
|
|
632
|
+
|
|
550
633
|
function locate(fileLike) {
|
|
551
634
|
if (fs.existsSync(fileLike)) {
|
|
552
635
|
return fileLike
|