selenium-webdriver 4.22.0 → 4.24.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,27 @@
1
+ ## 4.24.0
2
+
3
+ - [js] expose selenium version for node.js (#14325)
4
+ - [bidi] Add authentication handlers
5
+ - [bidi] Add high-level script command (#14293)
6
+ - Handle optional dependency for @bazel/runfiles
7
+ - remove console msg and safe exit if runfiles never found
8
+ - fix rbe build(use global node process)
9
+ - Fix error handling for missing runfiles
10
+ - [ci] Use a tag to figure out what we might want to release (#14378)
11
+ - Add CDP for Chrome 128 and remove 125
12
+
13
+ ## 4.23.0
14
+
15
+ - Expose pnpm as a tool we can use
16
+ - [bidi] Fix the event unsubscribe method. Update modules to have close methods. (#14192)
17
+ - Run Node browser tests on the RBE (#14194)
18
+ - [bidi] Add methods to add/remove handlers in Script module (#14230)
19
+ - [bidi] Add source type to log entry (#14244)
20
+ - [bidi] Add dom mutation handlers (#14238)
21
+ - [bidi] Add high-level script pinning APIs (#14250)
22
+ - [bidi] Deprecate argument value wrapper class (#14251)
23
+ - Add CDP for Chrome 127 and remove 124
24
+
1
25
  ## 4.22.0
2
26
 
3
27
  - [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()
@@ -127,10 +127,10 @@ class BrowsingContextInspector {
127
127
  let response = null
128
128
  if ('navigation' in params) {
129
129
  response = new NavigationInfo(params.context, params.navigation, params.timestamp, params.url)
130
- } else if ('type' in params) {
131
- response = new UserPromptOpened(params.context, params.type, params.message)
132
130
  } else if ('accepted' in params) {
133
131
  response = new UserPromptClosed(params.context, params.accepted, params.userText)
132
+ } else if ('type' in params) {
133
+ response = new UserPromptOpened(params.context, params.type, params.message)
134
134
  } else {
135
135
  response = new BrowsingContextInfo(params.context, params.url, params.children, params.parent)
136
136
  }
@@ -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
@@ -21,11 +21,22 @@ const { ContinueResponseParameters } = require('./continueResponseParameters')
21
21
  const { ContinueRequestParameters } = require('./continueRequestParameters')
22
22
  const { ProvideResponseParameters } = require('./provideResponseParameters')
23
23
 
24
+ const NetworkEvent = {
25
+ BEFORE_REQUEST_SENT: 'network.beforeRequestSent',
26
+ RESPONSE_STARTED: 'network.responseStarted',
27
+ RESPONSE_COMPLETED: 'network.responseCompleted',
28
+ AUTH_REQUIRED: 'network.authRequired',
29
+ FETCH_ERROR: 'network.fetchError',
30
+ }
31
+
24
32
  /**
25
33
  * Represents all commands and events of Network module.
26
34
  * Described in https://w3c.github.io/webdriver-bidi/#module-network.
27
35
  */
28
36
  class Network {
37
+ #callbackId = 0
38
+ #listener
39
+
29
40
  /**
30
41
  * Represents a Network object.
31
42
  * @constructor
@@ -35,6 +46,43 @@ class Network {
35
46
  constructor(driver, browsingContextIds) {
36
47
  this._driver = driver
37
48
  this._browsingContextIds = browsingContextIds
49
+ this.#listener = new Map()
50
+ this.#listener.set(NetworkEvent.AUTH_REQUIRED, new Map())
51
+ this.#listener.set(NetworkEvent.BEFORE_REQUEST_SENT, new Map())
52
+ this.#listener.set(NetworkEvent.FETCH_ERROR, new Map())
53
+ this.#listener.set(NetworkEvent.RESPONSE_STARTED, new Map())
54
+ this.#listener.set(NetworkEvent.RESPONSE_COMPLETED, new Map())
55
+ }
56
+
57
+ addCallback(eventType, callback) {
58
+ const id = ++this.#callbackId
59
+
60
+ const eventCallbackMap = this.#listener.get(eventType)
61
+ eventCallbackMap.set(id, callback)
62
+ return id
63
+ }
64
+
65
+ removeCallback(id) {
66
+ let hasId = false
67
+ for (const [, callbacks] of this.#listener) {
68
+ if (callbacks.has(id)) {
69
+ callbacks.delete(id)
70
+ hasId = true
71
+ }
72
+ }
73
+
74
+ if (!hasId) {
75
+ throw Error(`Callback with id ${id} not found`)
76
+ }
77
+ }
78
+
79
+ invokeCallbacks(eventType, data) {
80
+ const callbacks = this.#listener.get(eventType)
81
+ if (callbacks) {
82
+ for (const [, callback] of callbacks) {
83
+ callback(data)
84
+ }
85
+ }
38
86
  }
39
87
 
40
88
  async init() {
@@ -75,10 +123,10 @@ class Network {
75
123
  * Subscribes to the 'network.authRequired' event and handles it with the provided callback.
76
124
  *
77
125
  * @param {Function} callback - The callback function to handle the event.
78
- * @returns {Promise<void>} - A promise that resolves when the subscription is successful.
126
+ * @returns {Promise<number>} - A promise that resolves when the subscription is successful.
79
127
  */
80
128
  async authRequired(callback) {
81
- await this.subscribeAndHandleEvent('network.authRequired', callback)
129
+ return await this.subscribeAndHandleEvent('network.authRequired', callback)
82
130
  }
83
131
 
84
132
  /**
@@ -97,10 +145,8 @@ class Network {
97
145
  } else {
98
146
  await this.bidi.subscribe(eventType)
99
147
  }
100
- await this._on(callback)
101
- }
148
+ let id = this.addCallback(eventType, callback)
102
149
 
103
- async _on(callback) {
104
150
  this.ws = await this.bidi.socket
105
151
  this.ws.on('message', (event) => {
106
152
  const { params } = JSON.parse(Buffer.from(event.toString()))
@@ -134,9 +180,10 @@ class Network {
134
180
  params.errorText,
135
181
  )
136
182
  }
137
- callback(response)
183
+ this.invokeCallbacks(eventType, response)
138
184
  }
139
185
  })
186
+ return id
140
187
  }
141
188
 
142
189
  /**
@@ -313,12 +360,26 @@ class Network {
313
360
  * @returns {Promise<void>} A promise that resolves when the network connection is closed.
314
361
  */
315
362
  async close() {
316
- await this.bidi.unsubscribe(
317
- 'network.beforeRequestSent',
318
- 'network.responseStarted',
319
- 'network.responseCompleted',
320
- 'network.authRequired',
321
- )
363
+ if (
364
+ this._browsingContextIds !== null &&
365
+ this._browsingContextIds !== undefined &&
366
+ this._browsingContextIds.length > 0
367
+ ) {
368
+ await this.bidi.unsubscribe(
369
+ 'network.beforeRequestSent',
370
+ 'network.responseStarted',
371
+ 'network.responseCompleted',
372
+ 'network.authRequired',
373
+ this._browsingContextIds,
374
+ )
375
+ } else {
376
+ await this.bidi.unsubscribe(
377
+ 'network.beforeRequestSent',
378
+ 'network.responseStarted',
379
+ 'network.responseCompleted',
380
+ 'network.authRequired',
381
+ )
382
+ }
322
383
  }
323
384
  }
324
385
 
@@ -15,7 +15,7 @@
15
15
  // specific language governing permissions and limitations
16
16
  // under the License.
17
17
 
18
- const { PrimitiveType, NonPrimitiveType, RemoteType } = require('./protocolType')
18
+ const { PrimitiveType, NonPrimitiveType, RemoteType, SpecialNumberType } = require('./protocolType')
19
19
 
20
20
  const TYPE_CONSTANT = 'type'
21
21
  const VALUE_CONSTANT = 'value'
@@ -185,7 +185,93 @@ 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
+ static getArgument(argument) {
193
+ let localValue = null
194
+
195
+ if (
196
+ argument === SpecialNumberType.NAN ||
197
+ argument === SpecialNumberType.MINUS_ZERO ||
198
+ argument === SpecialNumberType.INFINITY ||
199
+ argument === SpecialNumberType.MINUS_INFINITY
200
+ ) {
201
+ localValue = LocalValue.createSpecialNumberValue(argument)
202
+ return localValue
203
+ }
204
+
205
+ const type = typeof argument
206
+
207
+ switch (type) {
208
+ case PrimitiveType.STRING:
209
+ localValue = LocalValue.createStringValue(argument)
210
+ break
211
+ case PrimitiveType.NUMBER:
212
+ localValue = LocalValue.createNumberValue(argument)
213
+ break
214
+ case PrimitiveType.BOOLEAN:
215
+ localValue = LocalValue.createBooleanValue(argument)
216
+ break
217
+ case PrimitiveType.BIGINT:
218
+ localValue = LocalValue.createBigIntValue(argument.toString())
219
+ break
220
+ case PrimitiveType.UNDEFINED:
221
+ localValue = LocalValue.createUndefinedValue()
222
+ break
223
+ case NonPrimitiveType.OBJECT:
224
+ if (argument === null) {
225
+ localValue = LocalValue.createNullValue()
226
+ break
227
+ }
228
+ if (argument instanceof Date) {
229
+ localValue = LocalValue.createDateValue(argument)
230
+ } else if (argument instanceof Map) {
231
+ const map = []
232
+
233
+ argument.forEach((value, key) => {
234
+ let objectKey
235
+ if (typeof key === 'string') {
236
+ objectKey = key
237
+ } else {
238
+ objectKey = LocalValue.getArgument(key)
239
+ }
240
+ const objectValue = LocalValue.getArgument(value)
241
+ map.push([objectKey, objectValue])
242
+ })
243
+ localValue = new LocalValue(NonPrimitiveType.MAP, map)
244
+ } else if (argument instanceof Set) {
245
+ const set = []
246
+ argument.forEach((value) => {
247
+ set.push(LocalValue.getArgument(value))
248
+ })
249
+ localValue = LocalValue.createSetValue(set)
250
+ } else if (argument instanceof Array) {
251
+ const arr = []
252
+ argument.forEach((value) => {
253
+ arr.push(LocalValue.getArgument(value))
254
+ })
255
+ localValue = LocalValue.createArrayValue(arr)
256
+ } else if (argument instanceof RegExp) {
257
+ localValue = LocalValue.createRegularExpressionValue({
258
+ pattern: argument.source,
259
+ flags: argument.flags,
260
+ })
261
+ } else {
262
+ let value = []
263
+ Object.entries(argument).forEach((entry) => {
264
+ value.push([LocalValue.getArgument(entry[0]), LocalValue.getArgument(entry[1])])
265
+ })
266
+ localValue = new LocalValue(NonPrimitiveType.OBJECT, value)
267
+ }
268
+ break
269
+ }
270
+
271
+ return localValue
272
+ }
273
+
274
+ asMap() {
189
275
  let toReturn = {}
190
276
  toReturn[TYPE_CONSTANT] = this.type
191
277
 
@@ -242,7 +328,7 @@ class RemoteValue {
242
328
  }
243
329
 
244
330
  deserializeValue(value, type) {
245
- if ([NonPrimitiveType.MAP, NonPrimitiveType.OBJECT].includes(type)) {
331
+ if (type === NonPrimitiveType.OBJECT) {
246
332
  return Object.fromEntries(value)
247
333
  } else if (type === NonPrimitiveType.REGULAR_EXPRESSION) {
248
334
  return new RegExpValue(value.pattern, value.flags)
@@ -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
package/index.js CHANGED
@@ -44,6 +44,7 @@ const BrowsingContext = require('./bidi/browsingContext')
44
44
  const BrowsingContextInspector = require('./bidi/browsingContextInspector')
45
45
  const ScriptManager = require('./bidi/scriptManager')
46
46
  const NetworkInspector = require('./bidi/networkInspector')
47
+ const version = require('./package.json').version
47
48
 
48
49
  const Browser = capabilities.Browser
49
50
  const Capabilities = capabilities.Capabilities
@@ -794,3 +795,4 @@ exports.BrowsingContext = BrowsingContext
794
795
  exports.BrowsingContextInspector = BrowsingContextInspector
795
796
  exports.ScriptManager = ScriptManager
796
797
  exports.NetworkInspector = NetworkInspector
798
+ exports.version = version
@@ -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
+ }
@@ -87,7 +87,7 @@ ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#0
87
87
  lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
88
88
  moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
89
89
  seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var Pc="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),Qc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,Rc=/^#(?:[0-9a-f]{3}){1,2}$/i,Sc=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,Tc=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function Uc(a,b){b=b.toLowerCase();return"style"==b?Vc(a.style.cssText):Lc&&"value"==b&&S(a,"INPUT")?a.value:Mc&&!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var Wc=/[;]+(?=(?:(?:[^"]*"){2})*[^"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\([^()]*\))*[^()]*$)/;
90
- function Vc(a){var b=[];l(a.split(Wc),function(c){var d=c.indexOf(":");0<d&&(c=[c.slice(0,d),c.slice(d+1)],2==c.length&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=";"==b.charAt(b.length-1)?b:b+";"}function S(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function Xc(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}Xc.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};Xc.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};Xc.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};function T(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}T.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};T.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
90
+ function Vc(a){var b=[];l(a.split(Wc),function(c){var d=c.indexOf(":");0<d&&(c=[c.slice(0,d),c.slice(d+1)],2==c.length&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=";"==b.charAt(b.length-1)?b:b+";"}function S(a,b){b&&"string"!==typeof b&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&1==a.nodeType&&(!b||"FORM"==b):!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function Xc(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}Xc.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};Xc.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};Xc.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};function T(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}T.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};T.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
91
91
  T.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Yc="function"===typeof ShadowRoot;function Zc(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return S(a)?a:null}
92
92
  function U(a,b){b=Ha(b);if("float"==b||"cssFloat"==b||"styleFloat"==b)b=Mc?"styleFloat":"cssFloat";a:{var c=b;var d=A(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||"";break a}c=""}a=c||$c(a,b);if(null===a)a=null;else if(0<=oa(Pc,b)){b:{var e=a.match(Sc);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(Tc))if(b=
93
93
  Number(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=Oc[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(Qc,"#$1$1$2$2$3$3")),!Rc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
@@ -2,6 +2,6 @@
2
2
  module.exports = function(){return (function(){var d=this||self;function f(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var h=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if("string"===typeof a)return"string"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},k=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,e="string"===typeof a?a.split(""):a,g=0;g<c;g++)g in e&&b.call(void 0,e[g],g,a)};function l(a,b){this.code=a;this.a=m[a]||n;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}f(l,Error);var n="unknown error",m={15:"element not selectable",11:"element not visible"};m[31]=n;m[30]=n;m[24]="invalid cookie domain";m[29]="invalid element coordinates";m[12]="invalid element state";m[32]="invalid selector";
3
3
  m[51]="invalid selector";m[52]="invalid selector";m[17]="javascript error";m[405]="unsupported operation";m[34]="move target out of bounds";m[27]="no such alert";m[7]="no such element";m[8]="no such frame";m[23]="no such window";m[28]="script timeout";m[33]="session not created";m[10]="stale element reference";m[21]="timeout";m[25]="unable to set cookie";m[26]="unexpected alert open";m[13]=n;m[9]="unknown command";var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=""}function t(a){return-1!=p.indexOf(a)};function u(){return t("Firefox")||t("FxiOS")}function v(){return(t("Chrome")||t("CriOS"))&&!t("Edge")};function w(){return t("iPhone")&&!t("iPod")&&!t("iPad")};var y=t("Opera"),z=t("Trident")||t("MSIE"),A=t("Edge"),B=t("Gecko")&&!(-1!=p.toLowerCase().indexOf("webkit")&&!t("Edge"))&&!(t("Trident")||t("MSIE"))&&!t("Edge"),C=-1!=p.toLowerCase().indexOf("webkit")&&!t("Edge");function D(){var a=d.document;return a?a.documentMode:void 0}var E;
4
4
  a:{var F="",G=function(){var a=p;if(B)return/rv:([^\);]+)(\)|;)/.exec(a);if(A)return/Edge\/([\d\.]+)/.exec(a);if(z)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(C)return/WebKit\/(\S+)/.exec(a);if(y)return/(?:Version)[ \/]?(\S+)/.exec(a)}();G&&(F=G?G[1]:"");if(z){var H=D();if(null!=H&&H>parseFloat(F)){E=String(H);break a}}E=F}var I;I=d.document&&z?D():void 0;var J=u(),K=w()||t("iPod"),L=t("iPad"),M=t("Android")&&!(v()||u()||t("Opera")||t("Silk")),N=v(),aa=t("Safari")&&!(v()||t("Coast")||t("Opera")||t("Edge")||t("Edg/")||t("OPR")||u()||t("Silk")||t("Android"))&&!(w()||t("iPad")||t("iPod"));function O(a){return(a=a.exec(p))?a[1]:""}(function(){if(J)return O(/Firefox\/([0-9.]+)/);if(z||A||y)return E;if(N)return w()||t("iPad")||t("iPod")?O(/CriOS\/([0-9.]+)/):O(/Chrome\/([0-9.]+)/);if(aa&&!(w()||t("iPad")||t("iPod")))return O(/Version\/([0-9.]+)/);if(K||L){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(p);if(a)return a[1]+"."+a[2]}else if(M)return(a=O(/Android\s+([0-9.]+)/))?a:O(/Version\/([0-9.]+)/);return""})();var P=z&&!(8<=Number(I)),ba=z&&!(9<=Number(I));var ca={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Q={IMG:" ",BR:"\n"};function R(a,b,c){if(!(a.nodeName in ca))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in Q)b.push(Q[a.nodeName]);else for(a=a.firstChild;a;)R(a,b,c),a=a.nextSibling};function S(a,b){b=b.toLowerCase();return"style"==b?da(a.style.cssText):P&&"value"==b&&T(a,"INPUT")?a.value:ba&&!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var ea=/[;]+(?=(?:(?:[^"]*"){2})*[^"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\([^()]*\))*[^()]*$)/;
5
- function da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(":");0<e&&(c=[c.slice(0,e),c.slice(e+1)],2==c.length&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=";"==b.charAt(b.length-1)?b:b+";"}function U(a,b){P&&"value"==b&&T(a,"OPTION")&&null===S(a,"value")?(b=[],R(a,b,!1),a=b.join("")):a=a[b];return a}function T(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}
6
- function V(a){return T(a,"OPTION")?!0:T(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):!1};var fa={"class":"className",readonly:"readOnly"},ha="allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate".split(" ");function W(a,b){var c=null,e=b.toLowerCase();if("style"==e)return(c=a.style)&&"string"!=typeof c&&(c=c.cssText),c;if(("selected"==e||"checked"==e)&&V(a)){if(!V(a))throw new l(15,"Element is not selectable");b="selected";c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";return U(a,b)?"true":null}var g=T(a,"A");if(T(a,"IMG")&&"src"==e||g&&"href"==e)return(c=S(a,e))&&(c=U(a,e)),c;if("spellcheck"==e){c=S(a,e);if(null!==c){if("false"==c.toLowerCase())return"false";if("true"==c.toLowerCase())return"true"}return U(a,
5
+ function da(a){var b=[];k(a.split(ea),function(c){var e=c.indexOf(":");0<e&&(c=[c.slice(0,e),c.slice(e+1)],2==c.length&&b.push(c[0].toLowerCase(),":",c[1],";"))});b=b.join("");return b=";"==b.charAt(b.length-1)?b:b+";"}function U(a,b){P&&"value"==b&&T(a,"OPTION")&&null===S(a,"value")?(b=[],R(a,b,!1),a=b.join("")):a=a[b];return a}
6
+ function T(a,b){b&&"string"!==typeof b&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&1==a.nodeType&&(!b||"FORM"==b):!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)}function V(a){return T(a,"OPTION")?!0:T(a,"INPUT")?(a=a.type.toLowerCase(),"checkbox"==a||"radio"==a):!1};var fa={"class":"className",readonly:"readOnly"},ha="allowfullscreen allowpaymentrequest allowusermedia async autofocus autoplay checked compact complete controls declare default defaultchecked defaultselected defer disabled ended formnovalidate hidden indeterminate iscontenteditable ismap itemscope loop multiple muted nohref nomodule noresize noshade novalidate nowrap open paused playsinline pubdate readonly required reversed scoped seamless seeking selected truespeed typemustmatch willvalidate".split(" ");function W(a,b){var c=null,e=b.toLowerCase();if("style"==e)return(c=a.style)&&"string"!=typeof c&&(c=c.cssText),c;if(("selected"==e||"checked"==e)&&V(a)){if(!V(a))throw new l(15,"Element is not selectable");b="selected";c=a.type&&a.type.toLowerCase();if("checkbox"==c||"radio"==c)b="checked";return U(a,b)?"true":null}var g=T(a,"A");if(T(a,"IMG")&&"src"==e||g&&"href"==e)return(c=S(a,e))&&(c=U(a,e)),c;if("spellcheck"==e){c=S(a,e);if(null!==c){if("false"==c.toLowerCase())return"false";if("true"==c.toLowerCase())return"true"}return U(a,
7
7
  e)+""}g=fa[b]||b;if(0<=h(ha,e))return(c=null!==S(a,b)||U(a,g))?"true":null;try{var x=U(a,g)}catch(ia){}(e=null==x)||(e=typeof x,e="object"==e&&null!=x||"function"==e);e?c=S(a,b):c=x;return null!=c?c.toString():null}var X=["_"],Y=d;X[0]in Y||"undefined"==typeof Y.execScript||Y.execScript("var "+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||void 0===W?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=W;; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);};
@@ -10,7 +10,7 @@ moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:
10
10
  seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var ia="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),ja=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,ka=/^#(?:[0-9a-f]{3}){1,2}$/i,la=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,ma=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function m(a,b){this.code=a;this.a=p[a]||r;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}ca(m,Error);var r="unknown error",p={15:"element not selectable",11:"element not visible"};p[31]=r;p[30]=r;p[24]="invalid cookie domain";p[29]="invalid element coordinates";p[12]="invalid element state";
11
11
  p[32]="invalid selector";p[51]="invalid selector";p[52]="invalid selector";p[17]="javascript error";p[405]="unsupported operation";p[34]="move target out of bounds";p[27]="no such alert";p[7]="no such element";p[8]="no such frame";p[23]="no such window";p[28]="script timeout";p[33]="session not created";p[10]="stale element reference";p[21]="timeout";p[25]="unable to set cookie";p[26]="unexpected alert open";p[13]=r;p[9]="unknown command";var u=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};
12
12
  function na(a,b){var c=0;a=u(String(a)).split(".");b=u(String(b)).split(".");for(var e=Math.max(a.length,b.length),d=0;0==c&&d<e;d++){var f=a[d]||"",h=b[d]||"";do{f=/(\d*)(\D*)(.*)/.exec(f)||["","","",""];h=/(\d*)(\D*)(.*)/.exec(h)||["","","",""];if(0==f[0].length&&0==h[0].length)break;c=v(0==f[1].length?0:parseInt(f[1],10),0==h[1].length?0:parseInt(h[1],10))||v(0==f[2].length,0==h[2].length)||v(f[2],h[2]);f=f[3];h=h[3]}while(0==c)}return c}function v(a,b){return a<b?-1:a>b?1:0};var w;a:{var oa=g.navigator;if(oa){var sa=oa.userAgent;if(sa){w=sa;break a}}w=""}function x(a){return-1!=w.indexOf(a)};function y(){return x("Firefox")||x("FxiOS")}function A(){return(x("Chrome")||x("CriOS"))&&!x("Edge")};function ta(a){return String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})};function B(){return x("iPhone")&&!x("iPod")&&!x("iPad")};function ua(a,b){var c=va;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var wa=x("Opera"),C=x("Trident")||x("MSIE"),xa=x("Edge"),ya=x("Gecko")&&!(-1!=w.toLowerCase().indexOf("webkit")&&!x("Edge"))&&!(x("Trident")||x("MSIE"))&&!x("Edge"),za=-1!=w.toLowerCase().indexOf("webkit")&&!x("Edge");function Aa(){var a=g.document;return a?a.documentMode:void 0}var E;
13
- a:{var F="",G=function(){var a=w;if(ya)return/rv:([^\);]+)(\)|;)/.exec(a);if(xa)return/Edge\/([\d\.]+)/.exec(a);if(C)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(za)return/WebKit\/(\S+)/.exec(a);if(wa)return/(?:Version)[ \/]?(\S+)/.exec(a)}();G&&(F=G?G[1]:"");if(C){var H=Aa();if(null!=H&&H>parseFloat(F)){E=String(H);break a}}E=F}var va={};function Ba(a){return ua(a,function(){return 0<=na(E,a)})}var I;I=g.document&&C?Aa():void 0;var Ca=y(),Da=B()||x("iPod"),Ea=x("iPad"),Fa=x("Android")&&!(A()||y()||x("Opera")||x("Silk")),Ga=A(),Ha=x("Safari")&&!(A()||x("Coast")||x("Opera")||x("Edge")||x("Edg/")||x("OPR")||y()||x("Silk")||x("Android"))&&!(B()||x("iPad")||x("iPod"));function J(a){return(a=a.exec(w))?a[1]:""}(function(){if(Ca)return J(/Firefox\/([0-9.]+)/);if(C||xa||wa)return E;if(Ga)return B()||x("iPad")||x("iPod")?J(/CriOS\/([0-9.]+)/):J(/Chrome\/([0-9.]+)/);if(Ha&&!(B()||x("iPad")||x("iPod")))return J(/Version\/([0-9.]+)/);if(Da||Ea){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(w);if(a)return a[1]+"."+a[2]}else if(Fa)return(a=J(/Android\s+([0-9.]+)/))?a:J(/Version\/([0-9.]+)/);return""})();var K;if(K=C)K=!(9<=Number(I));var Ia=K;function L(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function N(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Ja(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Ka(a){this.a=a||g.document||document};function P(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function Q(a,b,c,e){this.f=a;this.a=b;this.b=c;this.c=e}Q.prototype.ceil=function(){this.f=Math.ceil(this.f);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.c=Math.ceil(this.c);return this};Q.prototype.floor=function(){this.f=Math.floor(this.f);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.c=Math.floor(this.c);return this};Q.prototype.round=function(){this.f=Math.round(this.f);this.a=Math.round(this.a);this.b=Math.round(this.b);this.c=Math.round(this.c);return this};function R(a,b,c,e){this.a=a;this.b=b;this.width=c;this.height=e}R.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
13
+ a:{var F="",G=function(){var a=w;if(ya)return/rv:([^\);]+)(\)|;)/.exec(a);if(xa)return/Edge\/([\d\.]+)/.exec(a);if(C)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(za)return/WebKit\/(\S+)/.exec(a);if(wa)return/(?:Version)[ \/]?(\S+)/.exec(a)}();G&&(F=G?G[1]:"");if(C){var H=Aa();if(null!=H&&H>parseFloat(F)){E=String(H);break a}}E=F}var va={};function Ba(a){return ua(a,function(){return 0<=na(E,a)})}var I;I=g.document&&C?Aa():void 0;var Ca=y(),Da=B()||x("iPod"),Ea=x("iPad"),Fa=x("Android")&&!(A()||y()||x("Opera")||x("Silk")),Ga=A(),Ha=x("Safari")&&!(A()||x("Coast")||x("Opera")||x("Edge")||x("Edg/")||x("OPR")||y()||x("Silk")||x("Android"))&&!(B()||x("iPad")||x("iPod"));function J(a){return(a=a.exec(w))?a[1]:""}(function(){if(Ca)return J(/Firefox\/([0-9.]+)/);if(C||xa||wa)return E;if(Ga)return B()||x("iPad")||x("iPod")?J(/CriOS\/([0-9.]+)/):J(/Chrome\/([0-9.]+)/);if(Ha&&!(B()||x("iPad")||x("iPod")))return J(/Version\/([0-9.]+)/);if(Da||Ea){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(w);if(a)return a[1]+"."+a[2]}else if(Fa)return(a=J(/Android\s+([0-9.]+)/))?a:J(/Version\/([0-9.]+)/);return""})();var K;if(K=C)K=!(9<=Number(I));var Ia=K;function L(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function N(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Ja(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Ka(a){this.a=a||g.document||document};function P(a,b){b&&"string"!==typeof b&&(b=b.toString());return a instanceof HTMLFormElement?!!a&&1==a.nodeType&&(!b||"FORM"==b):!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function Q(a,b,c,e){this.f=a;this.a=b;this.b=c;this.c=e}Q.prototype.ceil=function(){this.f=Math.ceil(this.f);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.c=Math.ceil(this.c);return this};Q.prototype.floor=function(){this.f=Math.floor(this.f);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.c=Math.floor(this.c);return this};Q.prototype.round=function(){this.f=Math.round(this.f);this.a=Math.round(this.a);this.b=Math.round(this.b);this.c=Math.round(this.c);return this};function R(a,b,c,e){this.a=a;this.b=b;this.width=c;this.height=e}R.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};
14
14
  R.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var La="function"===typeof ShadowRoot;function S(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return P(a)?a:null}
15
15
  function T(a,b){b=ta(b);if("float"==b||"cssFloat"==b||"styleFloat"==b)b=Ia?"styleFloat":"cssFloat";a:{var c=b;var e=N(a);if(e.defaultView&&e.defaultView.getComputedStyle&&(e=e.defaultView.getComputedStyle(a,null))){c=e[c]||e.getPropertyValue(c)||"";break a}c=""}a=c||Ma(a,b);if(null===a)a=null;else if(0<=da(ia,b)){b:{var d=a.match(la);if(d&&(b=Number(d[1]),c=Number(d[2]),e=Number(d[3]),d=Number(d[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=e&&255>=e&&0<=d&&1>=d)){b=[b,c,e,d];break b}b=null}if(!b)b:{if(e=a.match(ma))if(b=
16
16
  Number(e[1]),c=Number(e[2]),e=Number(e[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=e&&255>=e){b=[b,c,e,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=ha[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(ja,"#$1$1$2$2$3$3")),!ka.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a}
package/lib/network.js ADDED
@@ -0,0 +1,78 @@
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 network = require('../bidi/network')
19
+ const { InterceptPhase } = require('../bidi/interceptPhase')
20
+ const { AddInterceptParameters } = require('../bidi/addInterceptParameters')
21
+
22
+ class Network {
23
+ #driver
24
+ #network
25
+ #callBackInterceptIdMap = new Map()
26
+
27
+ constructor(driver) {
28
+ this.#driver = driver
29
+ }
30
+
31
+ // This should be done in the constructor.
32
+ // But since it needs to call async methods we cannot do that in the constructor.
33
+ // We can have a separate async method that initialises the Script instance.
34
+ // However, that pattern does not allow chaining the methods as we would like the user to use it.
35
+ // Since it involves awaiting to get the instance and then another await to call the method.
36
+ // Using this allows the user to do this "await driver.network.addAuthenticationHandler(callback)"
37
+ async #init() {
38
+ if (this.#network !== undefined) {
39
+ return
40
+ }
41
+ this.#network = await network(this.#driver)
42
+ }
43
+
44
+ async addAuthenticationHandler(username, password) {
45
+ await this.#init()
46
+
47
+ const interceptId = await this.#network.addIntercept(new AddInterceptParameters(InterceptPhase.AUTH_REQUIRED))
48
+
49
+ const id = await this.#network.authRequired(async (event) => {
50
+ await this.#network.continueWithAuth(event.request.request, username, password)
51
+ })
52
+
53
+ this.#callBackInterceptIdMap.set(id, interceptId)
54
+ return id
55
+ }
56
+
57
+ async removeAuthenticationHandler(id) {
58
+ await this.#init()
59
+
60
+ const interceptId = this.#callBackInterceptIdMap.get(id)
61
+
62
+ await this.#network.removeIntercept(interceptId)
63
+ await this.#network.removeCallback(id)
64
+
65
+ this.#callBackInterceptIdMap.delete(id)
66
+ }
67
+
68
+ async clearAuthenticationHandlers() {
69
+ for (const [key, value] of this.#callBackInterceptIdMap.entries()) {
70
+ await this.#network.removeIntercept(value)
71
+ await this.#network.removeCallback(key)
72
+ }
73
+
74
+ this.#callBackInterceptIdMap.clear()
75
+ }
76
+ }
77
+
78
+ module.exports = Network
@@ -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,72 @@ 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
+ }
124
+
125
+ async execute(script, ...args) {
126
+ await this.#initScript()
127
+
128
+ const browsingContextId = await this.#driver.getWindowHandle()
129
+
130
+ const argumentList = []
131
+
132
+ args.forEach((arg) => {
133
+ argumentList.push(LocalValue.getArgument(arg))
134
+ })
135
+
136
+ const response = await this.#script.callFunctionInBrowsingContext(browsingContextId, script, true, argumentList)
137
+
138
+ return response.result
139
+ }
61
140
  }
62
141
 
63
142
  module.exports = Script
package/lib/webdriver.js CHANGED
@@ -44,6 +44,7 @@ const BIDI = require('../bidi')
44
44
  const { PinnedScript } = require('./pinnedScript')
45
45
  const JSZip = require('jszip')
46
46
  const Script = require('./script')
47
+ const Network = require('./network')
47
48
 
48
49
  // Capability names that are defined in the W3C spec.
49
50
  const W3C_CAPABILITY_NAMES = new Set([
@@ -656,6 +657,7 @@ function filterNonW3CCaps(capabilities) {
656
657
  */
657
658
  class WebDriver {
658
659
  #script = undefined
660
+ #network = undefined
659
661
  /**
660
662
  * @param {!(./session.Session|IThenable<!./session.Session>)} session Either
661
663
  * a known session or a promise that will be resolved to a session.
@@ -1116,6 +1118,16 @@ class WebDriver {
1116
1118
  return this.#script
1117
1119
  }
1118
1120
 
1121
+ network() {
1122
+ // The Network maintains state of the callbacks.
1123
+ // Returning a new instance of the same driver will not work while removing callbacks.
1124
+ if (this.#network === undefined) {
1125
+ this.#network = new Network(this)
1126
+ }
1127
+
1128
+ return this.#network
1129
+ }
1130
+
1119
1131
  validatePrintPageParams(keys, object) {
1120
1132
  let page = {}
1121
1133
  let margin = {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selenium-webdriver",
3
- "version": "4.22.0",
3
+ "version": "4.24.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.18.0"
29
30
  },
30
31
  "devDependencies": {
31
- "@bazel/runfiles": "^5.8.1",
32
- "@eslint/js": "^9.1.1",
32
+ "@eslint/js": "^9.8.0",
33
33
  "clean-jsdoc-theme": "^4.3.0",
34
- "eslint": "^9.1.0",
34
+ "eslint": "^9.8.0",
35
35
  "eslint-config-prettier": "^9.1.0",
36
- "eslint-plugin-mocha": "^10.4.3",
37
- "eslint-plugin-n": "^17.2.1",
36
+ "eslint-plugin-mocha": "^10.5.0",
37
+ "eslint-plugin-n": "^17.10.1",
38
38
  "eslint-plugin-no-only-tests": "^3.1.0",
39
- "eslint-plugin-prettier": "^5.1.3",
39
+ "eslint-plugin-prettier": "^5.2.1",
40
40
  "express": "^4.19.2",
41
- "globals": "^15.0.0",
41
+ "globals": "^15.9.0",
42
+ "has-flag": "^5.0.1",
42
43
  "jsdoc": "^4.0.3",
43
- "mocha": "^10.4.0",
44
+ "mocha": "^10.7.0",
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.3",
47
48
  "serve-index": "^1.9.1",
48
- "sinon": "^17.0.1"
49
+ "sinon": "^18.0.0",
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,6 +32,8 @@
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')
36
38
  const chrome = require('../chrome')
37
39
  const edge = require('../edge')
@@ -43,6 +45,14 @@ const { Browser } = require('../lib/capabilities')
43
45
  const { Builder } = require('../index')
44
46
  const { getBinaryPaths } = require('../common/driverFinder')
45
47
 
48
+ let runfiles
49
+ try {
50
+ // Attempt to require @bazel/runfiles
51
+ runfiles = require('@bazel/runfiles').runfiles
52
+ } catch {
53
+ // Fall through
54
+ }
55
+
46
56
  /**
47
57
  * Describes a browser targeted by a {@linkplain suite test suite}.
48
58
  * @record
@@ -288,6 +298,37 @@ class Environment {
288
298
  const urlOrServer = URL_MAP.get(this)
289
299
 
290
300
  const builder = new Builder()
301
+
302
+ // Sniff the environment variables for paths to use for the common browsers
303
+ // Chrome
304
+ if ('SE_CHROMEDRIVER' in process.env) {
305
+ const found = locate(process.env.SE_CHROMEDRIVER)
306
+ const service = new chrome.ServiceBuilder(found)
307
+ builder.setChromeService(service)
308
+ }
309
+ if ('SE_CHROME' in process.env) {
310
+ const binary = locate(process.env.SE_CHROME)
311
+ const options = new chrome.Options()
312
+ options.setChromeBinaryPath(binary)
313
+ options.setAcceptInsecureCerts(true)
314
+ options.addArguments('disable-infobars', 'disable-breakpad', 'disable-dev-shm-usage', 'no-sandbox')
315
+ builder.setChromeOptions(options)
316
+ }
317
+ // Edge
318
+ // Firefox
319
+ if ('SE_GECKODRIVER' in process.env) {
320
+ const found = locate(process.env.SE_GECKODRIVER)
321
+ const service = new firefox.ServiceBuilder(found)
322
+ builder.setFirefoxService(service)
323
+ }
324
+ if ('SE_FIREFOX' in process.env) {
325
+ const binary = locate(process.env.SE_FIREFOX)
326
+ const options = new firefox.Options()
327
+ options.enableBidi()
328
+ options.setBinary(binary)
329
+ builder.setFirefoxOptions(options)
330
+ }
331
+
291
332
  builder.disableEnvironmentOverrides()
292
333
 
293
334
  const realBuild = builder.build
@@ -305,6 +346,7 @@ class Environment {
305
346
  // Enable BiDi for supporting browsers.
306
347
  if (browser.name === Browser.FIREFOX || browser.name === Browser.CHROME || browser.name === Browser.EDGE) {
307
348
  builder.setCapability('webSocketUrl', true)
349
+ builder.setCapability('unhandledPromptBehavior', 'ignore')
308
350
  }
309
351
 
310
352
  if (typeof urlOrServer === 'string') {
@@ -505,6 +547,63 @@ function getTestHook(name) {
505
547
  return fn
506
548
  }
507
549
 
550
+ function locate(fileLike) {
551
+ if (fs.existsSync(fileLike)) {
552
+ return fileLike
553
+ }
554
+
555
+ if (!runfiles) {
556
+ throw new Error('Unable to find ' + fileLike)
557
+ }
558
+
559
+ try {
560
+ return runfiles.resolve(fileLike)
561
+ } catch {
562
+ // Fall through
563
+ }
564
+
565
+ // Is the item in the workspace?
566
+ try {
567
+ return runfiles.resolveWorkspaceRelative(fileLike)
568
+ } catch {
569
+ // Fall through
570
+ }
571
+
572
+ // Find the repo mapping file
573
+ let repoMappingFile
574
+ try {
575
+ repoMappingFile = runfiles.resolve('_repo_mapping')
576
+ } catch {
577
+ throw new Error('Unable to locate (no repo mapping file): ' + fileLike)
578
+ }
579
+ const lines = fs.readFileSync(repoMappingFile, { encoding: 'utf8' }).split('\n')
580
+
581
+ // Build a map of "repo we declared we need" to "path"
582
+ const mapping = {}
583
+ for (const line of lines) {
584
+ if (line.startsWith(',')) {
585
+ const parts = line.split(',', 3)
586
+ mapping[parts[1]] = parts[2]
587
+ }
588
+ }
589
+
590
+ // Get the first segment of the path
591
+ const pathSegments = fileLike.split('/')
592
+ if (!pathSegments.length) {
593
+ throw new Error('Unable to locate ' + fileLike)
594
+ }
595
+
596
+ pathSegments[0] = mapping[pathSegments[0]] ? mapping[pathSegments[0]] : '_main'
597
+
598
+ try {
599
+ return runfiles.resolve(path.join(...pathSegments))
600
+ } catch {
601
+ // Fall through
602
+ }
603
+
604
+ throw new Error('Unable to find ' + fileLike)
605
+ }
606
+
508
607
  // PUBLIC API
509
608
 
510
609
  module.exports = {