selenium-webdriver 4.7.1 → 4.8.1
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 +31 -0
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/bidi/browsingContext.js +194 -0
- package/bidi/filterBy.js +46 -0
- package/bidi/index.js +224 -0
- package/bidi/logEntries.js +87 -0
- package/bidi/logInspector.js +269 -0
- package/bin/linux/selenium-manager +0 -0
- package/bin/macos/selenium-manager +0 -0
- package/bin/windows/selenium-manager.exe +0 -0
- package/chromium.js +17 -0
- package/common/seleniumManager.js +1 -1
- package/devtools/CDPConnection.js +37 -0
- package/firefox.js +12 -0
- package/http/index.js +1 -1
- package/ie.js +1 -1
- package/index.js +4 -0
- package/lib/http.js +5 -5
- package/lib/pinnedScript.js +59 -0
- package/lib/webdriver.js +135 -2
- package/package.json +7 -8
package/CHANGES.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## v4.8.1
|
|
2
|
+
|
|
3
|
+
#### :rocket: New Feature
|
|
4
|
+
|
|
5
|
+
* Add script pinning (#11584)
|
|
6
|
+
|
|
7
|
+
#### :nail_care: Polish
|
|
8
|
+
|
|
9
|
+
* Add CDP support for v110 and remove v107
|
|
10
|
+
* Updating Selenium Manager binaries for 4.8.1 release
|
|
11
|
+
|
|
12
|
+
#### :bug: Bug Fix
|
|
13
|
+
|
|
14
|
+
fix: iedriver download with selenium-manager #11579
|
|
15
|
+
|
|
16
|
+
## v4.8.0
|
|
17
|
+
|
|
18
|
+
#### :rocket: New Feature
|
|
19
|
+
|
|
20
|
+
* Add initial BiDi Support (#11395)
|
|
21
|
+
* Add window wrappers getSize and setSize
|
|
22
|
+
* Add BiDi browser context commands (#11473)
|
|
23
|
+
* Add BiDi methods to listen to js logs and any type of logs
|
|
24
|
+
* Add BiDi filtering capability to LogInspector (#11495)
|
|
25
|
+
* Add comment with name of large JS executions (#11038)
|
|
26
|
+
|
|
27
|
+
#### :nail_care: Polish
|
|
28
|
+
|
|
29
|
+
* Add CDP support for v109 and remove v106
|
|
30
|
+
* Deprecate setHeadless() in Chrome and Firefox (#11467)
|
|
31
|
+
|
|
1
32
|
## v4.7.1
|
|
2
33
|
|
|
3
34
|
#### :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
|
|
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-
|
|
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
|
package/bidi/filterBy.js
ADDED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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
|
/**
|
|
@@ -30,7 +30,7 @@ const execSync = require('child_process').execSync
|
|
|
30
30
|
* currently supported browsers for selenium-manager
|
|
31
31
|
* @type {string[]}
|
|
32
32
|
*/
|
|
33
|
-
const Browser = ['chrome', 'firefox', 'edge']
|
|
33
|
+
const Browser = ['chrome', 'firefox', 'edge', 'iexplorer']
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Determines the path of the correct Selenium Manager binary
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// specific language governing permissions and limitations
|
|
16
16
|
// under the License.
|
|
17
17
|
|
|
18
|
+
const RESPONSE_TIMEOUT = 1000 * 30
|
|
18
19
|
class CDPConnection {
|
|
19
20
|
constructor(wsConnection) {
|
|
20
21
|
this._wsConnection = wsConnection
|
|
@@ -35,6 +36,42 @@ class CDPConnection {
|
|
|
35
36
|
const mergedMessage = Object.assign({ params: params }, message)
|
|
36
37
|
this._wsConnection.send(JSON.stringify(mergedMessage), callback)
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
async send(method, params) {
|
|
41
|
+
let cdp_id = this.cmd_id++
|
|
42
|
+
let message = {
|
|
43
|
+
method,
|
|
44
|
+
id: cdp_id,
|
|
45
|
+
}
|
|
46
|
+
if (this.sessionId) {
|
|
47
|
+
message['sessionId'] = this.sessionId
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const mergedMessage = Object.assign({ params: params }, message)
|
|
51
|
+
this._wsConnection.send(JSON.stringify(mergedMessage))
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const timeoutId = setTimeout(() => {
|
|
55
|
+
reject(new Error(`Request with id ${cdp_id} timed out`))
|
|
56
|
+
handler.off('message', listener)
|
|
57
|
+
}, RESPONSE_TIMEOUT)
|
|
58
|
+
|
|
59
|
+
const listener = (data) => {
|
|
60
|
+
try {
|
|
61
|
+
const payload = JSON.parse(data.toString())
|
|
62
|
+
if (payload.id === cdp_id) {
|
|
63
|
+
clearTimeout(timeoutId)
|
|
64
|
+
handler.off('message', listener)
|
|
65
|
+
resolve(payload)
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(`Failed parse message: ${err.message}`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handler = this._wsConnection.on('message', listener)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
38
75
|
}
|
|
39
76
|
|
|
40
77
|
exports.CdpConnection = CDPConnection
|
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
|
-
|
|
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/ie.js
CHANGED
|
@@ -406,7 +406,7 @@ function createServiceFromCapabilities(capabilities) {
|
|
|
406
406
|
)
|
|
407
407
|
|
|
408
408
|
try {
|
|
409
|
-
exe = driverLocation(
|
|
409
|
+
exe = driverLocation('iexplorer')
|
|
410
410
|
} catch (err) {
|
|
411
411
|
console.log(`Unable to obtain driver using Selenium Manager: ${err}`)
|
|
412
412
|
}
|
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',
|
|
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
|
|
|
@@ -0,0 +1,59 @@
|
|
|
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 crypto = require('crypto')
|
|
19
|
+
|
|
20
|
+
class PinnedScript {
|
|
21
|
+
constructor(script) {
|
|
22
|
+
this.scriptSource_ = script
|
|
23
|
+
this.scriptHandle_ = crypto.randomUUID().replace(/-/gi, '')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get handle() {
|
|
27
|
+
return this.scriptHandle_
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get source() {
|
|
31
|
+
return this.scriptSource_
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get scriptId() {
|
|
35
|
+
return this.scriptId_
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set scriptId(id) {
|
|
39
|
+
this.scriptId_ = id
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
creationScript() {
|
|
43
|
+
return `function __webdriver_${this.scriptHandle_}(arguments) { ${this.scriptSource_} }`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
executionScript() {
|
|
47
|
+
return `return __webdriver_${this.scriptHandle_}(arguments)`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
removalScript() {
|
|
51
|
+
return `__webdriver_${this.scriptHandle_} = undefined`
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// PUBLIC API
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
PinnedScript,
|
|
59
|
+
}
|
package/lib/webdriver.js
CHANGED
|
@@ -40,6 +40,8 @@ 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')
|
|
44
|
+
const { PinnedScript } = require('./pinnedScript')
|
|
43
45
|
|
|
44
46
|
// Capability names that are defined in the W3C spec.
|
|
45
47
|
const W3C_CAPABILITY_NAMES = new Set([
|
|
@@ -685,6 +687,8 @@ class WebDriver {
|
|
|
685
687
|
|
|
686
688
|
/** @private {./virtual_authenticator}*/
|
|
687
689
|
this.authenticatorId_ = null
|
|
690
|
+
|
|
691
|
+
this.pinnedScripts_ = {}
|
|
688
692
|
}
|
|
689
693
|
|
|
690
694
|
/**
|
|
@@ -794,6 +798,15 @@ class WebDriver {
|
|
|
794
798
|
if (typeof script === 'function') {
|
|
795
799
|
script = 'return (' + script + ').apply(null, arguments);'
|
|
796
800
|
}
|
|
801
|
+
|
|
802
|
+
if (script && script instanceof PinnedScript) {
|
|
803
|
+
return this.execute(
|
|
804
|
+
new command.Command(command.Name.EXECUTE_SCRIPT)
|
|
805
|
+
.setParameter('script', script.executionScript())
|
|
806
|
+
.setParameter('args', args)
|
|
807
|
+
)
|
|
808
|
+
}
|
|
809
|
+
|
|
797
810
|
return this.execute(
|
|
798
811
|
new command.Command(command.Name.EXECUTE_SCRIPT)
|
|
799
812
|
.setParameter('script', script)
|
|
@@ -806,6 +819,15 @@ class WebDriver {
|
|
|
806
819
|
if (typeof script === 'function') {
|
|
807
820
|
script = 'return (' + script + ').apply(null, arguments);'
|
|
808
821
|
}
|
|
822
|
+
|
|
823
|
+
if (script && script instanceof PinnedScript) {
|
|
824
|
+
return this.execute(
|
|
825
|
+
new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT)
|
|
826
|
+
.setParameter('script', script.executionScript())
|
|
827
|
+
.setParameter('args', args)
|
|
828
|
+
)
|
|
829
|
+
}
|
|
830
|
+
|
|
809
831
|
return this.execute(
|
|
810
832
|
new command.Command(command.Name.EXECUTE_ASYNC_SCRIPT)
|
|
811
833
|
.setParameter('script', script)
|
|
@@ -1225,7 +1247,9 @@ class WebDriver {
|
|
|
1225
1247
|
this._wsUrl = await this.getWsUrl(debuggerUrl, target, caps)
|
|
1226
1248
|
return new Promise((resolve, reject) => {
|
|
1227
1249
|
try {
|
|
1228
|
-
this._wsConnection = new WebSocket(
|
|
1250
|
+
this._wsConnection = new WebSocket(
|
|
1251
|
+
this._wsUrl.replace('localhost', '127.0.0.1')
|
|
1252
|
+
)
|
|
1229
1253
|
this._cdpConnection = new cdp.CdpConnection(this._wsConnection)
|
|
1230
1254
|
} catch (err) {
|
|
1231
1255
|
reject(err)
|
|
@@ -1271,6 +1295,16 @@ class WebDriver {
|
|
|
1271
1295
|
this._cdpConnection.execute('Target.getTargets')
|
|
1272
1296
|
}
|
|
1273
1297
|
|
|
1298
|
+
/**
|
|
1299
|
+
* Initiates bidi connection using 'webSocketUrl'
|
|
1300
|
+
* @returns {BIDI}
|
|
1301
|
+
*/
|
|
1302
|
+
async getBidi() {
|
|
1303
|
+
const caps = await this.getCapabilities()
|
|
1304
|
+
let WebSocketUrl = caps['map_'].get('webSocketUrl')
|
|
1305
|
+
return new BIDI(WebSocketUrl.replace('localhost', '127.0.0.1'))
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1274
1308
|
/**
|
|
1275
1309
|
* Retrieves 'webSocketDebuggerUrl' by sending a http request using debugger address
|
|
1276
1310
|
* @param {string} debuggerAddress
|
|
@@ -1521,6 +1555,74 @@ class WebDriver {
|
|
|
1521
1555
|
})
|
|
1522
1556
|
}
|
|
1523
1557
|
|
|
1558
|
+
async pinScript(script) {
|
|
1559
|
+
let pinnedScript = new PinnedScript(script)
|
|
1560
|
+
let connection
|
|
1561
|
+
if (Object.is(this._cdpConnection, undefined)) {
|
|
1562
|
+
connection = await this.createCDPConnection('page')
|
|
1563
|
+
} else {
|
|
1564
|
+
connection = this._cdpConnection
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
await connection.execute('Page.enable', {}, null)
|
|
1568
|
+
|
|
1569
|
+
await connection.execute(
|
|
1570
|
+
'Runtime.evaluate',
|
|
1571
|
+
{
|
|
1572
|
+
expression: pinnedScript.creationScript(),
|
|
1573
|
+
},
|
|
1574
|
+
null
|
|
1575
|
+
)
|
|
1576
|
+
|
|
1577
|
+
let result = await connection.send(
|
|
1578
|
+
'Page.addScriptToEvaluateOnNewDocument',
|
|
1579
|
+
{
|
|
1580
|
+
source: pinnedScript.creationScript(),
|
|
1581
|
+
}
|
|
1582
|
+
)
|
|
1583
|
+
|
|
1584
|
+
pinnedScript.scriptId = result['result']['identifier']
|
|
1585
|
+
|
|
1586
|
+
this.pinnedScripts_[pinnedScript.handle] = pinnedScript
|
|
1587
|
+
|
|
1588
|
+
return pinnedScript
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
async unpinScript(script) {
|
|
1592
|
+
if (script && !(script instanceof PinnedScript)) {
|
|
1593
|
+
throw Error(`Pass valid PinnedScript object. Received: ${script}`)
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if (script.handle in this.pinnedScripts_) {
|
|
1597
|
+
let connection
|
|
1598
|
+
if (Object.is(this._cdpConnection, undefined)) {
|
|
1599
|
+
connection = this.createCDPConnection('page')
|
|
1600
|
+
} else {
|
|
1601
|
+
connection = this._cdpConnection
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
await connection.execute('Page.enable', {}, null)
|
|
1605
|
+
|
|
1606
|
+
await connection.execute(
|
|
1607
|
+
'Runtime.evaluate',
|
|
1608
|
+
{
|
|
1609
|
+
expression: script.removalScript(),
|
|
1610
|
+
},
|
|
1611
|
+
null
|
|
1612
|
+
)
|
|
1613
|
+
|
|
1614
|
+
await connection.execute(
|
|
1615
|
+
'Page.removeScriptToEvaluateOnLoad',
|
|
1616
|
+
{
|
|
1617
|
+
identifier: script.scriptId,
|
|
1618
|
+
},
|
|
1619
|
+
null
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
delete this.pinnedScripts_[script.handle]
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1524
1626
|
/**
|
|
1525
1627
|
*
|
|
1526
1628
|
* @returns The value of authenticator ID added
|
|
@@ -2142,6 +2244,37 @@ class Window {
|
|
|
2142
2244
|
new command.Command(command.Name.FULLSCREEN_WINDOW)
|
|
2143
2245
|
)
|
|
2144
2246
|
}
|
|
2247
|
+
|
|
2248
|
+
/**
|
|
2249
|
+
* Gets the width and height of the current window
|
|
2250
|
+
* @param windowHandle
|
|
2251
|
+
* @returns {Promise<{width: *, height: *}>}
|
|
2252
|
+
*/
|
|
2253
|
+
async getSize(windowHandle = 'current') {
|
|
2254
|
+
if (windowHandle !== 'current') {
|
|
2255
|
+
console.warn(`Only 'current' window is supported for W3C compatible browsers.`);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
const rect = await this.getRect();
|
|
2259
|
+
return {height: rect.height, width: rect.width};
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
/**
|
|
2263
|
+
* Sets the width and height of the current window. (window.resizeTo)
|
|
2264
|
+
* @param x
|
|
2265
|
+
* @param y
|
|
2266
|
+
* @param width
|
|
2267
|
+
* @param height
|
|
2268
|
+
* @param windowHandle
|
|
2269
|
+
* @returns {Promise<void>}
|
|
2270
|
+
*/
|
|
2271
|
+
async setSize({x = 0, y = 0, width = 0, height = 0}, windowHandle = 'current') {
|
|
2272
|
+
if (windowHandle !== 'current') {
|
|
2273
|
+
console.warn(`Only 'current' window is supported for W3C compatible browsers.`);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
await this.setRect({x, y, width, height});
|
|
2277
|
+
}
|
|
2145
2278
|
}
|
|
2146
2279
|
|
|
2147
2280
|
/**
|
|
@@ -2863,7 +2996,7 @@ class WebElement {
|
|
|
2863
2996
|
*/
|
|
2864
2997
|
submit() {
|
|
2865
2998
|
const script =
|
|
2866
|
-
'var form = arguments[0];\n' +
|
|
2999
|
+
'/* submitForm */var form = arguments[0];\n' +
|
|
2867
3000
|
'while (form.nodeName != "FORM" && form.parentNode) {\n' +
|
|
2868
3001
|
' form = form.parentNode;\n' +
|
|
2869
3002
|
'}\n' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selenium-webdriver",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.1",
|
|
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.
|
|
28
|
+
"ws": ">=8.11.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"eslint": "^8.
|
|
32
|
-
"eslint-config-prettier": "^8.
|
|
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.
|
|
35
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
36
36
|
"express": "^4.18.2",
|
|
37
|
-
"
|
|
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": "
|
|
46
|
+
"test-jasmine": "bazel test //javascript/node/selenium-webdriver:tests"
|
|
48
47
|
},
|
|
49
48
|
"mocha": {
|
|
50
49
|
"recursive": true,
|