selenium-webdriver 4.22.0 → 4.23.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 CHANGED
@@ -1,3 +1,15 @@
1
+ ## 4.23.0
2
+
3
+ - Expose pnpm as a tool we can use
4
+ - [bidi] Fix the event unsubscribe method. Update modules to have close methods. (#14192)
5
+ - Run Node browser tests on the RBE (#14194)
6
+ - [bidi] Add methods to add/remove handlers in Script module (#14230)
7
+ - [bidi] Add source type to log entry (#14244)
8
+ - [bidi] Add dom mutation handlers (#14238)
9
+ - [bidi] Add high-level script pinning APIs (#14250)
10
+ - [bidi] Deprecate argument value wrapper class (#14251)
11
+ - Add CDP for Chrome 127 and remove 124
12
+
1
13
  ## 4.22.0
2
14
 
3
15
  - [bidi] Add types for user prompt related events (#14097)
@@ -17,6 +17,11 @@
17
17
 
18
18
  const { LocalValue } = require('./protocolValue')
19
19
 
20
+ /**
21
+ * @deprecated
22
+ * in favor of LocalValue methods for all argument values.
23
+ * This extra wrapper is not required.
24
+ */
20
25
  class ArgumentValue {
21
26
  constructor(value) {
22
27
  this.value = value
@@ -24,7 +29,7 @@ class ArgumentValue {
24
29
 
25
30
  asMap() {
26
31
  if (this.value instanceof LocalValue) {
27
- return this.value.toJson()
32
+ return this.value.asMap()
28
33
  } else {
29
34
  // ReferenceValue
30
35
  return this.value.asMap()
@@ -138,6 +138,29 @@ class BrowsingContextInspector {
138
138
  }
139
139
  })
140
140
  }
141
+
142
+ async close() {
143
+ if (
144
+ this._browsingContextIds !== null &&
145
+ this._browsingContextIds !== undefined &&
146
+ this._browsingContextIds.length > 0
147
+ ) {
148
+ await this.bidi.unsubscribe(
149
+ 'browsingContext.contextCreated',
150
+ 'browsingContext.contextDestroyed',
151
+ 'browsingContext.fragmentNavigated',
152
+ 'browsingContext.userPromptClosed',
153
+ this._browsingContextIds,
154
+ )
155
+ } else {
156
+ await this.bidi.unsubscribe(
157
+ 'browsingContext.contextCreated',
158
+ 'browsingContext.contextDestroyed',
159
+ 'browsingContext.fragmentNavigated',
160
+ 'browsingContext.userPromptClosed',
161
+ )
162
+ }
163
+ }
141
164
  }
142
165
 
143
166
  async function getBrowsingContextInstance(driver, browsingContextIds = null) {
package/bidi/index.js CHANGED
@@ -157,6 +157,8 @@ class Index extends EventEmitter {
157
157
  params.params.contexts = contextsArray
158
158
  }
159
159
 
160
+ this.events.push(...eventsArray)
161
+
160
162
  await this.send(params)
161
163
  }
162
164
 
@@ -167,11 +169,14 @@ class Index extends EventEmitter {
167
169
  * @returns {Promise<void>}
168
170
  */
169
171
  async unsubscribe(events, browsingContexts) {
170
- if (typeof events === 'string') {
171
- this.events = this.events.filter((event) => event !== events)
172
- } else if (Array.isArray(events)) {
173
- this.events = this.events.filter((event) => !events.includes(event))
174
- }
172
+ const eventsToRemove = typeof events === 'string' ? [events] : events
173
+
174
+ // Check if the eventsToRemove are in the subscribed events array
175
+ // Filter out events that are not in this.events before filtering
176
+ const existingEvents = eventsToRemove.filter((event) => this.events.includes(event))
177
+
178
+ // Remove the events from the subscribed events array
179
+ this.events = this.events.filter((event) => !existingEvents.includes(event))
175
180
 
176
181
  if (typeof browsingContexts === 'string') {
177
182
  this.browsingContexts.pop()
@@ -179,10 +184,13 @@ class Index extends EventEmitter {
179
184
  this.browsingContexts = this.browsingContexts.filter((id) => !browsingContexts.includes(id))
180
185
  }
181
186
 
187
+ if (existingEvents.length === 0) {
188
+ return
189
+ }
182
190
  const params = {
183
191
  method: 'session.unsubscribe',
184
192
  params: {
185
- events: this.events,
193
+ events: existingEvents,
186
194
  },
187
195
  }
188
196
 
@@ -17,6 +17,8 @@
17
17
 
18
18
  'use strict'
19
19
 
20
+ const { Source } = require('./scriptTypes')
21
+
20
22
  /**
21
23
  * Represents a base log entry.
22
24
  * Desribed in https://w3c.github.io/webdriver-bidi/#types-log-logentry.
@@ -25,12 +27,14 @@ class BaseLogEntry {
25
27
  /**
26
28
  * Creates a new instance of BaseLogEntry.
27
29
  * @param {string} level - The log level.
30
+ * @param {string} text - The log source.
28
31
  * @param {string} text - The log text.
29
32
  * @param {number} timeStamp - The log timestamp.
30
33
  * @param {string} stackTrace - The log stack trace.
31
34
  */
32
- constructor(level, text, timeStamp, stackTrace) {
35
+ constructor(level, source, text, timeStamp, stackTrace) {
33
36
  this._level = level
37
+ this._source = new Source(source)
34
38
  this._text = text
35
39
  this._timeStamp = timeStamp
36
40
  this._stackTrace = stackTrace
@@ -67,6 +71,10 @@ class BaseLogEntry {
67
71
  get stackTrace() {
68
72
  return this._stackTrace
69
73
  }
74
+
75
+ get source() {
76
+ return this._source
77
+ }
70
78
  }
71
79
 
72
80
  /**
@@ -83,8 +91,8 @@ class GenericLogEntry extends BaseLogEntry {
83
91
  * @param {string} type - The log type.
84
92
  * @param {string} stackTrace - The log stack trace.
85
93
  */
86
- constructor(level, text, timeStamp, type, stackTrace) {
87
- super(level, text, timeStamp, stackTrace)
94
+ constructor(level, source, text, timeStamp, type, stackTrace) {
95
+ super(level, source, text, timeStamp, stackTrace)
88
96
  this._type = type
89
97
  }
90
98
 
@@ -103,10 +111,9 @@ class GenericLogEntry extends BaseLogEntry {
103
111
  * @extends GenericLogEntry
104
112
  */
105
113
  class ConsoleLogEntry extends GenericLogEntry {
106
- constructor(level, text, timeStamp, type, method, realm, args, stackTrace) {
107
- super(level, text, timeStamp, type, stackTrace)
114
+ constructor(level, source, text, timeStamp, type, method, args, stackTrace) {
115
+ super(level, source, text, timeStamp, type, stackTrace)
108
116
  this._method = method
109
- this._realm = realm
110
117
  this._args = args
111
118
  }
112
119
 
@@ -117,15 +124,6 @@ class ConsoleLogEntry extends GenericLogEntry {
117
124
  get method() {
118
125
  return this._method
119
126
  }
120
-
121
- /**
122
- * Gets the realm associated with the log entry.
123
- * @returns {string} The realm associated with the log entry.
124
- */
125
- get realm() {
126
- return this._realm
127
- }
128
-
129
127
  /**
130
128
  * Gets the arguments associated with the log entry.
131
129
  * @returns {Array} The arguments associated with the log entry.
@@ -141,8 +139,8 @@ class ConsoleLogEntry extends GenericLogEntry {
141
139
  * @extends GenericLogEntry
142
140
  */
143
141
  class JavascriptLogEntry extends GenericLogEntry {
144
- constructor(level, text, timeStamp, type, stackTrace) {
145
- super(level, text, timeStamp, type, stackTrace)
142
+ constructor(level, source, text, timeStamp, type, stackTrace) {
143
+ super(level, source, text, timeStamp, type, stackTrace)
146
144
  }
147
145
  }
148
146
 
@@ -128,11 +128,11 @@ class LogInspector {
128
128
  if (params?.type === LOG.TYPE_CONSOLE) {
129
129
  let consoleEntry = new ConsoleLogEntry(
130
130
  params.level,
131
+ params.source,
131
132
  params.text,
132
133
  params.timestamp,
133
134
  params.type,
134
135
  params.method,
135
- params.realm,
136
136
  params.args,
137
137
  params.stackTrace,
138
138
  )
@@ -178,6 +178,7 @@ class LogInspector {
178
178
  if (params?.type === LOG.TYPE_JS_LOGS) {
179
179
  let jsEntry = new JavascriptLogEntry(
180
180
  params.level,
181
+ params.source,
181
182
  params.text,
182
183
  params.timestamp,
183
184
  params.type,
@@ -212,6 +213,7 @@ class LogInspector {
212
213
  if (params?.type === 'javascript' && params?.level === 'error') {
213
214
  let jsErrorEntry = new JavascriptLogEntry(
214
215
  params.level,
216
+ params.source,
215
217
  params.text,
216
218
  params.timestamp,
217
219
  params.type,
@@ -250,6 +252,7 @@ class LogInspector {
250
252
  if (params?.type === 'javascript') {
251
253
  let jsEntry = new JavascriptLogEntry(
252
254
  params.level,
255
+ params.source,
253
256
  params.text,
254
257
  params.timestamp,
255
258
  params.type,
@@ -279,11 +282,11 @@ class LogInspector {
279
282
  if (params?.type === 'console') {
280
283
  let consoleEntry = new ConsoleLogEntry(
281
284
  params.level,
285
+ params.source,
282
286
  params.text,
283
287
  params.timestamp,
284
288
  params.type,
285
289
  params.method,
286
- params.realm,
287
290
  params.args,
288
291
  params.stackTrace,
289
292
  )
@@ -302,6 +305,7 @@ class LogInspector {
302
305
  if (params !== undefined && !['console', 'javascript'].includes(params?.type)) {
303
306
  let genericEntry = new GenericLogEntry(
304
307
  params.level,
308
+ params.source,
305
309
  params.text,
306
310
  params.timestamp,
307
311
  params.type,
@@ -330,7 +334,15 @@ class LogInspector {
330
334
  * @returns {Promise<void>}
331
335
  */
332
336
  async close() {
333
- await this.bidi.unsubscribe('log.entryAdded', this._browsingContextIds)
337
+ if (
338
+ this._browsingContextIds !== null &&
339
+ this._browsingContextIds !== undefined &&
340
+ this._browsingContextIds.length > 0
341
+ ) {
342
+ await this.bidi.unsubscribe('log.entryAdded', this._browsingContextIds)
343
+ } else {
344
+ await this.bidi.unsubscribe('log.entryAdded')
345
+ }
334
346
  }
335
347
  }
336
348
 
package/bidi/network.js CHANGED
@@ -313,12 +313,26 @@ class Network {
313
313
  * @returns {Promise<void>} A promise that resolves when the network connection is closed.
314
314
  */
315
315
  async close() {
316
- await this.bidi.unsubscribe(
317
- 'network.beforeRequestSent',
318
- 'network.responseStarted',
319
- 'network.responseCompleted',
320
- 'network.authRequired',
321
- )
316
+ if (
317
+ this._browsingContextIds !== null &&
318
+ this._browsingContextIds !== undefined &&
319
+ this._browsingContextIds.length > 0
320
+ ) {
321
+ await this.bidi.unsubscribe(
322
+ 'network.beforeRequestSent',
323
+ 'network.responseStarted',
324
+ 'network.responseCompleted',
325
+ 'network.authRequired',
326
+ this._browsingContextIds,
327
+ )
328
+ } else {
329
+ await this.bidi.unsubscribe(
330
+ 'network.beforeRequestSent',
331
+ 'network.responseStarted',
332
+ 'network.responseCompleted',
333
+ 'network.authRequired',
334
+ )
335
+ }
322
336
  }
323
337
  }
324
338
 
@@ -185,7 +185,11 @@ class LocalValue {
185
185
  return new LocalValue(NonPrimitiveType.CHANNEL, value)
186
186
  }
187
187
 
188
- toJson() {
188
+ static createReferenceValue(handle, sharedId) {
189
+ return new ReferenceValue(handle, sharedId)
190
+ }
191
+
192
+ asMap() {
189
193
  let toReturn = {}
190
194
  toReturn[TYPE_CONSTANT] = this.type
191
195
 
@@ -27,14 +27,58 @@ const { RemoteValue } = require('./protocolValue')
27
27
  const { Source } = require('./scriptTypes')
28
28
  const { WebDriverError } = require('../lib/error')
29
29
 
30
+ const ScriptEvent = {
31
+ MESSAGE: 'script.message',
32
+ REALM_CREATED: 'script.realmCreated',
33
+ REALM_DESTROYED: 'script.realmDestroyed',
34
+ }
35
+
30
36
  /**
31
37
  * Represents class to run events and commands of Script module.
32
38
  * Described in https://w3c.github.io/webdriver-bidi/#module-script.
33
39
  * @class
34
40
  */
35
41
  class ScriptManager {
42
+ #callbackId = 0
43
+ #listener
44
+
36
45
  constructor(driver) {
37
46
  this._driver = driver
47
+ this.#listener = new Map()
48
+ this.#listener.set(ScriptEvent.MESSAGE, new Map())
49
+ this.#listener.set(ScriptEvent.REALM_CREATED, new Map())
50
+ this.#listener.set(ScriptEvent.REALM_DESTROYED, new Map())
51
+ }
52
+
53
+ addCallback(eventType, callback) {
54
+ const id = ++this.#callbackId
55
+
56
+ const eventCallbackMap = this.#listener.get(eventType)
57
+ eventCallbackMap.set(id, callback)
58
+ return id
59
+ }
60
+
61
+ removeCallback(id) {
62
+ let hasId = false
63
+ for (const [, callbacks] of this.#listener) {
64
+ if (callbacks.has(id)) {
65
+ callbacks.delete(id)
66
+ hasId = true
67
+ }
68
+ }
69
+
70
+ if (!hasId) {
71
+ throw Error(`Callback with id ${id} not found`)
72
+ }
73
+ }
74
+
75
+ invokeCallbacks(eventType, data) {
76
+ const callbacks = this.#listener.get(eventType)
77
+ if (callbacks) {
78
+ for (const [, callback] of callbacks) {
79
+ callback(data)
80
+ }
81
+ }
38
82
  }
39
83
 
40
84
  async init(browsingContextIds) {
@@ -251,6 +295,14 @@ class ScriptManager {
251
295
  params.contexts = new Array(this._browsingContextIds)
252
296
  }
253
297
 
298
+ if (argumentValueList != null) {
299
+ let argumentParams = []
300
+ argumentValueList.forEach((argumentValue) => {
301
+ argumentParams.push(argumentValue.asMap())
302
+ })
303
+ params['arguments'] = argumentParams
304
+ }
305
+
254
306
  const command = {
255
307
  method: 'script.addPreloadScript',
256
308
  params,
@@ -433,7 +485,7 @@ class ScriptManager {
433
485
  * @returns {Promise<void>} - A promise that resolves when the subscription is successful.
434
486
  */
435
487
  async onMessage(callback) {
436
- await this.subscribeAndHandleEvent('script.message', callback)
488
+ return await this.subscribeAndHandleEvent(ScriptEvent.MESSAGE, callback)
437
489
  }
438
490
 
439
491
  /**
@@ -443,7 +495,7 @@ class ScriptManager {
443
495
  * @returns {Promise<void>} - A promise that resolves when the subscription is successful.
444
496
  */
445
497
  async onRealmCreated(callback) {
446
- await this.subscribeAndHandleEvent('script.realmCreated', callback)
498
+ return await this.subscribeAndHandleEvent(ScriptEvent.REALM_CREATED, callback)
447
499
  }
448
500
 
449
501
  /**
@@ -453,19 +505,18 @@ class ScriptManager {
453
505
  * @returns {Promise<void>} - A promise that resolves when the subscription is successful.
454
506
  */
455
507
  async onRealmDestroyed(callback) {
456
- await this.subscribeAndHandleEvent('script.realmDestroyed', callback)
508
+ return await this.subscribeAndHandleEvent(ScriptEvent.REALM_DESTROYED, callback)
457
509
  }
458
510
 
459
511
  async subscribeAndHandleEvent(eventType, callback) {
460
- if (this.browsingContextIds != null) {
461
- await this.bidi.subscribe(eventType, this.browsingContextIds)
512
+ if (this._browsingContextIds != null) {
513
+ await this.bidi.subscribe(eventType, this._browsingContextIds)
462
514
  } else {
463
515
  await this.bidi.subscribe(eventType)
464
516
  }
465
- await this._on(callback)
466
- }
467
517
 
468
- async _on(callback) {
518
+ let id = this.addCallback(eventType, callback)
519
+
469
520
  this.ws = await this.bidi.socket
470
521
  this.ws.on('message', (event) => {
471
522
  const { params } = JSON.parse(Buffer.from(event.toString()))
@@ -482,9 +533,28 @@ class ScriptManager {
482
533
  response = params.realm
483
534
  }
484
535
  }
485
- callback(response)
536
+ this.invokeCallbacks(eventType, response)
486
537
  }
487
538
  })
539
+
540
+ return id
541
+ }
542
+
543
+ async close() {
544
+ if (
545
+ this._browsingContextIds !== null &&
546
+ this._browsingContextIds !== undefined &&
547
+ this._browsingContextIds.length > 0
548
+ ) {
549
+ await this.bidi.unsubscribe(
550
+ 'script.message',
551
+ 'script.realmCreated',
552
+ 'script.realmDestroyed',
553
+ this._browsingContextIds,
554
+ )
555
+ } else {
556
+ await this.bidi.unsubscribe('script.message', 'script.realmCreated', 'script.realmDestroyed')
557
+ }
488
558
  }
489
559
  }
490
560
 
Binary file
Binary file
Binary file
@@ -0,0 +1,55 @@
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
+ function observeMutations(channel) {
19
+ const observer = new MutationObserver((mutations) => {
20
+ for (const mutation of mutations) {
21
+ switch (mutation.type) {
22
+ case 'attributes':
23
+ // Don't report our own attribute has changed.
24
+ if (mutation.attributeName === 'data-__webdriver_id') {
25
+ break
26
+ }
27
+ const curr = mutation.target.getAttribute(mutation.attributeName)
28
+ let id = mutation.target.dataset.__webdriver_id
29
+ if (!id) {
30
+ id = Math.random().toString(36).substring(2) + Date.now().toString(36)
31
+ mutation.target.dataset.__webdriver_id = id
32
+ }
33
+ const json = JSON.stringify({
34
+ target: id,
35
+ name: mutation.attributeName,
36
+ value: curr,
37
+ oldValue: mutation.oldValue,
38
+ })
39
+ channel(json)
40
+ break
41
+ default:
42
+ break
43
+ }
44
+ }
45
+ })
46
+
47
+ observer.observe(document, {
48
+ attributes: true,
49
+ attributeOldValue: true,
50
+ characterData: true,
51
+ characterDataOldValue: true,
52
+ childList: true,
53
+ subtree: true,
54
+ })
55
+ }
@@ -20,6 +20,7 @@ const crypto = require('node:crypto')
20
20
  class PinnedScript {
21
21
  constructor(script) {
22
22
  this.scriptSource_ = script
23
+ // eslint-disable-next-line
23
24
  this.scriptHandle_ = crypto.randomUUID().replace(/-/gi, '')
24
25
  }
25
26
 
package/lib/script.js CHANGED
@@ -16,10 +16,16 @@
16
16
  // under the License.
17
17
 
18
18
  const logInspector = require('../bidi/logInspector')
19
+ const scriptManager = require('../bidi//scriptManager')
20
+ const { LocalValue, ChannelValue } = require('../bidi/protocolValue')
21
+ const fs = require('node:fs')
22
+ const path = require('node:path')
23
+ const by = require('./by')
19
24
 
20
25
  class Script {
21
26
  #driver
22
27
  #logInspector
28
+ #script
23
29
 
24
30
  constructor(driver) {
25
31
  this.#driver = driver
@@ -38,6 +44,13 @@ class Script {
38
44
  this.#logInspector = await logInspector(this.#driver)
39
45
  }
40
46
 
47
+ async #initScript() {
48
+ if (this.#script !== undefined) {
49
+ return
50
+ }
51
+ this.#script = await scriptManager([], this.#driver)
52
+ }
53
+
41
54
  async addJavaScriptErrorHandler(callback) {
42
55
  await this.#init()
43
56
  return await this.#logInspector.onJavascriptException(callback)
@@ -58,6 +71,56 @@ class Script {
58
71
 
59
72
  await this.#logInspector.removeCallback(id)
60
73
  }
74
+
75
+ async addDomMutationHandler(callback) {
76
+ await this.#initScript()
77
+
78
+ let argumentValues = []
79
+ let value = LocalValue.createChannelValue(new ChannelValue('channel_name'))
80
+ argumentValues.push(value)
81
+
82
+ const filePath = path.join(__dirname, 'atoms', 'bidi-mutation-listener.js')
83
+
84
+ let mutationListener = fs.readFileSync(filePath, 'utf-8').toString()
85
+ await this.#script.addPreloadScript(mutationListener, argumentValues)
86
+
87
+ let id = await this.#script.onMessage(async (message) => {
88
+ let payload = JSON.parse(message['data']['value'])
89
+ let elements = await this.#driver.findElements({
90
+ css: '*[data-__webdriver_id=' + by.escapeCss(payload['target']) + ']',
91
+ })
92
+
93
+ if (elements.length === 0) {
94
+ return
95
+ }
96
+
97
+ let event = {
98
+ element: elements[0],
99
+ attribute_name: payload['name'],
100
+ current_value: payload['value'],
101
+ old_value: payload['oldValue'],
102
+ }
103
+ callback(event)
104
+ })
105
+
106
+ return id
107
+ }
108
+
109
+ async removeDomMutationHandler(id) {
110
+ await this.#initScript()
111
+
112
+ await this.#script.removeCallback(id)
113
+ }
114
+
115
+ async pin(script) {
116
+ await this.#initScript()
117
+ return await this.#script.addPreloadScript(script)
118
+ }
119
+
120
+ async unpin(id) {
121
+ await this.#initScript()
122
+ await this.#script.removePreloadScript(id)
123
+ }
61
124
  }
62
125
 
63
126
  module.exports = Script
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selenium-webdriver",
3
- "version": "4.22.0",
3
+ "version": "4.23.0",
4
4
  "description": "The official WebDriver JavaScript bindings from the Selenium project",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -23,35 +23,36 @@
23
23
  "node": ">= 14.21.0"
24
24
  },
25
25
  "dependencies": {
26
+ "@bazel/runfiles": "^5.8.1",
26
27
  "jszip": "^3.10.1",
27
28
  "tmp": "^0.2.3",
28
- "ws": ">=8.16.0"
29
+ "ws": "^8.17.1"
29
30
  },
30
31
  "devDependencies": {
31
- "@bazel/runfiles": "^5.8.1",
32
- "@eslint/js": "^9.1.1",
32
+ "@eslint/js": "^9.5.0",
33
33
  "clean-jsdoc-theme": "^4.3.0",
34
- "eslint": "^9.1.0",
34
+ "eslint": "^9.5.0",
35
35
  "eslint-config-prettier": "^9.1.0",
36
36
  "eslint-plugin-mocha": "^10.4.3",
37
- "eslint-plugin-n": "^17.2.1",
37
+ "eslint-plugin-n": "^17.9.0",
38
38
  "eslint-plugin-no-only-tests": "^3.1.0",
39
39
  "eslint-plugin-prettier": "^5.1.3",
40
40
  "express": "^4.19.2",
41
- "globals": "^15.0.0",
41
+ "globals": "^15.6.0",
42
+ "has-flag": "^4.0.0",
42
43
  "jsdoc": "^4.0.3",
43
- "mocha": "^10.4.0",
44
+ "mocha": "^10.5.1",
44
45
  "mocha-junit-reporter": "^2.2.1",
45
- "multer": "^1.4.5-lts.1",
46
- "prettier": "^3.2.5",
46
+ "multer": "1.4.5-lts.1",
47
+ "prettier": "^3.3.2",
47
48
  "serve-index": "^1.9.1",
48
- "sinon": "^17.0.1"
49
+ "sinon": "^17.0.1",
50
+ "supports-color": "^9.4.0"
49
51
  },
50
52
  "scripts": {
51
53
  "lint": "eslint .",
52
54
  "lint:fix": "eslint . --fix",
53
- "test": "npm run lint && mocha -t 600000 --recursive test",
54
- "test-jasmine": "bazel test //javascript/node/selenium-webdriver:tests",
55
+ "test": "bazel test //javascript/node/selenium-webdriver/...",
55
56
  "generate-docs": "jsdoc --configure jsdoc_conf.json --verbose"
56
57
  },
57
58
  "mocha": {
package/testing/index.js CHANGED
@@ -32,7 +32,10 @@
32
32
 
33
33
  'use strict'
34
34
 
35
+ const fs = require('node:fs')
36
+ const path = require('node:path')
35
37
  const { isatty } = require('node:tty')
38
+ const { runfiles } = require('@bazel/runfiles')
36
39
  const chrome = require('../chrome')
37
40
  const edge = require('../edge')
38
41
  const firefox = require('../firefox')
@@ -288,6 +291,37 @@ class Environment {
288
291
  const urlOrServer = URL_MAP.get(this)
289
292
 
290
293
  const builder = new Builder()
294
+
295
+ // Sniff the environment variables for paths to use for the common browsers
296
+ // Chrome
297
+ if ('SE_CHROMEDRIVER' in process.env) {
298
+ const found = locate(process.env.SE_CHROMEDRIVER)
299
+ const service = new chrome.ServiceBuilder(found)
300
+ builder.setChromeService(service)
301
+ }
302
+ if ('SE_CHROME' in process.env) {
303
+ const binary = locate(process.env.SE_CHROME)
304
+ const options = new chrome.Options()
305
+ options.setChromeBinaryPath(binary)
306
+ options.setAcceptInsecureCerts(true)
307
+ options.addArguments('disable-infobars', 'disable-breakpad', 'disable-dev-shm-usage', 'no-sandbox')
308
+ builder.setChromeOptions(options)
309
+ }
310
+ // Edge
311
+ // Firefox
312
+ if ('SE_GECKODRIVER' in process.env) {
313
+ const found = locate(process.env.SE_GECKODRIVER)
314
+ const service = new firefox.ServiceBuilder(found)
315
+ builder.setFirefoxService(service)
316
+ }
317
+ if ('SE_FIREFOX' in process.env) {
318
+ const binary = locate(process.env.SE_FIREFOX)
319
+ const options = new firefox.Options()
320
+ options.enableBidi()
321
+ options.setBinary(binary)
322
+ builder.setFirefoxOptions(options)
323
+ }
324
+
291
325
  builder.disableEnvironmentOverrides()
292
326
 
293
327
  const realBuild = builder.build
@@ -505,6 +539,59 @@ function getTestHook(name) {
505
539
  return fn
506
540
  }
507
541
 
542
+ function locate(fileLike) {
543
+ if (fs.existsSync(fileLike)) {
544
+ return fileLike
545
+ }
546
+
547
+ try {
548
+ return runfiles.resolve(fileLike)
549
+ } catch {
550
+ // Fall through
551
+ }
552
+
553
+ // Is the item in the workspace?
554
+ try {
555
+ return runfiles.resolveWorkspaceRelative(fileLike)
556
+ } catch {
557
+ // Fall through
558
+ }
559
+
560
+ // Find the repo mapping file
561
+ let repoMappingFile
562
+ try {
563
+ repoMappingFile = runfiles.resolve('_repo_mapping')
564
+ } catch {
565
+ throw new Error('Unable to locate (no repo mapping file): ' + fileLike)
566
+ }
567
+ const lines = fs.readFileSync(repoMappingFile, { encoding: 'utf8' }).split('\n')
568
+
569
+ // Build a map of "repo we declared we need" to "path"
570
+ const mapping = {}
571
+ for (const line of lines) {
572
+ if (line.startsWith(',')) {
573
+ const parts = line.split(',', 3)
574
+ mapping[parts[1]] = parts[2]
575
+ }
576
+ }
577
+
578
+ // Get the first segment of the path
579
+ const pathSegments = fileLike.split('/')
580
+ if (!pathSegments.length) {
581
+ throw new Error('Unable to locate ' + fileLike)
582
+ }
583
+
584
+ pathSegments[0] = mapping[pathSegments[0]] ? mapping[pathSegments[0]] : '_main'
585
+
586
+ try {
587
+ return runfiles.resolve(path.join(...pathSegments))
588
+ } catch {
589
+ // Fall through
590
+ }
591
+
592
+ throw new Error('Unable to find ' + fileLike)
593
+ }
594
+
508
595
  // PUBLIC API
509
596
 
510
597
  module.exports = {