selenium-webdriver 4.7.1 → 4.8.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,19 @@
1
+ ## v4.8.0
2
+
3
+ #### :rocket: New Feature
4
+
5
+ * Add initial BiDi Support (#11395)
6
+ * Add window wrappers getSize and setSize
7
+ * Add BiDi browser context commands (#11473)
8
+ * Add BiDi methods to listen to js logs and any type of logs
9
+ * Add BiDi filtering capability to LogInspector (#11495)
10
+ * Add comment with name of large JS executions (#11038)
11
+
12
+ #### :nail_care: Polish
13
+
14
+ * Add CDP support for v109 and remove v106
15
+ * Deprecate setHeadless() in Chrome and Firefox (#11467)
16
+
1
17
  ## v4.7.1
2
18
 
3
19
  #### :nail_care: Polish
package/LICENSE CHANGED
@@ -187,7 +187,7 @@
187
187
  same "printed page" as the copyright notice for easier
188
188
  identification within third-party archives.
189
189
 
190
- Copyright 2022 Software Freedom Conservancy (SFC)
190
+ Copyright 2023 Software Freedom Conservancy (SFC)
191
191
 
192
192
  Licensed under the Apache License, Version 2.0 (the "License");
193
193
  you may not use this file except in compliance with the License.
package/NOTICE CHANGED
@@ -1,2 +1,2 @@
1
- Copyright 2011-2022 Software Freedom Conservancy
1
+ Copyright 2011-2023 Software Freedom Conservancy
2
2
  Copyright 2004-2011 Selenium committers
@@ -0,0 +1,194 @@
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
+ class BrowsingContext {
19
+ constructor(driver) {
20
+ this._driver = driver
21
+ }
22
+
23
+ async init({ browsingContextId, type, referenceContext }) {
24
+ if (!(await this._driver.getCapabilities()).get('webSocketUrl')) {
25
+ throw Error('WebDriver instance must support BiDi protocol')
26
+ }
27
+
28
+ if (type != undefined && !['window', 'tab'].includes(type)) {
29
+ throw Error(`Valid types are 'window' & 'tab'. Received: ${type}`)
30
+ }
31
+
32
+ this.bidi = await this._driver.getBidi()
33
+ this._id =
34
+ browsingContextId == undefined
35
+ ? (await this.create(type, referenceContext))['result']['context']
36
+ : browsingContextId
37
+ }
38
+
39
+ /**
40
+ * Creates a browsing context for the given type and referenceContext
41
+ */
42
+ async create(type, referenceContext) {
43
+ const params = {
44
+ method: 'browsingContext.create',
45
+ params: {
46
+ type: type,
47
+ referenceContext: referenceContext,
48
+ },
49
+ }
50
+ return await this.bidi.send(params)
51
+ }
52
+
53
+ /**
54
+ * @returns id
55
+ */
56
+ get id() {
57
+ return this._id
58
+ }
59
+
60
+ /**
61
+ * @param url the url to navigate to
62
+ * @param readinessState type of readiness state: "none" / "interactive" / "complete"
63
+ * @returns NavigateResult object
64
+ */
65
+ async navigate(url, readinessState = undefined) {
66
+ if (
67
+ readinessState != undefined &&
68
+ !['none', 'interactive', 'complete'].includes(readinessState)
69
+ ) {
70
+ throw Error(
71
+ `Valid readiness states are 'none', 'interactive' & 'complete'. Received: ${readinessState}`
72
+ )
73
+ }
74
+
75
+ const params = {
76
+ method: 'browsingContext.navigate',
77
+ params: {
78
+ context: this._id,
79
+ url: url,
80
+ wait: readinessState,
81
+ },
82
+ }
83
+ const navigateResult = (await this.bidi.send(params))['result']
84
+
85
+ return new NavigateResult(
86
+ navigateResult['url'],
87
+ navigateResult['navigation']
88
+ )
89
+ }
90
+
91
+ /**
92
+ * @param maxDepth the max depth of the descendents of browsing context tree
93
+ * @returns BrowsingContextInfo object
94
+ */
95
+ async getTree(maxDepth = undefined) {
96
+ const params = {
97
+ method: 'browsingContext.getTree',
98
+ params: {
99
+ root: this._id,
100
+ maxDepth: maxDepth,
101
+ },
102
+ }
103
+
104
+ let result = await this.bidi.send(params)
105
+ if ('error' in result) {
106
+ throw Error(result['error'])
107
+ }
108
+
109
+ result = result['result']['contexts'][0]
110
+ return new BrowsingContextInfo(
111
+ result['context'],
112
+ result['url'],
113
+ result['children'],
114
+ result['parent']
115
+ )
116
+ }
117
+
118
+ /**
119
+ * Closes the browing context
120
+ * @returns {Promise<void>}
121
+ */
122
+ async close() {
123
+ const params = {
124
+ method: 'browsingContext.close',
125
+ params: {
126
+ context: this._id,
127
+ },
128
+ }
129
+ await this.bidi.send(params)
130
+ }
131
+ }
132
+
133
+ class NavigateResult {
134
+ constructor(url, navigationId) {
135
+ this._url = url
136
+ this._navigationId = navigationId
137
+ }
138
+
139
+ get url() {
140
+ return this._url
141
+ }
142
+
143
+ get navigationId() {
144
+ return this._navigationId
145
+ }
146
+ }
147
+
148
+ class BrowsingContextInfo {
149
+ constructor(id, url, children, parentBrowsingContext) {
150
+ this._id = id
151
+ this._url = url
152
+ this._children = children
153
+ this._parentBrowsingContext = parentBrowsingContext
154
+ }
155
+
156
+ get id() {
157
+ return this._id
158
+ }
159
+
160
+ get url() {
161
+ return this._url
162
+ }
163
+
164
+ get children() {
165
+ return this._children
166
+ }
167
+
168
+ get parentBrowsingContext() {
169
+ return this._parentBrowsingContext
170
+ }
171
+ }
172
+
173
+ /**
174
+ * initiate browsing context instance and return
175
+ * @param driver
176
+ * @param browsingContextId The browsing context of current window/tab
177
+ * @param type "window" or "tab"
178
+ * @param referenceContext To get a browsing context for this reference if passed
179
+ * @returns {Promise<BrowsingContext>}
180
+ */
181
+ async function getBrowsingContextInstance(
182
+ driver,
183
+ { browsingContextId, type, referenceContext }
184
+ ) {
185
+ let instance = new BrowsingContext(driver)
186
+ await instance.init({ browsingContextId, type, referenceContext })
187
+ return instance
188
+ }
189
+
190
+ /**
191
+ * API
192
+ * @type {function(*, {*,*,*}): Promise<BrowsingContext>}
193
+ */
194
+ module.exports = getBrowsingContextInstance
@@ -0,0 +1,46 @@
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
+ class FilterBy {
19
+ constructor(level) {
20
+ this.level_ = level
21
+ }
22
+
23
+ static logLevel(level) {
24
+ if (
25
+ level === undefined ||
26
+ (level != undefined &&
27
+ !['debug', 'error', 'info', 'warning'].includes(level))
28
+ ) {
29
+ throw Error(
30
+ `Please pass valid log level. Valid log levels are 'debug', 'error', 'info' and 'warning'. Received: ${level}`
31
+ )
32
+ }
33
+
34
+ return new FilterBy(level)
35
+ }
36
+
37
+ getLevel() {
38
+ return this.level_
39
+ }
40
+ }
41
+
42
+ // PUBLIC API
43
+
44
+ module.exports = {
45
+ FilterBy,
46
+ }
package/bidi/index.js ADDED
@@ -0,0 +1,224 @@
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 { EventEmitter } = require('node:events')
19
+ const WebSocket = require('ws')
20
+
21
+ const RESPONSE_TIMEOUT = 1000 * 30
22
+
23
+ class Index extends EventEmitter {
24
+ id = 0
25
+ isConnected = false
26
+ events = []
27
+ browsingContexts = []
28
+
29
+ /**
30
+ * Create a new websocket connection
31
+ * @param _webSocketUrl
32
+ */
33
+ constructor (_webSocketUrl) {
34
+ super()
35
+ this.isConnected = false
36
+ this._ws = new WebSocket(_webSocketUrl)
37
+ this._ws.on('open', () => {
38
+ this.isConnected = true
39
+ })
40
+ }
41
+
42
+ /**
43
+ * Resolve connection
44
+ * @returns {Promise<unknown>}
45
+ */
46
+ async waitForConnection () {
47
+ return new Promise((resolve) => {
48
+ if (this.isConnected) {
49
+ resolve()
50
+ } else {
51
+ this._ws.once('open', () => {
52
+ resolve()
53
+ })
54
+ }
55
+ })
56
+ }
57
+
58
+ /**
59
+ * @returns {WebSocket}
60
+ */
61
+ get socket () {
62
+ return this._ws
63
+ }
64
+
65
+ /**
66
+ * @returns {boolean|*}
67
+ */
68
+ get isConnected () {
69
+ return this.isConnected
70
+ }
71
+
72
+ /**
73
+ * Sends a bidi request
74
+ * @param params
75
+ * @returns {Promise<unknown>}
76
+ */
77
+ async send (params) {
78
+ if (!this.isConnected) {
79
+ await this.waitForConnection()
80
+ }
81
+
82
+ const id = ++this.id
83
+
84
+ this._ws.send(JSON.stringify({ id, ...params }))
85
+
86
+ return new Promise((resolve, reject) => {
87
+ const timeoutId = setTimeout(() => {
88
+ reject(new Error(`Request with id ${id} timed out`))
89
+ handler.off('message', listener)
90
+ }, RESPONSE_TIMEOUT)
91
+
92
+ const listener = (data) => {
93
+ try {
94
+ const payload = JSON.parse(data.toString())
95
+ if (payload.id === id) {
96
+ clearTimeout(timeoutId)
97
+ handler.off('message', listener)
98
+ resolve(payload)
99
+ }
100
+ } catch (err) {
101
+ log.error(`Failed parse message: ${err.message}`)
102
+ }
103
+ }
104
+
105
+ const handler = this._ws.on('message', listener)
106
+ })
107
+ }
108
+
109
+ /**
110
+ * Subscribe to events
111
+ * @param events
112
+ * @param browsingContexts
113
+ * @returns {Promise<void>}
114
+ */
115
+ async subscribe (events, browsingContexts) {
116
+ function toArray (arg) {
117
+ if (arg === undefined) {
118
+ return []
119
+ }
120
+
121
+ return Array.isArray(arg) ? [...arg] : [arg]
122
+ }
123
+
124
+ const eventsArray = toArray(events)
125
+ const contextsArray = toArray(browsingContexts)
126
+
127
+ const params = {
128
+ method: 'session.subscribe', params: {},
129
+ }
130
+
131
+ if (eventsArray.length && eventsArray.some(
132
+ event => typeof event !== 'string')) {
133
+ throw new TypeError('events should be string or string array')
134
+ }
135
+
136
+ if (contextsArray.length && contextsArray.some(
137
+ context => typeof context !== 'string')) {
138
+ throw new TypeError('browsingContexts should be string or string array')
139
+ }
140
+
141
+ if (eventsArray.length) {
142
+ params.params.events = eventsArray
143
+ }
144
+
145
+ if (contextsArray.length) {
146
+ params.params.contexts = contextsArray
147
+ }
148
+
149
+ await this.send(params)
150
+ }
151
+
152
+ /**
153
+ * Unsubscribe to events
154
+ * @param events
155
+ * @param browsingContexts
156
+ * @returns {Promise<void>}
157
+ */
158
+ async unsubscribe (events, browsingContexts) {
159
+ if (typeof events === 'string') {
160
+ this.events = this.events.filter(event => event !== events)
161
+ } else if (Array.isArray(events)) {
162
+ this.events = this.events.filter(event => !events.includes(event))
163
+ }
164
+
165
+ if (typeof browsingContexts === 'string') {
166
+ this.browsingContexts.pop()
167
+ } else if (Array.isArray(browsingContexts)) {
168
+ this.browsingContexts =
169
+ this.browsingContexts.filter(id => !browsingContexts.includes(id))
170
+ }
171
+
172
+ const params = {
173
+ method: 'session.unsubscribe', params: {
174
+ events: this.events,
175
+ }
176
+ }
177
+
178
+ if (this.browsingContexts.length > 0) {
179
+ params.params.contexts = this.browsingContexts
180
+ }
181
+
182
+ await this.send(params)
183
+ }
184
+
185
+ /**
186
+ * Get Bidi Status
187
+ * @returns {Promise<*>}
188
+ */
189
+ get status () {
190
+ return this.send({
191
+ method: 'session.status', params: {}
192
+ })
193
+ }
194
+
195
+ /**
196
+ * Close ws connection.
197
+ * @returns {Promise<unknown>}
198
+ */
199
+ close () {
200
+ const closeWebSocket = (callback) => {
201
+ // don't close if it's already closed
202
+ if (this._ws.readyState === 3) {
203
+ callback()
204
+ } else {
205
+ // don't notify on user-initiated shutdown ('disconnect' event)
206
+ this._ws.removeAllListeners('close')
207
+ this._ws.once('close', () => {
208
+ this._ws.removeAllListeners()
209
+ callback()
210
+ })
211
+ this._ws.close()
212
+ }
213
+ }
214
+ return new Promise((fulfill, reject) => {
215
+ closeWebSocket(fulfill)
216
+ })
217
+ }
218
+ }
219
+
220
+ /**
221
+ * API
222
+ * @type {function(*): Promise<Index>}
223
+ */
224
+ module.exports = Index
@@ -0,0 +1,87 @@
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
+ 'use strict'
19
+
20
+ class BaseLogEntry {
21
+ constructor (level, text, timeStamp, stackTrace) {
22
+ this._level = level
23
+ this._text = text
24
+ this._timeStamp = timeStamp
25
+ this._stackTrace = stackTrace
26
+ }
27
+
28
+ get level () {
29
+ return this._level
30
+ }
31
+
32
+ get text () {
33
+ return this._text
34
+ }
35
+
36
+ get timeStamp () {
37
+ return this._timeStamp
38
+ }
39
+
40
+ get stackTrace () {
41
+ return this._stackTrace
42
+ }
43
+ }
44
+
45
+ class GenericLogEntry extends BaseLogEntry {
46
+ constructor (level, text, timeStamp, type, stackTrace) {
47
+ super(level, text, timeStamp, stackTrace)
48
+ this._type = type
49
+ }
50
+
51
+ get type () {
52
+ return this._type
53
+ }
54
+ }
55
+
56
+ class ConsoleLogEntry extends GenericLogEntry {
57
+ constructor (level, text, timeStamp, type, method, realm, args, stackTrace) {
58
+ super(level, text, timeStamp, type, stackTrace)
59
+ this._method = method
60
+ this._realm = realm
61
+ this._args = args
62
+ }
63
+
64
+ get method () {
65
+ return this._method
66
+ }
67
+
68
+ get realm () {
69
+ return this._realm
70
+ }
71
+
72
+ get args () {
73
+ return this._args
74
+ }
75
+ }
76
+
77
+ class JavascriptLogEntry extends GenericLogEntry {
78
+ constructor (level, text, timeStamp, type, stackTrace) {
79
+ super(level, text, timeStamp, type, stackTrace)
80
+ }
81
+ }
82
+
83
+ // PUBLIC API
84
+
85
+ module.exports = {
86
+ BaseLogEntry, GenericLogEntry, ConsoleLogEntry, JavascriptLogEntry,
87
+ }
@@ -0,0 +1,269 @@
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 { FilterBy } = require('./filterBy')
19
+ const { ConsoleLogEntry, JavascriptLogEntry, GenericLogEntry } = require('./logEntries')
20
+
21
+ const LOG = {
22
+ TYPE_CONSOLE : 'console',
23
+ TYPE_JS_LOGS : 'javascript',
24
+ }
25
+
26
+ class LogInspector {
27
+ bidi
28
+ ws
29
+
30
+ constructor (driver, browsingContextIds) {
31
+ this._driver = driver
32
+ this._browsingContextIds = browsingContextIds
33
+ this.listener = {}
34
+ }
35
+
36
+ /**
37
+ * Subscribe to log event
38
+ * @returns {Promise<void>}
39
+ */
40
+ async init () {
41
+ this.bidi = await this._driver.getBidi()
42
+ await this.bidi.subscribe('log.entryAdded', this._browsingContextIds)
43
+ }
44
+
45
+ /**
46
+ * @param kind
47
+ */
48
+ logListener (kind) {
49
+ if (!(kind in this.listener)) {
50
+ this.listener[kind] = []
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Listen to Console logs
56
+ * @param callback
57
+ * @returns {Promise<void>}
58
+ */
59
+ async onConsoleEntry(callback, filterBy = undefined) {
60
+ if (filterBy != undefined && !(filterBy instanceof FilterBy)) {
61
+ throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
62
+ }
63
+
64
+ this.ws = await this.bidi.socket
65
+
66
+ this.ws.on('message', (event) => {
67
+ const { params } = JSON.parse(Buffer.from(event.toString()))
68
+
69
+ if (params?.type === LOG.TYPE_CONSOLE) {
70
+ let consoleEntry = new ConsoleLogEntry(
71
+ params.level,
72
+ params.text,
73
+ params.timestamp,
74
+ params.type,
75
+ params.method,
76
+ params.realm,
77
+ params.args,
78
+ params.stackTrace
79
+ )
80
+
81
+ if (filterBy != undefined) {
82
+ if (params?.level === filterBy.getLevel()) {
83
+ callback(consoleEntry)
84
+ }
85
+ return
86
+ }
87
+
88
+ callback(consoleEntry)
89
+ }
90
+ })
91
+ }
92
+
93
+ /**
94
+ * Listen to JS logs
95
+ * @param callback
96
+ * @returns {Promise<void>}
97
+ */
98
+ async onJavascriptLog(callback, filterBy = undefined) {
99
+ if (filterBy != undefined && !(filterBy instanceof FilterBy)) {
100
+ throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
101
+ }
102
+
103
+ this.ws = await this.bidi.socket
104
+
105
+ this.ws.on('message', (event) => {
106
+ const { params } = JSON.parse(Buffer.from(event.toString()))
107
+
108
+ if (params?.type === LOG.TYPE_JS_LOGS) {
109
+ let jsEntry = new JavascriptLogEntry(
110
+ params.level,
111
+ params.text,
112
+ params.timestamp,
113
+ params.type,
114
+ params.stackTrace
115
+ )
116
+
117
+ if (filterBy != undefined) {
118
+ if (params?.level === filterBy.getLevel()) {
119
+ callback(jsEntry)
120
+ }
121
+ return
122
+ }
123
+
124
+ callback(jsEntry)
125
+ }
126
+ })
127
+ }
128
+
129
+ /**
130
+ * Listen to JS Exceptions
131
+ * @param callback
132
+ * @returns {Promise<void>}
133
+ */
134
+ async onJavascriptException(callback) {
135
+ this.ws = await this.bidi.socket
136
+ let enabled =
137
+ LOG.TYPE_JS_EXCEPTION in this.listener ||
138
+ this.logListener(LOG.TYPE_JS_EXCEPTION)
139
+ this.listener[LOG.TYPE_JS_EXCEPTION].push(callback)
140
+
141
+ if (enabled) {
142
+ return
143
+ }
144
+
145
+ this.ws.on('message', (event) => {
146
+ const { params } = JSON.parse(Buffer.from(event.toString()))
147
+ if (params?.type === 'javascript' && params?.level === 'error') {
148
+ let jsErrorEntry = new JavascriptLogEntry(
149
+ params.level,
150
+ params.text,
151
+ params.timestamp,
152
+ params.type,
153
+ params.stackTrace
154
+ )
155
+
156
+ this.listener[LOG.TYPE_JS_EXCEPTION].forEach((listener) => {
157
+ listener(jsErrorEntry)
158
+ })
159
+ }
160
+ })
161
+ }
162
+
163
+ /**
164
+ * Listen to any logs
165
+ * @param callback
166
+ * @returns {Promise<void>}
167
+ */
168
+ async onLog(callback, filterBy = undefined) {
169
+ if (filterBy != undefined && !(filterBy instanceof FilterBy)) {
170
+ throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
171
+ }
172
+
173
+ this.ws = await this.bidi.socket
174
+
175
+ this.ws.on('message', (event) => {
176
+ const { params } = JSON.parse(Buffer.from(event.toString()))
177
+ if (params?.type === 'javascript') {
178
+ let jsEntry = new JavascriptLogEntry(
179
+ params.level,
180
+ params.text,
181
+ params.timestamp,
182
+ params.type,
183
+ params.stackTrace
184
+ )
185
+
186
+ if (filterBy != undefined) {
187
+ if (params?.level === filterBy.getLevel()) {
188
+ callback(jsEntry)
189
+ }
190
+ return
191
+ }
192
+
193
+ callback(jsEntry)
194
+ return
195
+ }
196
+
197
+ if (params?.type === 'console') {
198
+ let consoleEntry = new ConsoleLogEntry(
199
+ params.level,
200
+ params.text,
201
+ params.timestamp,
202
+ params.type,
203
+ params.method,
204
+ params.realm,
205
+ params.args,
206
+ params.stackTrace
207
+ )
208
+
209
+ if (filterBy != undefined) {
210
+ if (params?.level === filterBy.getLevel()) {
211
+ callback(consoleEntry)
212
+ }
213
+ return
214
+ }
215
+
216
+ callback(consoleEntry)
217
+ return
218
+ }
219
+
220
+ if (
221
+ params != undefined &&
222
+ !['console', 'javascript'].includes(params?.type)
223
+ ) {
224
+ let genericEntry = new GenericLogEntry(
225
+ params.level,
226
+ params.text,
227
+ params.timestamp,
228
+ params.type,
229
+ params.stackTrace
230
+ )
231
+
232
+ if (filterBy != undefined) {
233
+ if (params?.level === filterBy.getLevel()) {
234
+ callback(genericEntry)
235
+ }
236
+ return
237
+ }
238
+
239
+ callback(genericEntry)
240
+ }
241
+ })
242
+ }
243
+
244
+ /**
245
+ * Unsubscribe to log event
246
+ * @returns {Promise<void>}
247
+ */
248
+ async close () {
249
+ await this.bidi.unsubscribe('log.entryAdded', this._browsingContextIds)
250
+ }
251
+ }
252
+
253
+ /**
254
+ * initiate inspector instance and return
255
+ * @param driver
256
+ * @param browsingContextIds
257
+ * @returns {Promise<LogInspector>}
258
+ */
259
+ async function getLogInspectorInstance (driver, browsingContextIds) {
260
+ let instance = new LogInspector(driver, browsingContextIds)
261
+ await instance.init()
262
+ return instance
263
+ }
264
+
265
+ /**
266
+ * API
267
+ * @type {function(*, *): Promise<LogInspector>}
268
+ */
269
+ module.exports = getLogInspectorInstance
package/chromium.js CHANGED
@@ -310,6 +310,15 @@ class Options extends Capabilities {
310
310
  }
311
311
 
312
312
  /**
313
+ * @deprecated Use {@link Options#addArguments} instead.
314
+ * @example
315
+ * options.addArguments('--headless=chrome'); (or)
316
+ * options.addArguments('--headless');
317
+ * @example
318
+ *
319
+ * Recommended to use '--headless=chrome' as argument for browsers v94-108.
320
+ * Recommended to use '--headless=new' as argument for browsers v109+.
321
+ *
313
322
  * Configures the driver to start the browser in headless mode.
314
323
  *
315
324
  * > __NOTE:__ Resizing the browser window in headless mode is only supported
@@ -600,6 +609,14 @@ class Options extends Capabilities {
600
609
  }
601
610
  return this
602
611
  }
612
+
613
+ /**
614
+ * Enable bidi connection
615
+ * @returns {!Capabilities}
616
+ */
617
+ enableBidi() {
618
+ return this.set('webSocketUrl', true)
619
+ }
603
620
  }
604
621
 
605
622
  /**
package/firefox.js CHANGED
@@ -299,6 +299,10 @@ class Options extends Capabilities {
299
299
  }
300
300
 
301
301
  /**
302
+ * @deprecated Use {@link Options#addArguments} instead.
303
+ * @example
304
+ * options.addArguments('-headless');
305
+ * @example
302
306
  * Configures the geckodriver to start Firefox in headless mode.
303
307
  *
304
308
  * @return {!Options} A self reference.
@@ -424,6 +428,14 @@ class Options extends Capabilities {
424
428
  enableDebugger() {
425
429
  return this.set('moz:debuggerAddress', true)
426
430
  }
431
+
432
+ /**
433
+ * Enable bidi connection
434
+ * @returns {!Capabilities}
435
+ */
436
+ enableBidi() {
437
+ return this.set('webSocketUrl', true)
438
+ }
427
439
  }
428
440
 
429
441
  /**
package/http/index.js CHANGED
@@ -54,7 +54,7 @@ function getRequestOptions(aUrl) {
54
54
  options.search = null
55
55
  options.hash = null
56
56
  options.path = options.pathname
57
- options.hostname = options.hostname === 'localhost' ? '127.0.0.1' : options.hostname // To support Node 17 and above. Refer https://github.com/nodejs/node/issues/40702 for details.
57
+ options.hostname = options.hostname === 'localhost' ? '127.0.0.1' : options.hostname // To support Node 17 and above. Refer https://github.com/nodejs/node/issues/40702 for details.
58
58
  return options
59
59
  }
60
60
 
package/index.js CHANGED
@@ -39,6 +39,8 @@ const session = require('./lib/session')
39
39
  const until = require('./lib/until')
40
40
  const webdriver = require('./lib/webdriver')
41
41
  const select = require('./lib/select')
42
+ const LogInspector = require('./bidi/logInspector')
43
+ const BrowsingContext = require('./bidi/browsingContext')
42
44
 
43
45
  const Browser = capabilities.Browser
44
46
  const Capabilities = capabilities.Capabilities
@@ -795,3 +797,5 @@ exports.logging = logging
795
797
  exports.promise = promise
796
798
  exports.until = until
797
799
  exports.Select = select.Select
800
+ exports.LogInspector = LogInspector
801
+ exports.BrowsingContext = BrowsingContext
package/lib/http.js CHANGED
@@ -182,14 +182,14 @@ class InternalTypeError extends TypeError {}
182
182
  * @param params
183
183
  * @return {!Command} The transformed command to execute.
184
184
  */
185
- function toExecuteAtomCommand(command, atom, ...params) {
185
+ function toExecuteAtomCommand(command, atom, name, ...params) {
186
186
  if (typeof atom !== 'function') {
187
187
  throw new InternalTypeError('atom is not a function: ' + typeof atom)
188
188
  }
189
189
 
190
190
  return new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
191
191
  .setParameter('sessionId', command.getParameter('sessionId'))
192
- .setParameter('script', `return (${atom}).apply(null, arguments)`)
192
+ .setParameter('script', `/* ${name} */return (${atom}).apply(null, arguments)`)
193
193
  .setParameter(
194
194
  'args',
195
195
  params.map((param) => command.getParameter(param))
@@ -252,7 +252,7 @@ const W3C_COMMAND_MAP = new Map([
252
252
  [
253
253
  cmd.Name.FIND_ELEMENTS_RELATIVE,
254
254
  (cmd) => {
255
- return toExecuteAtomCommand(cmd, Atom.FIND_ELEMENTS, 'args')
255
+ return toExecuteAtomCommand(cmd, Atom.FIND_ELEMENTS, 'findElements', 'args')
256
256
  },
257
257
  ],
258
258
  [
@@ -272,7 +272,7 @@ const W3C_COMMAND_MAP = new Map([
272
272
  [
273
273
  cmd.Name.GET_ELEMENT_ATTRIBUTE,
274
274
  (cmd) => {
275
- return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name')
275
+ return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'getAttribute', 'id', 'name')
276
276
  },
277
277
  ],
278
278
  [
@@ -308,7 +308,7 @@ const W3C_COMMAND_MAP = new Map([
308
308
  [
309
309
  cmd.Name.IS_ELEMENT_DISPLAYED,
310
310
  (cmd) => {
311
- return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id')
311
+ return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'isDisplayed', 'id')
312
312
  },
313
313
  ],
314
314
 
package/lib/webdriver.js CHANGED
@@ -40,6 +40,7 @@ const cdpTargets = ['page', 'browser']
40
40
  const { Credential } = require('./virtual_authenticator')
41
41
  const webElement = require('./webelement')
42
42
  const { isObject } = require('./util')
43
+ const BIDI = require('../bidi')
43
44
 
44
45
  // Capability names that are defined in the W3C spec.
45
46
  const W3C_CAPABILITY_NAMES = new Set([
@@ -1225,7 +1226,7 @@ class WebDriver {
1225
1226
  this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
1226
1227
  return new Promise((resolve, reject) => {
1227
1228
  try {
1228
- this._wsConnection = new WebSocket(this._wsUrl)
1229
+ this._wsConnection = new WebSocket(this._wsUrl.replace('localhost', '127.0.0.1'))
1229
1230
  this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
1230
1231
  } catch (err) {
1231
1232
  reject(err)
@@ -1271,6 +1272,16 @@ class WebDriver {
1271
1272
  this._cdpConnection.execute('Target.getTargets')
1272
1273
  }
1273
1274
 
1275
+ /**
1276
+ * Initiates bidi connection using 'webSocketUrl'
1277
+ * @returns {BIDI}
1278
+ */
1279
+ async getBidi() {
1280
+ const caps = await this.getCapabilities()
1281
+ let WebSocketUrl = caps['map_'].get('webSocketUrl')
1282
+ return new BIDI(WebSocketUrl.replace('localhost', '127.0.0.1'))
1283
+ }
1284
+
1274
1285
  /**
1275
1286
  * Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
1276
1287
  * @param {string} debuggerAddress
@@ -2142,6 +2153,37 @@ class Window {
2142
2153
  new command.Command(command.Name.FULLSCREEN_WINDOW)
2143
2154
  )
2144
2155
  }
2156
+
2157
+ /**
2158
+ * Gets the width and height of the current window
2159
+ * @param windowHandle
2160
+ * @returns {Promise<{width: *, height: *}>}
2161
+ */
2162
+ async getSize(windowHandle = 'current') {
2163
+ if (windowHandle !== 'current') {
2164
+ console.warn(`Only 'current' window is supported for W3C compatible browsers.`);
2165
+ }
2166
+
2167
+ const rect = await this.getRect();
2168
+ return {height: rect.height, width: rect.width};
2169
+ }
2170
+
2171
+ /**
2172
+ * Sets the width and height of the current window. (window.resizeTo)
2173
+ * @param x
2174
+ * @param y
2175
+ * @param width
2176
+ * @param height
2177
+ * @param windowHandle
2178
+ * @returns {Promise<void>}
2179
+ */
2180
+ async setSize({x = 0, y = 0, width = 0, height = 0}, windowHandle = 'current') {
2181
+ if (windowHandle !== 'current') {
2182
+ console.warn(`Only 'current' window is supported for W3C compatible browsers.`);
2183
+ }
2184
+
2185
+ await this.setRect({x, y, width, height});
2186
+ }
2145
2187
  }
2146
2188
 
2147
2189
  /**
@@ -2863,7 +2905,7 @@ class WebElement {
2863
2905
  */
2864
2906
  submit() {
2865
2907
  const script =
2866
- 'var form = arguments[0];\n' +
2908
+ '/* submitForm */var form = arguments[0];\n' +
2867
2909
  'while (form.nodeName != "FORM" && form.parentNode) {\n' +
2868
2910
  ' form = form.parentNode;\n' +
2869
2911
  '}\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selenium-webdriver",
3
- "version": "4.7.1",
3
+ "version": "4.8.0",
4
4
  "description": "The official WebDriver JavaScript bindings from the Selenium project",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -25,17 +25,16 @@
25
25
  "dependencies": {
26
26
  "jszip": "^3.10.0",
27
27
  "tmp": "^0.2.1",
28
- "ws": ">=8.7.0"
28
+ "ws": ">=8.11.0"
29
29
  },
30
30
  "devDependencies": {
31
- "eslint": "^8.27.0",
32
- "eslint-config-prettier": "^8.5.0",
31
+ "eslint": "^8.31.0",
32
+ "eslint-config-prettier": "^8.6.0",
33
33
  "eslint-plugin-no-only-tests": "^3.1.0",
34
34
  "eslint-plugin-node": "^11.1.0",
35
- "eslint-plugin-prettier": "^4.2.0",
35
+ "eslint-plugin-prettier": "^4.2.1",
36
36
  "express": "^4.18.2",
37
- "jasmine": "^4.5.0",
38
- "mocha": "^10.1.0",
37
+ "mocha": "^10.2.0",
39
38
  "multer": "^1.4.5-lts.1",
40
39
  "prettier": "^2.7.1",
41
40
  "serve-index": "^1.9.1",
@@ -44,7 +43,7 @@
44
43
  "scripts": {
45
44
  "lint": "eslint --ignore-pattern node_modules --ignore-pattern generator --fix --ext js lib/http.js \"**/*.js\"",
46
45
  "test": "npm run lint && mocha -t 600000 --recursive test",
47
- "test-jasmine": "jasmine JASMINE_CONFIG_PATH=jasmine.json"
46
+ "test-jasmine": "bazel test //javascript/node/selenium-webdriver:tests"
48
47
  },
49
48
  "mocha": {
50
49
  "recursive": true,