selenium-webdriver 4.46.0 → 4.48.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.
Binary file
package/firefox.js CHANGED
@@ -317,6 +317,7 @@ class Options extends Capabilities {
317
317
  *
318
318
  * @param {...string} paths The paths to the extension XPI files to install.
319
319
  * @return {!Options} A self reference.
320
+ * @deprecated Use {@link Driver#installAddon} instead.
320
321
  */
321
322
  addExtensions(...paths) {
322
323
  this.profile_().addExtensions(paths)
package/index.js CHANGED
@@ -478,6 +478,14 @@ class Builder {
478
478
  return this.firefoxOptions_
479
479
  }
480
480
 
481
+ /**
482
+ * @return {firefox.ServiceBuilder} the Firefox service currently configured
483
+ * for this instance.
484
+ */
485
+ getFirefoxService() {
486
+ return this.firefoxService_
487
+ }
488
+
481
489
  /**
482
490
  * Sets the {@link firefox.ServiceBuilder} to use to manage the geckodriver
483
491
  * child process when creating Firefox sessions locally.
@@ -650,6 +658,11 @@ class Builder {
650
658
 
651
659
  if (url) {
652
660
  this.log_.fine('Creating session on remote server')
661
+
662
+ if (typeof url === 'string') {
663
+ capabilities.set('se:remoteUrl', url)
664
+ }
665
+
653
666
  let client = Promise.resolve(url).then((url) => new _http.HttpClient(url, this.agent_, this.proxy_))
654
667
  let executor = new _http.Executor(client)
655
668
 
@@ -1,4 +1,22 @@
1
- // GENERATED CODE - DO NOT EDIT
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ // DO NOT EDIT! This file is generated by javascript/selenium-webdriver/lib/atoms/make-atoms-module.js.
19
+ // Regenerate with: `bazel build //javascript/selenium-webdriver/lib/atoms:all`
2
20
  module.exports = (function findElements(target, root) {
3
21
  const INVALID_SELECTOR = 'invalid selector';
4
22
  const INVALID_ARGUMENT = 'invalid argument';
@@ -1,4 +1,22 @@
1
- // GENERATED CODE - DO NOT EDIT
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ // DO NOT EDIT! This file is generated by javascript/selenium-webdriver/lib/atoms/make-atoms-module.js.
19
+ // Regenerate with: `bazel build //javascript/selenium-webdriver/lib/atoms:all`
2
20
  module.exports = (function (element, attribute) {
3
21
  const PROPERTY_ALIASES = {
4
22
  'class': 'className',
@@ -1,4 +1,22 @@
1
- // GENERATED CODE - DO NOT EDIT
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ // DO NOT EDIT! This file is generated by javascript/selenium-webdriver/lib/atoms/make-atoms-module.js.
19
+ // Regenerate with: `bazel build //javascript/selenium-webdriver/lib/atoms:all`
2
20
  module.exports = (function isShownElement(elem, optIgnoreOpacity) {
3
21
  var tagNameDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'tagName');
4
22
  var computedStyleCache = new Map();
@@ -0,0 +1,84 @@
1
+ // Licensed to the Software Freedom Conservancy (SFC) under one
2
+ // or more contributor license agreements. See the NOTICE file
3
+ // distributed with this work for additional information
4
+ // regarding copyright ownership. The SFC licenses this file
5
+ // to you under the Apache License, Version 2.0 (the
6
+ // "License"); you may not use this file except in compliance
7
+ // with the License. You may obtain a copy of the License at
8
+ //
9
+ // http://www.apache.org/licenses/LICENSE-2.0
10
+ //
11
+ // Unless required by applicable law or agreed to in writing,
12
+ // software distributed under the License is distributed on an
13
+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ // KIND, either express or implied. See the License for the
15
+ // specific language governing permissions and limitations
16
+ // under the License.
17
+
18
+ const BiDi = require('../bidi')
19
+
20
+ /**
21
+ * One BiDi connection per driver, kept outside the WebDriver class itself so
22
+ * it isn't a discoverable method on it — see
23
+ * docs/decisions/17670-bidi-implementation-boundaries.md. Not re-exported from
24
+ * the package's public entry point.
25
+ *
26
+ * Stores the in-flight promise, not the resolved connection, so a second
27
+ * concurrent call for the same driver awaits the same creation instead of
28
+ * racing its own and leaking whichever connection loses.
29
+ * @type {WeakMap<object, Promise<BiDi>>}
30
+ */
31
+ const connections = new WeakMap()
32
+
33
+ /**
34
+ * Returns the BiDi connection for `driver`, creating it on first access.
35
+ * @param {object} driver The WebDriver instance to obtain a BiDi connection for.
36
+ * @returns {Promise<BiDi>} A promise resolving to `driver`'s BiDi connection, shared
37
+ * with any other in-flight or already-resolved call for the same driver.
38
+ */
39
+ function getBidiConnection(driver) {
40
+ if (!connections.has(driver)) {
41
+ connections.set(driver, createConnection(driver))
42
+ }
43
+ return connections.get(driver)
44
+ }
45
+
46
+ /**
47
+ * @param {object} driver
48
+ * @returns {Promise<BiDi>}
49
+ */
50
+ async function createConnection(driver) {
51
+ const caps = await driver.getCapabilities()
52
+ const webSocketUrl = caps['map_'].get('webSocketUrl')
53
+ if (!webSocketUrl) {
54
+ throw new Error('WebDriver instance must support BiDi protocol')
55
+ }
56
+ return new BiDi(webSocketUrl.replace('localhost', '127.0.0.1'))
57
+ }
58
+
59
+ /**
60
+ * Closes `driver`'s BiDi connection, if one was ever opened. A no-op
61
+ * otherwise — must not lazily create a connection just to close it.
62
+ *
63
+ * Callers (e.g. quit()) invoke this fire-and-forget, so it must never reject:
64
+ * if the original connection attempt itself had failed, `pending` is already
65
+ * rejected and there is nothing live to close.
66
+ * @param {object} driver The WebDriver instance whose BiDi connection should be closed.
67
+ * @returns {Promise<void>} A promise that always resolves, once any open connection
68
+ * has been closed (or immediately, if none was ever opened).
69
+ */
70
+ async function closeBidiConnection(driver) {
71
+ const pending = connections.get(driver)
72
+ if (pending === undefined) {
73
+ return
74
+ }
75
+ connections.delete(driver)
76
+ try {
77
+ const connection = await pending
78
+ await connection.close()
79
+ } catch {
80
+ // Nothing to close — the original connection attempt failed.
81
+ }
82
+ }
83
+
84
+ module.exports = { getBidiConnection, closeBidiConnection }
package/lib/logging.js CHANGED
@@ -271,6 +271,9 @@ class Logger {
271
271
 
272
272
  /** @private {Set<function(!Entry)>} */
273
273
  this.handlers_ = null
274
+
275
+ /** @private {Set<string>} ids already reported via {@link #deprecate}. */
276
+ this.deprecated_ = new Set()
274
277
  }
275
278
 
276
279
  /** @return {string} the name of this logger. */
@@ -392,6 +395,38 @@ class Logger {
392
395
  this.log(Level.INFO, loggable)
393
396
  }
394
397
 
398
+ /**
399
+ * Logs a deprecation notice at the {@link Level.WARNING} log level, once per
400
+ * `id` for this logger's lifetime — a repeat call with the same `id` is a
401
+ * no-op, matching the once-only behavior `util.deprecate` gives by call-site
402
+ * identity, but keyed on a stable id instead so it survives being wrapped,
403
+ * rebound, or called through multiple paths.
404
+ *
405
+ * `id` is only claimed once the notice is actually loggable at this
406
+ * logger's effective level — under the default `Level.OFF` root level, a
407
+ * call here logs nothing and leaves `id` unclaimed, so a later call (once
408
+ * logging is enabled) still gets to report it instead of finding it already
409
+ * silently used up.
410
+ * @param {string} id a stable, non-empty identifier for this deprecation
411
+ * (e.g. `'webdriver-getBidi'`), distinct from the message text so
412
+ * tooling can key off it even if the wording changes later.
413
+ * @param {string} message the deprecation notice to log.
414
+ * @throws {TypeError} if `id` is empty.
415
+ */
416
+ deprecate(id, message) {
417
+ if (!id) {
418
+ throw new TypeError('Logger#deprecate() requires a non-empty id')
419
+ }
420
+ if (this.deprecated_.has(id)) {
421
+ return
422
+ }
423
+ if (!this.isLoggable(Level.WARNING)) {
424
+ return
425
+ }
426
+ this.deprecated_.add(id)
427
+ this.warning(`[${id}] ${message}`)
428
+ }
429
+
395
430
  /**
396
431
  * Logs a message at the {@link Level.DEBUG} log level.
397
432
  * @param {(string|function(): string)} loggable the message to log, or a
package/lib/proxy.js CHANGED
@@ -77,13 +77,6 @@ PacConfig.prototype.proxyAutoconfigUrl
77
77
  */
78
78
  function ManualConfig() {}
79
79
 
80
- /**
81
- * The proxy host for FTP requests.
82
- *
83
- * @type {(string|undefined)}
84
- */
85
- ManualConfig.prototype.ftpProxy
86
-
87
80
  /**
88
81
  * The proxy host for HTTP requests.
89
82
  *
@@ -138,30 +131,24 @@ function direct() {
138
131
  * Manually configures the browser proxy. The following options are
139
132
  * supported:
140
133
  *
141
- * - `ftp`: Proxy host to use for FTP requests
142
134
  * - `http`: Proxy host to use for HTTP requests
143
135
  * - `https`: Proxy host to use for HTTPS requests
144
136
  * - `bypass`: A list of hosts requests should directly connect to,
145
137
  * bypassing any other proxies for that request. May be specified as a
146
138
  * comma separated string, or a list of strings.
147
139
  *
148
- * Behavior is undefined for FTP, HTTP, and HTTPS requests if the
140
+ * Behavior is undefined for HTTP and HTTPS requests if the
149
141
  * corresponding key is omitted from the configuration options.
150
142
  *
151
- * @param {{ftp: (string|undefined),
152
- * http: (string|undefined),
143
+ * @param {{http: (string|undefined),
153
144
  * https: (string|undefined),
154
145
  * bypass: (Array<string>|undefined)}} options Proxy
155
146
  * configuration options.
156
147
  * @return {!ManualConfig} A new proxy configuration object.
157
148
  */
158
- function manual({ ftp, http, https, bypass }) {
159
- if (ftp !== undefined) {
160
- console.warn('ftpProxy is deprecated and will be removed in the future')
161
- }
149
+ function manual({ http, https, bypass }) {
162
150
  return {
163
151
  proxyType: Type.MANUAL,
164
- ftpProxy: ftp,
165
152
  httpProxy: http,
166
153
  sslProxy: https,
167
154
  noProxy: bypass,
package/lib/webdriver.js CHANGED
@@ -40,7 +40,7 @@ const cdpTargets = ['page', 'browser']
40
40
  const { Credential } = require('./virtual_authenticator')
41
41
  const webElement = require('./webelement')
42
42
  const { isObject } = require('./util')
43
- const BIDI = require('../bidi')
43
+ const { getBidiConnection, closeBidiConnection } = require('./bidi_connection')
44
44
  const { PinnedScript } = require('./pinnedScript')
45
45
  const JSZip = require('jszip')
46
46
  const Script = require('./script')
@@ -691,6 +691,16 @@ class WebDriver {
691
691
  this.pinnedScripts_ = {}
692
692
  }
693
693
 
694
+ /**
695
+ * The shared logger deprecation notices (e.g. {@link WebDriver#getBidi})
696
+ * are emitted through — a class-level accessor since a deprecation can be
697
+ * reported from a static/prototype context with no driver instance at hand.
698
+ * @return {!./logging.Logger} the shared `selenium.webdriver.webdriver` logger.
699
+ */
700
+ static get logger() {
701
+ return logging.getLogger('selenium.webdriver.webdriver')
702
+ }
703
+
694
704
  /**
695
705
  * Creates a new WebDriver session.
696
706
  *
@@ -794,10 +804,9 @@ class WebDriver {
794
804
  this._cdpWsConnection.close()
795
805
  }
796
806
 
797
- // Close the BiDi websocket connection
798
- if (this._bidiConnection !== undefined) {
799
- this._bidiConnection.close()
800
- }
807
+ // Not awaited: the session is already torn down by this point, so
808
+ // closing our end of the socket doesn't need to gate quit() completing.
809
+ closeBidiConnection(this)
801
810
  })
802
811
  }
803
812
 
@@ -1045,6 +1054,9 @@ class WebDriver {
1045
1054
  async findElementInternal_(locatorFn, context) {
1046
1055
  let result = await locatorFn(context)
1047
1056
  if (Array.isArray(result)) {
1057
+ if (result.length === 0) {
1058
+ throw new NoSuchElementError('Cannot locate an element with provided parameters')
1059
+ }
1048
1060
  result = result[0]
1049
1061
  }
1050
1062
  if (!(result instanceof WebElement)) {
@@ -1088,7 +1100,15 @@ class WebDriver {
1088
1100
  * @private
1089
1101
  */
1090
1102
  async findElementsInternal_(locatorFn, context) {
1091
- const result = await locatorFn(context)
1103
+ let result
1104
+ try {
1105
+ result = await locatorFn(context)
1106
+ } catch (ex) {
1107
+ if (ex instanceof NoSuchElementError) {
1108
+ return []
1109
+ }
1110
+ throw ex
1111
+ }
1092
1112
  if (result instanceof WebElement) {
1093
1113
  return [result]
1094
1114
  }
@@ -1301,19 +1321,6 @@ class WebDriver {
1301
1321
  this._cdpConnection.execute('Target.getTargets')
1302
1322
  }
1303
1323
 
1304
- /**
1305
- * Initiates bidi connection using 'webSocketUrl'
1306
- * @returns {BIDI}
1307
- */
1308
- async getBidi() {
1309
- if (this._bidiConnection === undefined) {
1310
- const caps = await this.getCapabilities()
1311
- let WebSocketUrl = caps['map_'].get('webSocketUrl')
1312
- this._bidiConnection = new BIDI(WebSocketUrl.replace('localhost', '127.0.0.1'))
1313
- }
1314
- return this._bidiConnection
1315
- }
1316
-
1317
1324
  /**
1318
1325
  * Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
1319
1326
  * @param {string} debuggerAddress
@@ -1778,6 +1785,36 @@ class WebDriver {
1778
1785
  }
1779
1786
  }
1780
1787
 
1788
+ /**
1789
+ * Returns the WebDriver BiDi connection for this session.
1790
+ *
1791
+ * @deprecated BiDi is an internal implementation detail — this accessor hands
1792
+ * back the raw transport directly, which is no longer supported public API.
1793
+ * Use a composed BiDi module instead, e.g. `Network.create(driver)` or
1794
+ * `require('selenium-webdriver/bidi/network')`.
1795
+ * @function
1796
+ * @name WebDriver#getBidi
1797
+ * @returns {Promise<import('../bidi')>} A promise resolving to this session's raw
1798
+ * BiDi connection, opened on first access and reused afterward.
1799
+ */
1800
+ // Object.defineProperty, not `WebDriver.prototype.getBidi = function () {...}`:
1801
+ // a plain assignment creates an enumerable property, but a method declared in
1802
+ // the class body (like every other method here) is non-enumerable — so BiDi
1803
+ // would become more discoverable off the driver than it was before.
1804
+ Object.defineProperty(WebDriver.prototype, 'getBidi', {
1805
+ value: function () {
1806
+ WebDriver.logger.deprecate(
1807
+ 'webdriver-getBidi',
1808
+ 'WebDriver#getBidi() is deprecated. Use a composed BiDi module instead, e.g. Network.create(driver) or ' +
1809
+ "require('selenium-webdriver/bidi/network').",
1810
+ )
1811
+ return getBidiConnection(this)
1812
+ },
1813
+ writable: true,
1814
+ enumerable: false,
1815
+ configurable: true,
1816
+ })
1817
+
1781
1818
  /**
1782
1819
  * Interface for navigating back and forth in the browser history.
1783
1820
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selenium-webdriver",
3
- "version": "4.46.0",
3
+ "version": "4.48.0",
4
4
  "description": "The official WebDriver JavaScript bindings from the Selenium project",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -20,35 +20,35 @@
20
20
  "url": "https://github.com/SeleniumHQ/selenium.git"
21
21
  },
22
22
  "engines": {
23
- "node": ">= 20.0.0"
23
+ "node": ">= 22.0.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "@bazel/runfiles": "^6.5.0",
27
27
  "jszip": "^3.10.1",
28
28
  "tmp": "^0.2.7",
29
- "ws": "^8.21.0"
29
+ "ws": "^8.21.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@eslint/js": "^10.0.1",
33
- "cddl": "^0.21.0",
34
- "cddl2ts": "^0.10.0",
33
+ "cddl": "^0.21.1",
34
+ "cddl2ts": "^0.10.1",
35
35
  "clean-jsdoc-theme": "^4.3.3",
36
- "eslint": "^10.7.0",
36
+ "eslint": "^10.8.1",
37
37
  "eslint-config-prettier": "^10.1.8",
38
38
  "eslint-plugin-mocha": "^11.3.0",
39
- "eslint-plugin-n": "^18.2.1",
39
+ "eslint-plugin-n": "^18.3.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.7.0",
43
+ "globals": "^17.9.0",
44
44
  "has-flag": "^5.0.1",
45
45
  "jsdoc": "^4.0.5",
46
- "mocha": "^11.7.6",
46
+ "mocha": "^11.8.0",
47
47
  "mocha-junit-reporter": "^2.2.1",
48
48
  "multer": "^2.2.0",
49
- "prettier": "^3.9.5",
49
+ "prettier": "^3.9.6",
50
50
  "serve-index": "^1.9.2",
51
- "sinon": "^22.0.0",
51
+ "sinon": "^22.1.0",
52
52
  "supports-color": "^10.2.2"
53
53
  },
54
54
  "scripts": {