selenium-webdriver 4.21.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 +20 -0
- package/README.md +1 -1
- package/bidi/argumentValue.js +6 -1
- package/bidi/browsingContextInspector.js +27 -2
- package/bidi/browsingContextTypes.js +17 -1
- package/bidi/index.js +14 -6
- package/bidi/logEntries.js +15 -17
- package/bidi/logInspector.js +131 -33
- 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/chrome.js +2 -0
- package/chromium.js +2 -0
- package/edge.js +2 -0
- package/firefox.js +5 -0
- package/ie.js +2 -0
- package/lib/atoms/bidi-mutation-listener.js +55 -0
- package/lib/pinnedScript.js +1 -0
- package/lib/script.js +126 -0
- package/lib/webdriver.js +12 -0
- package/package.json +17 -13
- package/safari.js +2 -0
- package/testing/index.js +87 -0
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
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
|
+
|
|
13
|
+
## 4.22.0
|
|
14
|
+
|
|
15
|
+
- [bidi] Add types for user prompt related events (#14097)
|
|
16
|
+
- Add preference to enable CDP in Firefox by default (#14091)
|
|
17
|
+
- [bidi] Add callback handlers for logging APIs (#14120)
|
|
18
|
+
- [bidi] Add high-level logging API (#14135)
|
|
19
|
+
- Add CDP for Chrome 126 and remove 123
|
|
20
|
+
|
|
1
21
|
## 4.21.0
|
|
2
22
|
|
|
3
23
|
- Add CDP for Chrome 125 and remove 122
|
package/README.md
CHANGED
|
@@ -217,7 +217,7 @@ under the License.
|
|
|
217
217
|
|
|
218
218
|
[LTS]: https://github.com/nodejs/LTS
|
|
219
219
|
[PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
|
220
|
-
[api]:
|
|
220
|
+
[api]: https://www.selenium.dev/selenium/docs/api/javascript/
|
|
221
221
|
[chrome]: https://googlechromelabs.github.io/chrome-for-testing/#stable
|
|
222
222
|
[gh]: https://github.com/SeleniumHQ/selenium/
|
|
223
223
|
[issues]: https://github.com/SeleniumHQ/selenium/issues
|
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()
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// specific language governing permissions and limitations
|
|
16
16
|
// under the License.
|
|
17
17
|
|
|
18
|
-
const { BrowsingContextInfo, NavigationInfo } = require('./browsingContextTypes')
|
|
18
|
+
const { BrowsingContextInfo, NavigationInfo, UserPromptOpened, UserPromptClosed } = require('./browsingContextTypes')
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Represents a browsing context related events.
|
|
@@ -127,8 +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)
|
|
130
132
|
} else if ('accepted' in params) {
|
|
131
|
-
|
|
133
|
+
response = new UserPromptClosed(params.context, params.accepted, params.userText)
|
|
132
134
|
} else {
|
|
133
135
|
response = new BrowsingContextInfo(params.context, params.url, params.children, params.parent)
|
|
134
136
|
}
|
|
@@ -136,6 +138,29 @@ class BrowsingContextInspector {
|
|
|
136
138
|
}
|
|
137
139
|
})
|
|
138
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
|
+
}
|
|
139
164
|
}
|
|
140
165
|
|
|
141
166
|
async function getBrowsingContextInstance(driver, browsingContextIds = null) {
|
|
@@ -80,4 +80,20 @@ class NavigationInfo {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
class UserPromptOpened {
|
|
84
|
+
constructor(browsingContextId, type, message) {
|
|
85
|
+
this.browsingContextId = browsingContextId
|
|
86
|
+
this.type = type
|
|
87
|
+
this.message = message
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class UserPromptClosed {
|
|
92
|
+
constructor(browsingContextId, accepted, userText = undefined) {
|
|
93
|
+
this.browsingContextId = browsingContextId
|
|
94
|
+
this.accepted = accepted
|
|
95
|
+
this.userText = userText
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { BrowsingContextInfo, NavigationInfo, UserPromptOpened, UserPromptClosed }
|
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
|
@@ -21,16 +21,31 @@ const { ConsoleLogEntry, JavascriptLogEntry, GenericLogEntry } = require('./logE
|
|
|
21
21
|
const LOG = {
|
|
22
22
|
TYPE_CONSOLE: 'console',
|
|
23
23
|
TYPE_JS_LOGS: 'javascript',
|
|
24
|
+
TYPE_JS_EXCEPTION: 'javascriptException',
|
|
25
|
+
TYPE_LOGS: 'logs',
|
|
26
|
+
TYPE_CONSOLE_FILTER: 'console_filter',
|
|
27
|
+
TYPE_JS_LOGS_FILTER: 'javascript_filter',
|
|
28
|
+
TYPE_JS_EXCEPTION_FILTER: 'javascriptException_filter',
|
|
29
|
+
TYPE_LOGS_FILTER: 'logs_filter',
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
class LogInspector {
|
|
27
33
|
bidi
|
|
28
34
|
ws
|
|
35
|
+
#callbackId = 0
|
|
29
36
|
|
|
30
37
|
constructor(driver, browsingContextIds) {
|
|
31
38
|
this._driver = driver
|
|
32
39
|
this._browsingContextIds = browsingContextIds
|
|
33
|
-
this.listener =
|
|
40
|
+
this.listener = new Map()
|
|
41
|
+
this.listener.set(LOG.TYPE_CONSOLE, new Map())
|
|
42
|
+
this.listener.set(LOG.TYPE_JS_LOGS, new Map())
|
|
43
|
+
this.listener.set(LOG.TYPE_JS_EXCEPTION, new Map())
|
|
44
|
+
this.listener.set(LOG.TYPE_LOGS, new Map())
|
|
45
|
+
this.listener.set(LOG.TYPE_CONSOLE_FILTER, new Map())
|
|
46
|
+
this.listener.set(LOG.TYPE_JS_LOGS_FILTER, new Map())
|
|
47
|
+
this.listener.set(LOG.TYPE_JS_EXCEPTION_FILTER, new Map())
|
|
48
|
+
this.listener.set(LOG.TYPE_LOGS_FILTER, new Map())
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
/**
|
|
@@ -42,12 +57,47 @@ class LogInspector {
|
|
|
42
57
|
await this.bidi.subscribe('log.entryAdded', this._browsingContextIds)
|
|
43
58
|
}
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
addCallback(eventType, callback) {
|
|
61
|
+
const id = ++this.#callbackId
|
|
62
|
+
|
|
63
|
+
const eventCallbackMap = this.listener.get(eventType)
|
|
64
|
+
eventCallbackMap.set(id, callback)
|
|
65
|
+
return id
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
removeCallback(id) {
|
|
69
|
+
let hasId = false
|
|
70
|
+
for (const [, callbacks] of this.listener) {
|
|
71
|
+
if (callbacks.has(id)) {
|
|
72
|
+
callbacks.delete(id)
|
|
73
|
+
hasId = true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!hasId) {
|
|
78
|
+
throw Error(`Callback with id ${id} not found`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
invokeCallbacks(eventType, data) {
|
|
83
|
+
const callbacks = this.listener.get(eventType)
|
|
84
|
+
if (callbacks) {
|
|
85
|
+
for (const [, callback] of callbacks) {
|
|
86
|
+
callback(data)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
invokeCallbacksWithFilter(eventType, data, filterLevel) {
|
|
92
|
+
const callbacks = this.listener.get(eventType)
|
|
93
|
+
if (callbacks) {
|
|
94
|
+
for (const [, value] of callbacks) {
|
|
95
|
+
const callback = value.callback
|
|
96
|
+
const filter = value.filter
|
|
97
|
+
if (filterLevel === filter.getLevel()) {
|
|
98
|
+
callback(data)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
51
101
|
}
|
|
52
102
|
}
|
|
53
103
|
|
|
@@ -55,13 +105,21 @@ class LogInspector {
|
|
|
55
105
|
* Listen to Console logs
|
|
56
106
|
* @param callback
|
|
57
107
|
* @param filterBy
|
|
58
|
-
* @returns {Promise<
|
|
108
|
+
* @returns {Promise<number>}
|
|
59
109
|
*/
|
|
60
110
|
async onConsoleEntry(callback, filterBy = undefined) {
|
|
61
111
|
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
|
|
62
112
|
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
|
|
63
113
|
}
|
|
64
114
|
|
|
115
|
+
let id
|
|
116
|
+
|
|
117
|
+
if (filterBy !== undefined) {
|
|
118
|
+
id = this.addCallback(LOG.TYPE_CONSOLE_FILTER, { callback: callback, filter: filterBy })
|
|
119
|
+
} else {
|
|
120
|
+
id = this.addCallback(LOG.TYPE_CONSOLE, callback)
|
|
121
|
+
}
|
|
122
|
+
|
|
65
123
|
this.ws = await this.bidi.socket
|
|
66
124
|
|
|
67
125
|
this.ws.on('message', (event) => {
|
|
@@ -70,38 +128,48 @@ class LogInspector {
|
|
|
70
128
|
if (params?.type === LOG.TYPE_CONSOLE) {
|
|
71
129
|
let consoleEntry = new ConsoleLogEntry(
|
|
72
130
|
params.level,
|
|
131
|
+
params.source,
|
|
73
132
|
params.text,
|
|
74
133
|
params.timestamp,
|
|
75
134
|
params.type,
|
|
76
135
|
params.method,
|
|
77
|
-
params.realm,
|
|
78
136
|
params.args,
|
|
79
137
|
params.stackTrace,
|
|
80
138
|
)
|
|
81
139
|
|
|
82
140
|
if (filterBy !== undefined) {
|
|
83
141
|
if (params?.level === filterBy.getLevel()) {
|
|
84
|
-
|
|
142
|
+
this.invokeCallbacksWithFilter(LOG.TYPE_CONSOLE_FILTER, consoleEntry, filterBy.getLevel())
|
|
85
143
|
}
|
|
86
144
|
return
|
|
87
145
|
}
|
|
88
146
|
|
|
89
|
-
|
|
147
|
+
this.invokeCallbacks(LOG.TYPE_CONSOLE, consoleEntry)
|
|
90
148
|
}
|
|
91
149
|
})
|
|
150
|
+
|
|
151
|
+
return id
|
|
92
152
|
}
|
|
93
153
|
|
|
94
154
|
/**
|
|
95
155
|
* Listen to JS logs
|
|
96
156
|
* @param callback
|
|
97
157
|
* @param filterBy
|
|
98
|
-
* @returns {Promise<
|
|
158
|
+
* @returns {Promise<number>}
|
|
99
159
|
*/
|
|
100
160
|
async onJavascriptLog(callback, filterBy = undefined) {
|
|
101
161
|
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
|
|
102
162
|
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
|
|
103
163
|
}
|
|
104
164
|
|
|
165
|
+
let id
|
|
166
|
+
|
|
167
|
+
if (filterBy !== undefined) {
|
|
168
|
+
id = this.addCallback(LOG.TYPE_JS_LOGS_FILTER, { callback: callback, filter: filterBy })
|
|
169
|
+
} else {
|
|
170
|
+
id = this.addCallback(LOG.TYPE_JS_LOGS, callback)
|
|
171
|
+
}
|
|
172
|
+
|
|
105
173
|
this.ws = await this.bidi.socket
|
|
106
174
|
|
|
107
175
|
this.ws.on('message', (event) => {
|
|
@@ -110,6 +178,7 @@ class LogInspector {
|
|
|
110
178
|
if (params?.type === LOG.TYPE_JS_LOGS) {
|
|
111
179
|
let jsEntry = new JavascriptLogEntry(
|
|
112
180
|
params.level,
|
|
181
|
+
params.source,
|
|
113
182
|
params.text,
|
|
114
183
|
params.timestamp,
|
|
115
184
|
params.type,
|
|
@@ -118,59 +187,64 @@ class LogInspector {
|
|
|
118
187
|
|
|
119
188
|
if (filterBy !== undefined) {
|
|
120
189
|
if (params?.level === filterBy.getLevel()) {
|
|
121
|
-
|
|
190
|
+
this.invokeCallbacksWithFilter(LOG.TYPE_JS_LOGS_FILTER, jsEntry, filterBy.getLevel())
|
|
122
191
|
}
|
|
123
192
|
return
|
|
124
193
|
}
|
|
125
194
|
|
|
126
|
-
|
|
195
|
+
this.invokeCallbacks(LOG.TYPE_JS_LOGS, jsEntry)
|
|
127
196
|
}
|
|
128
197
|
})
|
|
198
|
+
|
|
199
|
+
return id
|
|
129
200
|
}
|
|
130
201
|
|
|
131
202
|
/**
|
|
132
203
|
* Listen to JS Exceptions
|
|
133
204
|
* @param callback
|
|
134
|
-
* @returns {Promise<
|
|
205
|
+
* @returns {Promise<number>}
|
|
135
206
|
*/
|
|
136
207
|
async onJavascriptException(callback) {
|
|
208
|
+
const id = this.addCallback(LOG.TYPE_JS_EXCEPTION, callback)
|
|
137
209
|
this.ws = await this.bidi.socket
|
|
138
|
-
let enabled = LOG.TYPE_JS_EXCEPTION in this.listener || this.logListener(LOG.TYPE_JS_EXCEPTION)
|
|
139
|
-
this.listener[LOG.TYPE_JS_EXCEPTION].push(callback)
|
|
140
|
-
|
|
141
|
-
if (enabled) {
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
210
|
|
|
145
211
|
this.ws.on('message', (event) => {
|
|
146
212
|
const { params } = JSON.parse(Buffer.from(event.toString()))
|
|
147
213
|
if (params?.type === 'javascript' && params?.level === 'error') {
|
|
148
214
|
let jsErrorEntry = new JavascriptLogEntry(
|
|
149
215
|
params.level,
|
|
216
|
+
params.source,
|
|
150
217
|
params.text,
|
|
151
218
|
params.timestamp,
|
|
152
219
|
params.type,
|
|
153
220
|
params.stackTrace,
|
|
154
221
|
)
|
|
155
222
|
|
|
156
|
-
this.
|
|
157
|
-
listener(jsErrorEntry)
|
|
158
|
-
})
|
|
223
|
+
this.invokeCallbacks(LOG.TYPE_JS_EXCEPTION, jsErrorEntry)
|
|
159
224
|
}
|
|
160
225
|
})
|
|
226
|
+
|
|
227
|
+
return id
|
|
161
228
|
}
|
|
162
229
|
|
|
163
230
|
/**
|
|
164
231
|
* Listen to any logs
|
|
165
232
|
* @param callback
|
|
166
233
|
* @param filterBy
|
|
167
|
-
* @returns {Promise<
|
|
234
|
+
* @returns {Promise<number>}
|
|
168
235
|
*/
|
|
169
236
|
async onLog(callback, filterBy = undefined) {
|
|
170
237
|
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
|
|
171
238
|
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
|
|
172
239
|
}
|
|
173
240
|
|
|
241
|
+
let id
|
|
242
|
+
if (filterBy !== undefined) {
|
|
243
|
+
id = this.addCallback(LOG.TYPE_LOGS_FILTER, { callback: callback, filter: filterBy })
|
|
244
|
+
} else {
|
|
245
|
+
id = this.addCallback(LOG.TYPE_LOGS, callback)
|
|
246
|
+
}
|
|
247
|
+
|
|
174
248
|
this.ws = await this.bidi.socket
|
|
175
249
|
|
|
176
250
|
this.ws.on('message', (event) => {
|
|
@@ -178,6 +252,7 @@ class LogInspector {
|
|
|
178
252
|
if (params?.type === 'javascript') {
|
|
179
253
|
let jsEntry = new JavascriptLogEntry(
|
|
180
254
|
params.level,
|
|
255
|
+
params.source,
|
|
181
256
|
params.text,
|
|
182
257
|
params.timestamp,
|
|
183
258
|
params.type,
|
|
@@ -191,36 +266,46 @@ class LogInspector {
|
|
|
191
266
|
return
|
|
192
267
|
}
|
|
193
268
|
|
|
194
|
-
|
|
269
|
+
if (filterBy !== undefined) {
|
|
270
|
+
if (params?.level === filterBy.getLevel()) {
|
|
271
|
+
{
|
|
272
|
+
this.invokeCallbacksWithFilter(LOG.TYPE_LOGS_FILTER, jsEntry, filterBy.getLevel())
|
|
273
|
+
}
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.invokeCallbacks(LOG.TYPE_LOGS, jsEntry)
|
|
195
279
|
return
|
|
196
280
|
}
|
|
197
281
|
|
|
198
282
|
if (params?.type === 'console') {
|
|
199
283
|
let consoleEntry = new ConsoleLogEntry(
|
|
200
284
|
params.level,
|
|
285
|
+
params.source,
|
|
201
286
|
params.text,
|
|
202
287
|
params.timestamp,
|
|
203
288
|
params.type,
|
|
204
289
|
params.method,
|
|
205
|
-
params.realm,
|
|
206
290
|
params.args,
|
|
207
291
|
params.stackTrace,
|
|
208
292
|
)
|
|
209
293
|
|
|
210
294
|
if (filterBy !== undefined) {
|
|
211
295
|
if (params?.level === filterBy.getLevel()) {
|
|
212
|
-
|
|
296
|
+
this.invokeCallbacksWithFilter(LOG.TYPE_LOGS_FILTER, consoleEntry, filterBy.getLevel())
|
|
213
297
|
}
|
|
214
298
|
return
|
|
215
299
|
}
|
|
216
300
|
|
|
217
|
-
|
|
301
|
+
this.invokeCallbacks(LOG.TYPE_LOGS, consoleEntry)
|
|
218
302
|
return
|
|
219
303
|
}
|
|
220
304
|
|
|
221
305
|
if (params !== undefined && !['console', 'javascript'].includes(params?.type)) {
|
|
222
306
|
let genericEntry = new GenericLogEntry(
|
|
223
307
|
params.level,
|
|
308
|
+
params.source,
|
|
224
309
|
params.text,
|
|
225
310
|
params.timestamp,
|
|
226
311
|
params.type,
|
|
@@ -229,14 +314,19 @@ class LogInspector {
|
|
|
229
314
|
|
|
230
315
|
if (filterBy !== undefined) {
|
|
231
316
|
if (params?.level === filterBy.getLevel()) {
|
|
232
|
-
|
|
317
|
+
{
|
|
318
|
+
this.invokeCallbacksWithFilter(LOG.TYPE_LOGS_FILTER, genericEntry, filterBy.getLevel())
|
|
319
|
+
}
|
|
320
|
+
return
|
|
233
321
|
}
|
|
234
|
-
return
|
|
235
322
|
}
|
|
236
323
|
|
|
237
|
-
|
|
324
|
+
this.invokeCallbacks(LOG.TYPE_LOGS, genericEntry)
|
|
325
|
+
return
|
|
238
326
|
}
|
|
239
327
|
})
|
|
328
|
+
|
|
329
|
+
return id
|
|
240
330
|
}
|
|
241
331
|
|
|
242
332
|
/**
|
|
@@ -244,7 +334,15 @@ class LogInspector {
|
|
|
244
334
|
* @returns {Promise<void>}
|
|
245
335
|
*/
|
|
246
336
|
async close() {
|
|
247
|
-
|
|
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
|
+
}
|
|
248
346
|
}
|
|
249
347
|
}
|
|
250
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
|
package/chrome.js
CHANGED
|
@@ -118,6 +118,8 @@
|
|
|
118
118
|
* [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
|
119
119
|
* [android]: https://chromedriver.chromium.org/getting-started/getting-started---android
|
|
120
120
|
* [webview]: https://developer.chrome.com/multidevice/webview/overview
|
|
121
|
+
*
|
|
122
|
+
* @module selenium-webdriver/chrome
|
|
121
123
|
*/
|
|
122
124
|
|
|
123
125
|
'use strict'
|
package/chromium.js
CHANGED
package/edge.js
CHANGED
package/firefox.js
CHANGED
|
@@ -103,6 +103,8 @@
|
|
|
103
103
|
*
|
|
104
104
|
* [geckodriver release]: https://github.com/mozilla/geckodriver/releases/
|
|
105
105
|
* [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
|
106
|
+
*
|
|
107
|
+
* @module selenium-webdriver/firefox
|
|
106
108
|
*/
|
|
107
109
|
|
|
108
110
|
'use strict'
|
|
@@ -244,6 +246,9 @@ class Options extends Capabilities {
|
|
|
244
246
|
constructor(other) {
|
|
245
247
|
super(other)
|
|
246
248
|
this.setBrowserName(Browser.FIREFOX)
|
|
249
|
+
// Firefox 129 onwards the CDP protocol will not be enabled by default. Setting this preference will enable it.
|
|
250
|
+
// https://fxdx.dev/deprecating-cdp-support-in-firefox-embracing-the-future-with-webdriver-bidi/.
|
|
251
|
+
this.setPreference('remote.active-protocols', 3)
|
|
247
252
|
}
|
|
248
253
|
|
|
249
254
|
/**
|
package/ie.js
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29). You must also apply
|
|
24
24
|
* the system configuration outlined on the Selenium project
|
|
25
25
|
* [wiki](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver)
|
|
26
|
+
*
|
|
27
|
+
* @module selenium-webdriver/ie
|
|
26
28
|
*/
|
|
27
29
|
|
|
28
30
|
'use strict'
|
|
@@ -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
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
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 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')
|
|
24
|
+
|
|
25
|
+
class Script {
|
|
26
|
+
#driver
|
|
27
|
+
#logInspector
|
|
28
|
+
#script
|
|
29
|
+
|
|
30
|
+
constructor(driver) {
|
|
31
|
+
this.#driver = driver
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// This should be done in the constructor.
|
|
35
|
+
// But since it needs to call async methods we cannot do that in the constructor.
|
|
36
|
+
// We can have a separate async method that initialises the Script instance.
|
|
37
|
+
// However, that pattern does not allow chaining the methods as we would like the user to use it.
|
|
38
|
+
// Since it involves awaiting to get the instance and then another await to call the method.
|
|
39
|
+
// Using this allows the user to do this "await driver.script().addJavaScriptErrorHandler(callback)"
|
|
40
|
+
async #init() {
|
|
41
|
+
if (this.#logInspector !== undefined) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
this.#logInspector = await logInspector(this.#driver)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async #initScript() {
|
|
48
|
+
if (this.#script !== undefined) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
this.#script = await scriptManager([], this.#driver)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async addJavaScriptErrorHandler(callback) {
|
|
55
|
+
await this.#init()
|
|
56
|
+
return await this.#logInspector.onJavascriptException(callback)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async removeJavaScriptErrorHandler(id) {
|
|
60
|
+
await this.#init()
|
|
61
|
+
await this.#logInspector.removeCallback(id)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async addConsoleMessageHandler(callback) {
|
|
65
|
+
await this.#init()
|
|
66
|
+
return this.#logInspector.onConsoleEntry(callback)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async removeConsoleMessageHandler(id) {
|
|
70
|
+
await this.#init()
|
|
71
|
+
|
|
72
|
+
await this.#logInspector.removeCallback(id)
|
|
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
|
+
|
|
126
|
+
module.exports = Script
|
package/lib/webdriver.js
CHANGED
|
@@ -43,6 +43,7 @@ const { isObject } = require('./util')
|
|
|
43
43
|
const BIDI = require('../bidi')
|
|
44
44
|
const { PinnedScript } = require('./pinnedScript')
|
|
45
45
|
const JSZip = require('jszip')
|
|
46
|
+
const Script = require('./script')
|
|
46
47
|
|
|
47
48
|
// Capability names that are defined in the W3C spec.
|
|
48
49
|
const W3C_CAPABILITY_NAMES = new Set([
|
|
@@ -654,6 +655,7 @@ function filterNonW3CCaps(capabilities) {
|
|
|
654
655
|
* @implements {IWebDriver}
|
|
655
656
|
*/
|
|
656
657
|
class WebDriver {
|
|
658
|
+
#script = undefined
|
|
657
659
|
/**
|
|
658
660
|
* @param {!(./session.Session|IThenable<!./session.Session>)} session Either
|
|
659
661
|
* a known session or a promise that will be resolved to a session.
|
|
@@ -1104,6 +1106,16 @@ class WebDriver {
|
|
|
1104
1106
|
return new TargetLocator(this)
|
|
1105
1107
|
}
|
|
1106
1108
|
|
|
1109
|
+
script() {
|
|
1110
|
+
// The Script calls the LogInspector which maintains state of the callbacks.
|
|
1111
|
+
// Returning a new instance of the same driver will not work while removing callbacks.
|
|
1112
|
+
if (this.#script === undefined) {
|
|
1113
|
+
this.#script = new Script(this)
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return this.#script
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1107
1119
|
validatePrintPageParams(keys, object) {
|
|
1108
1120
|
let page = {}
|
|
1109
1121
|
let margin = {}
|
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,33 +23,37 @@
|
|
|
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
|
-
"
|
|
33
|
-
"eslint": "^9.
|
|
32
|
+
"@eslint/js": "^9.5.0",
|
|
33
|
+
"clean-jsdoc-theme": "^4.3.0",
|
|
34
|
+
"eslint": "^9.5.0",
|
|
34
35
|
"eslint-config-prettier": "^9.1.0",
|
|
35
36
|
"eslint-plugin-mocha": "^10.4.3",
|
|
36
|
-
"eslint-plugin-n": "^17.
|
|
37
|
+
"eslint-plugin-n": "^17.9.0",
|
|
37
38
|
"eslint-plugin-no-only-tests": "^3.1.0",
|
|
38
39
|
"eslint-plugin-prettier": "^5.1.3",
|
|
39
40
|
"express": "^4.19.2",
|
|
40
|
-
"globals": "^15.
|
|
41
|
-
"
|
|
41
|
+
"globals": "^15.6.0",
|
|
42
|
+
"has-flag": "^4.0.0",
|
|
43
|
+
"jsdoc": "^4.0.3",
|
|
44
|
+
"mocha": "^10.5.1",
|
|
42
45
|
"mocha-junit-reporter": "^2.2.1",
|
|
43
|
-
"multer": "
|
|
44
|
-
"prettier": "^3.2
|
|
46
|
+
"multer": "1.4.5-lts.1",
|
|
47
|
+
"prettier": "^3.3.2",
|
|
45
48
|
"serve-index": "^1.9.1",
|
|
46
|
-
"sinon": "^17.0.1"
|
|
49
|
+
"sinon": "^17.0.1",
|
|
50
|
+
"supports-color": "^9.4.0"
|
|
47
51
|
},
|
|
48
52
|
"scripts": {
|
|
49
53
|
"lint": "eslint .",
|
|
50
54
|
"lint:fix": "eslint . --fix",
|
|
51
|
-
"test": "
|
|
52
|
-
"
|
|
55
|
+
"test": "bazel test //javascript/node/selenium-webdriver/...",
|
|
56
|
+
"generate-docs": "jsdoc --configure jsdoc_conf.json --verbose"
|
|
53
57
|
},
|
|
54
58
|
"mocha": {
|
|
55
59
|
"recursive": true,
|
package/safari.js
CHANGED
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 = {
|