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 +12 -0
- package/bidi/argumentValue.js +6 -1
- package/bidi/browsingContextInspector.js +23 -0
- package/bidi/index.js +14 -6
- package/bidi/logEntries.js +15 -17
- package/bidi/logInspector.js +15 -3
- package/bidi/network.js +20 -6
- package/bidi/protocolValue.js +5 -1
- package/bidi/scriptManager.js +79 -9
- package/bin/linux/selenium-manager +0 -0
- package/bin/macos/selenium-manager +0 -0
- package/bin/windows/selenium-manager.exe +0 -0
- package/lib/atoms/bidi-mutation-listener.js +55 -0
- package/lib/pinnedScript.js +1 -0
- package/lib/script.js +63 -0
- package/package.json +14 -13
- package/testing/index.js +87 -0
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)
|
package/bidi/argumentValue.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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:
|
|
193
|
+
events: existingEvents,
|
|
186
194
|
},
|
|
187
195
|
}
|
|
188
196
|
|
package/bidi/logEntries.js
CHANGED
|
@@ -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,
|
|
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
|
|
package/bidi/logInspector.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
package/bidi/protocolValue.js
CHANGED
|
@@ -185,7 +185,11 @@ class LocalValue {
|
|
|
185
185
|
return new LocalValue(NonPrimitiveType.CHANNEL, value)
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
|
|
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
|
|
package/bidi/scriptManager.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
508
|
+
return await this.subscribeAndHandleEvent(ScriptEvent.REALM_DESTROYED, callback)
|
|
457
509
|
}
|
|
458
510
|
|
|
459
511
|
async subscribeAndHandleEvent(eventType, callback) {
|
|
460
|
-
if (this.
|
|
461
|
-
await this.bidi.subscribe(eventType, this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/pinnedScript.js
CHANGED
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.
|
|
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": "
|
|
29
|
+
"ws": "^8.17.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"@
|
|
32
|
-
"@eslint/js": "^9.1.1",
|
|
32
|
+
"@eslint/js": "^9.5.0",
|
|
33
33
|
"clean-jsdoc-theme": "^4.3.0",
|
|
34
|
-
"eslint": "^9.
|
|
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.
|
|
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.
|
|
41
|
+
"globals": "^15.6.0",
|
|
42
|
+
"has-flag": "^4.0.0",
|
|
42
43
|
"jsdoc": "^4.0.3",
|
|
43
|
-
"mocha": "^10.
|
|
44
|
+
"mocha": "^10.5.1",
|
|
44
45
|
"mocha-junit-reporter": "^2.2.1",
|
|
45
|
-
"multer": "
|
|
46
|
-
"prettier": "^3.2
|
|
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": "
|
|
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 = {
|