Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/javascript/selenium-webdriver/bidi/logInspector.js
4000 views
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
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',
30
}
31
32
class LogInspector {
33
bidi
34
ws
35
#callbackId = 0
36
37
constructor(driver, browsingContextIds) {
38
this._driver = driver
39
this._browsingContextIds = browsingContextIds
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())
49
}
50
51
/**
52
* Subscribe to log event
53
* @returns {Promise<void>}
54
*/
55
async init() {
56
this.bidi = await this._driver.getBidi()
57
await this.bidi.subscribe('log.entryAdded', this._browsingContextIds)
58
}
59
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
}
101
}
102
}
103
104
/**
105
* Listen to Console logs
106
* @param callback
107
* @param filterBy
108
* @returns {Promise<number>}
109
*/
110
async onConsoleEntry(callback, filterBy = undefined) {
111
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
112
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
113
}
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
123
this.ws = await this.bidi.socket
124
125
this.ws.on('message', (event) => {
126
const { params } = JSON.parse(Buffer.from(event.toString()))
127
128
if (params?.type === LOG.TYPE_CONSOLE) {
129
let consoleEntry = new ConsoleLogEntry(
130
params.level,
131
params.source,
132
params.text,
133
params.timestamp,
134
params.type,
135
params.method,
136
params.args,
137
params.stackTrace,
138
)
139
140
if (filterBy !== undefined) {
141
if (params?.level === filterBy.getLevel()) {
142
this.invokeCallbacksWithFilter(LOG.TYPE_CONSOLE_FILTER, consoleEntry, filterBy.getLevel())
143
}
144
return
145
}
146
147
this.invokeCallbacks(LOG.TYPE_CONSOLE, consoleEntry)
148
}
149
})
150
151
return id
152
}
153
154
/**
155
* Listen to JS logs
156
* @param callback
157
* @param filterBy
158
* @returns {Promise<number>}
159
*/
160
async onJavascriptLog(callback, filterBy = undefined) {
161
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
162
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
163
}
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
173
this.ws = await this.bidi.socket
174
175
this.ws.on('message', (event) => {
176
const { params } = JSON.parse(Buffer.from(event.toString()))
177
178
if (params?.type === LOG.TYPE_JS_LOGS) {
179
let jsEntry = new JavascriptLogEntry(
180
params.level,
181
params.source,
182
params.text,
183
params.timestamp,
184
params.type,
185
params.stackTrace,
186
)
187
188
if (filterBy !== undefined) {
189
if (params?.level === filterBy.getLevel()) {
190
this.invokeCallbacksWithFilter(LOG.TYPE_JS_LOGS_FILTER, jsEntry, filterBy.getLevel())
191
}
192
return
193
}
194
195
this.invokeCallbacks(LOG.TYPE_JS_LOGS, jsEntry)
196
}
197
})
198
199
return id
200
}
201
202
/**
203
* Listen to JS Exceptions
204
* @param callback
205
* @returns {Promise<number>}
206
*/
207
async onJavascriptException(callback) {
208
const id = this.addCallback(LOG.TYPE_JS_EXCEPTION, callback)
209
this.ws = await this.bidi.socket
210
211
this.ws.on('message', (event) => {
212
const { params } = JSON.parse(Buffer.from(event.toString()))
213
if (params?.type === 'javascript' && params?.level === 'error') {
214
let jsErrorEntry = new JavascriptLogEntry(
215
params.level,
216
params.source,
217
params.text,
218
params.timestamp,
219
params.type,
220
params.stackTrace,
221
)
222
223
this.invokeCallbacks(LOG.TYPE_JS_EXCEPTION, jsErrorEntry)
224
}
225
})
226
227
return id
228
}
229
230
/**
231
* Listen to any logs
232
* @param callback
233
* @param filterBy
234
* @returns {Promise<number>}
235
*/
236
async onLog(callback, filterBy = undefined) {
237
if (filterBy !== undefined && !(filterBy instanceof FilterBy)) {
238
throw Error(`Pass valid FilterBy object. Received: ${filterBy}`)
239
}
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
248
this.ws = await this.bidi.socket
249
250
this.ws.on('message', (event) => {
251
const { params } = JSON.parse(Buffer.from(event.toString()))
252
if (params?.type === 'javascript') {
253
let jsEntry = new JavascriptLogEntry(
254
params.level,
255
params.source,
256
params.text,
257
params.timestamp,
258
params.type,
259
params.stackTrace,
260
)
261
262
if (filterBy !== undefined) {
263
if (params?.level === filterBy.getLevel()) {
264
callback(jsEntry)
265
}
266
return
267
}
268
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)
279
return
280
}
281
282
if (params?.type === 'console') {
283
let consoleEntry = new ConsoleLogEntry(
284
params.level,
285
params.source,
286
params.text,
287
params.timestamp,
288
params.type,
289
params.method,
290
params.args,
291
params.stackTrace,
292
)
293
294
if (filterBy !== undefined) {
295
if (params?.level === filterBy.getLevel()) {
296
this.invokeCallbacksWithFilter(LOG.TYPE_LOGS_FILTER, consoleEntry, filterBy.getLevel())
297
}
298
return
299
}
300
301
this.invokeCallbacks(LOG.TYPE_LOGS, consoleEntry)
302
return
303
}
304
305
if (params !== undefined && !['console', 'javascript'].includes(params?.type)) {
306
let genericEntry = new GenericLogEntry(
307
params.level,
308
params.source,
309
params.text,
310
params.timestamp,
311
params.type,
312
params.stackTrace,
313
)
314
315
if (filterBy !== undefined) {
316
if (params?.level === filterBy.getLevel()) {
317
{
318
this.invokeCallbacksWithFilter(LOG.TYPE_LOGS_FILTER, genericEntry, filterBy.getLevel())
319
}
320
return
321
}
322
}
323
324
this.invokeCallbacks(LOG.TYPE_LOGS, genericEntry)
325
return
326
}
327
})
328
329
return id
330
}
331
332
/**
333
* Unsubscribe to log event
334
* @returns {Promise<void>}
335
*/
336
async close() {
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
}
346
}
347
}
348
349
/**
350
* initiate inspector instance and return
351
* @param driver
352
* @param browsingContextIds
353
* @returns {Promise<LogInspector>}
354
*/
355
async function getLogInspectorInstance(driver, browsingContextIds) {
356
let instance = new LogInspector(driver, browsingContextIds)
357
await instance.init()
358
return instance
359
}
360
361
/**
362
* API
363
* @type {function(*, *): Promise<LogInspector>}
364
*/
365
module.exports = getLogInspectorInstance
366
367