selenium-webdriver 4.0.0-alpha.7 → 4.0.0-beta.3
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 +56 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +2 -0
- package/chrome.js +69 -79
- package/chromium.js +231 -164
- package/{lib/atoms/make-atoms-module.js → devtools/CDPConnection.js} +15 -9
- package/edge.js +104 -146
- package/example/chrome_android.js +45 -0
- package/example/chrome_mobile_emulation.js +46 -0
- package/example/firefox_channels.js +84 -0
- package/example/google_search.js +50 -0
- package/example/google_search_test.js +70 -0
- package/example/headless.js +63 -0
- package/example/logging.js +64 -0
- package/firefox.js +199 -200
- package/http/index.js +128 -116
- package/http/util.js +56 -64
- package/ie.js +180 -104
- package/index.js +246 -199
- package/io/exec.js +35 -40
- package/io/index.js +146 -157
- package/io/zip.js +75 -66
- package/lib/atoms/find-elements.js +123 -0
- package/lib/atoms/get-attribute.js +75 -59
- package/lib/atoms/is-displayed.js +72 -72
- package/lib/atoms/mutation-listener.js +55 -0
- package/lib/by.js +192 -65
- package/lib/capabilities.js +76 -86
- package/lib/command.js +19 -35
- package/lib/error.js +134 -168
- package/lib/http.js +308 -232
- package/lib/input.js +192 -453
- package/lib/logging.js +126 -137
- package/lib/promise.js +53 -71
- package/lib/proxy.js +31 -41
- package/lib/session.js +12 -15
- package/lib/symbols.js +3 -4
- package/lib/until.js +162 -160
- package/lib/webdriver.js +1014 -549
- package/net/index.js +30 -36
- package/net/portprober.js +30 -143
- package/opera.js +406 -0
- package/package.json +15 -7
- package/proxy.js +2 -2
- package/remote/index.js +189 -187
- package/safari.js +40 -49
- package/testing/index.js +504 -0
- package/lib/README +0 -5
- package/lib/actions.js +0 -609
package/lib/http.js
CHANGED
|
@@ -23,21 +23,28 @@
|
|
|
23
23
|
* the {@link Executor} to send commands to the remote end.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
'use strict'
|
|
26
|
+
'use strict'
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const {
|
|
28
|
+
const path = require('path')
|
|
29
|
+
const cmd = require('./command')
|
|
30
|
+
const error = require('./error')
|
|
31
|
+
const logging = require('./logging')
|
|
32
|
+
const promise = require('./promise')
|
|
33
|
+
const { Session } = require('./session')
|
|
34
|
+
const { WebElement } = require('./webdriver')
|
|
34
35
|
|
|
35
36
|
const getAttribute = requireAtom(
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
'get-attribute.js',
|
|
38
|
+
'//javascript/node/selenium-webdriver/lib/atoms:get-attribute.js'
|
|
39
|
+
)
|
|
38
40
|
const isDisplayed = requireAtom(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
'is-displayed.js',
|
|
42
|
+
'//javascript/node/selenium-webdriver/lib/atoms:is-displayed.js'
|
|
43
|
+
)
|
|
44
|
+
const findElements = requireAtom(
|
|
45
|
+
'find-elements.js',
|
|
46
|
+
'//javascript/node/selenium-webdriver/lib/atoms:find-elements.js'
|
|
47
|
+
)
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* @param {string} module
|
|
@@ -46,36 +53,36 @@ const isDisplayed = requireAtom(
|
|
|
46
53
|
*/
|
|
47
54
|
function requireAtom(module, bazelTarget) {
|
|
48
55
|
try {
|
|
49
|
-
return require('./atoms/' + module)
|
|
56
|
+
return require('./atoms/' + module)
|
|
50
57
|
} catch (ex) {
|
|
51
58
|
try {
|
|
52
|
-
const file = bazelTarget.slice(2).replace(':', '/')
|
|
53
|
-
|
|
59
|
+
const file = bazelTarget.slice(2).replace(':', '/')
|
|
60
|
+
console.log(`../../../bazel-bin/${file}`)
|
|
61
|
+
return require(path.resolve(`../../../bazel-bin/${file}`))
|
|
54
62
|
} catch (ex2) {
|
|
55
|
-
console.log(ex2)
|
|
63
|
+
console.log(ex2)
|
|
56
64
|
throw Error(
|
|
57
|
-
`Failed to import atoms module ${module}. If running in dev mode, you`
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
`Failed to import atoms module ${module}. If running in dev mode, you` +
|
|
66
|
+
` need to run \`bazel build ${bazelTarget}\` from the project` +
|
|
67
|
+
`root: ${ex}`
|
|
68
|
+
)
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
|
|
65
73
|
/**
|
|
66
74
|
* Converts a headers map to a HTTP header block string.
|
|
67
75
|
* @param {!Map<string, string>} headers The map to convert.
|
|
68
76
|
* @return {string} The headers as a string.
|
|
69
77
|
*/
|
|
70
78
|
function headersToString(headers) {
|
|
71
|
-
|
|
72
|
-
headers.forEach(function(value, name) {
|
|
73
|
-
ret.push(`${name.toLowerCase()}: ${value}`)
|
|
74
|
-
})
|
|
75
|
-
return ret.join('\n')
|
|
79
|
+
const ret = []
|
|
80
|
+
headers.forEach(function (value, name) {
|
|
81
|
+
ret.push(`${name.toLowerCase()}: ${value}`)
|
|
82
|
+
})
|
|
83
|
+
return ret.join('\n')
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
79
86
|
/**
|
|
80
87
|
* Represents a HTTP request message. This class is a "partial" request and only
|
|
81
88
|
* defines the path on the server to send a request to. It is each client's
|
|
@@ -89,25 +96,25 @@ class Request {
|
|
|
89
96
|
* @param {Object=} opt_data This request's non-serialized JSON payload data.
|
|
90
97
|
*/
|
|
91
98
|
constructor(method, path, opt_data) {
|
|
92
|
-
this.method = /** string */method
|
|
93
|
-
this.path = /** string */path
|
|
94
|
-
this.data = /** Object */opt_data
|
|
95
|
-
this.headers = /** !Map<string, string> */new Map(
|
|
96
|
-
|
|
99
|
+
this.method = /** string */ method
|
|
100
|
+
this.path = /** string */ path
|
|
101
|
+
this.data = /** Object */ opt_data
|
|
102
|
+
this.headers = /** !Map<string, string> */ new Map([
|
|
103
|
+
['Accept', 'application/json; charset=utf-8'],
|
|
104
|
+
])
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
/** @override */
|
|
100
108
|
toString() {
|
|
101
|
-
let ret = `${this.method} ${this.path} HTTP/1.1\n
|
|
102
|
-
ret += headersToString(this.headers) + '\n\n'
|
|
109
|
+
let ret = `${this.method} ${this.path} HTTP/1.1\n`
|
|
110
|
+
ret += headersToString(this.headers) + '\n\n'
|
|
103
111
|
if (this.data) {
|
|
104
|
-
ret += JSON.stringify(this.data)
|
|
112
|
+
ret += JSON.stringify(this.data)
|
|
105
113
|
}
|
|
106
|
-
return ret
|
|
114
|
+
return ret
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
|
|
111
118
|
/**
|
|
112
119
|
* Represents a HTTP response message.
|
|
113
120
|
* @final
|
|
@@ -120,54 +127,54 @@ class Response {
|
|
|
120
127
|
* @param {string} body The response body.
|
|
121
128
|
*/
|
|
122
129
|
constructor(status, headers, body) {
|
|
123
|
-
this.status = /** number */status
|
|
124
|
-
this.body = /** string */body
|
|
125
|
-
this.headers = /** !Map<string, string>*/new Map
|
|
130
|
+
this.status = /** number */ status
|
|
131
|
+
this.body = /** string */ body
|
|
132
|
+
this.headers = /** !Map<string, string>*/ new Map()
|
|
126
133
|
for (let header in headers) {
|
|
127
|
-
this.headers.set(header.toLowerCase(), headers[header])
|
|
134
|
+
this.headers.set(header.toLowerCase(), headers[header])
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
/** @override */
|
|
132
139
|
toString() {
|
|
133
|
-
let ret = `HTTP/1.1 ${this.status}\n${headersToString(this.headers)}\n\n
|
|
140
|
+
let ret = `HTTP/1.1 ${this.status}\n${headersToString(this.headers)}\n\n`
|
|
134
141
|
if (this.body) {
|
|
135
|
-
ret += this.body
|
|
142
|
+
ret += this.body
|
|
136
143
|
}
|
|
137
|
-
return ret
|
|
144
|
+
return ret
|
|
138
145
|
}
|
|
139
146
|
}
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
const DEV_ROOT = '../../../../buck-out/gen/javascript/';
|
|
143
|
-
|
|
144
148
|
/** @enum {!Function} */
|
|
145
149
|
const Atom = {
|
|
146
150
|
GET_ATTRIBUTE: getAttribute,
|
|
147
|
-
IS_DISPLAYED: isDisplayed
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const LOG = logging.getLogger('webdriver.http');
|
|
152
|
-
|
|
151
|
+
IS_DISPLAYED: isDisplayed,
|
|
152
|
+
FIND_ELEMENTS: findElements,
|
|
153
|
+
}
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
function del(path) { return resource('DELETE', path); }
|
|
156
|
-
function get(path) { return resource('GET', path); }
|
|
157
|
-
function resource(method, path) { return {method: method, path: path}; }
|
|
155
|
+
const LOG = logging.getLogger('webdriver.http')
|
|
158
156
|
|
|
157
|
+
function post(path) {
|
|
158
|
+
return resource('POST', path)
|
|
159
|
+
}
|
|
160
|
+
function del(path) {
|
|
161
|
+
return resource('DELETE', path)
|
|
162
|
+
}
|
|
163
|
+
function get(path) {
|
|
164
|
+
return resource('GET', path)
|
|
165
|
+
}
|
|
166
|
+
function resource(method, path) {
|
|
167
|
+
return { method: method, path: path }
|
|
168
|
+
}
|
|
159
169
|
|
|
160
170
|
/** @typedef {{method: string, path: string}} */
|
|
161
|
-
var CommandSpec
|
|
162
|
-
|
|
171
|
+
var CommandSpec // eslint-disable-line
|
|
163
172
|
|
|
164
173
|
/** @typedef {function(!cmd.Command): !cmd.Command} */
|
|
165
|
-
var CommandTransformer
|
|
166
|
-
|
|
174
|
+
var CommandTransformer // eslint-disable-line
|
|
167
175
|
|
|
168
176
|
class InternalTypeError extends TypeError {}
|
|
169
177
|
|
|
170
|
-
|
|
171
178
|
/**
|
|
172
179
|
* @param {!cmd.Command} command The initial command.
|
|
173
180
|
* @param {Atom} atom The name of the atom to execute.
|
|
@@ -175,94 +182,130 @@ class InternalTypeError extends TypeError {}
|
|
|
175
182
|
*/
|
|
176
183
|
function toExecuteAtomCommand(command, atom, ...params) {
|
|
177
184
|
if (typeof atom !== 'function') {
|
|
178
|
-
throw new InternalTypeError('atom is not a function: ' + typeof atom)
|
|
185
|
+
throw new InternalTypeError('atom is not a function: ' + typeof atom)
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
return new cmd.Command(cmd.Name.EXECUTE_SCRIPT)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
.setParameter('sessionId', command.getParameter('sessionId'))
|
|
190
|
+
.setParameter('script', `return (${atom}).apply(null, arguments)`)
|
|
191
|
+
.setParameter(
|
|
192
|
+
'args',
|
|
193
|
+
params.map((param) => command.getParameter(param))
|
|
194
|
+
)
|
|
185
195
|
}
|
|
186
196
|
|
|
187
|
-
|
|
188
|
-
|
|
189
197
|
/** @const {!Map<string, CommandSpec>} */
|
|
190
198
|
const COMMAND_MAP = new Map([
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
199
|
+
[cmd.Name.GET_SERVER_STATUS, get('/status')],
|
|
200
|
+
[cmd.Name.NEW_SESSION, post('/session')],
|
|
201
|
+
[cmd.Name.GET_SESSIONS, get('/sessions')],
|
|
202
|
+
[cmd.Name.QUIT, del('/session/:sessionId')],
|
|
203
|
+
[cmd.Name.CLOSE, del('/session/:sessionId/window')],
|
|
204
|
+
[
|
|
205
|
+
cmd.Name.GET_CURRENT_WINDOW_HANDLE,
|
|
206
|
+
get('/session/:sessionId/window_handle'),
|
|
207
|
+
],
|
|
208
|
+
[cmd.Name.GET_WINDOW_HANDLES, get('/session/:sessionId/window_handles')],
|
|
209
|
+
[cmd.Name.GET_CURRENT_URL, get('/session/:sessionId/url')],
|
|
210
|
+
[cmd.Name.GET, post('/session/:sessionId/url')],
|
|
211
|
+
[cmd.Name.GO_BACK, post('/session/:sessionId/back')],
|
|
212
|
+
[cmd.Name.GO_FORWARD, post('/session/:sessionId/forward')],
|
|
213
|
+
[cmd.Name.REFRESH, post('/session/:sessionId/refresh')],
|
|
214
|
+
[cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
|
|
215
|
+
[cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
|
|
216
|
+
[cmd.Name.DELETE_ALL_COOKIES, del('/session/:sessionId/cookie')],
|
|
217
|
+
[cmd.Name.DELETE_COOKIE, del('/session/:sessionId/cookie/:name')],
|
|
218
|
+
[cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
|
|
219
|
+
[cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
|
|
220
|
+
[cmd.Name.GET_ACTIVE_ELEMENT, post('/session/:sessionId/element/active')],
|
|
221
|
+
[
|
|
222
|
+
cmd.Name.FIND_CHILD_ELEMENT,
|
|
223
|
+
post('/session/:sessionId/element/:id/element'),
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
cmd.Name.FIND_CHILD_ELEMENTS,
|
|
227
|
+
post('/session/:sessionId/element/:id/elements'),
|
|
228
|
+
],
|
|
229
|
+
[cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
|
|
230
|
+
[cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
|
|
231
|
+
[
|
|
232
|
+
cmd.Name.SEND_KEYS_TO_ELEMENT,
|
|
233
|
+
post('/session/:sessionId/element/:id/value'),
|
|
234
|
+
],
|
|
235
|
+
[cmd.Name.SUBMIT_ELEMENT, post('/session/:sessionId/element/:id/submit')],
|
|
236
|
+
[cmd.Name.GET_ELEMENT_TEXT, get('/session/:sessionId/element/:id/text')],
|
|
237
|
+
[
|
|
238
|
+
cmd.Name.GET_COMPUTED_ROLE,
|
|
239
|
+
get('/session/:sessionId/element/:id/computedrole'),
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
cmd.Name.GET_COMPUTED_LABEL,
|
|
243
|
+
get('/session/:sessionId/element/:id/computedlabel'),
|
|
244
|
+
],
|
|
245
|
+
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
246
|
+
[
|
|
247
|
+
cmd.Name.IS_ELEMENT_SELECTED,
|
|
248
|
+
get('/session/:sessionId/element/:id/selected'),
|
|
249
|
+
],
|
|
250
|
+
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
251
|
+
[
|
|
252
|
+
cmd.Name.IS_ELEMENT_DISPLAYED,
|
|
253
|
+
get('/session/:sessionId/element/:id/displayed'),
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
cmd.Name.GET_ELEMENT_LOCATION,
|
|
257
|
+
get('/session/:sessionId/element/:id/location'),
|
|
258
|
+
],
|
|
259
|
+
[cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/size')],
|
|
260
|
+
[
|
|
261
|
+
cmd.Name.GET_ELEMENT_ATTRIBUTE,
|
|
262
|
+
get('/session/:sessionId/element/:id/attribute/:name'),
|
|
263
|
+
],
|
|
264
|
+
[
|
|
265
|
+
cmd.Name.GET_ELEMENT_PROPERTY,
|
|
266
|
+
get('/session/:sessionId/element/:id/property/:name'),
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
|
270
|
+
get('/session/:sessionId/element/:id/css/:propertyName'),
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
cmd.Name.TAKE_ELEMENT_SCREENSHOT,
|
|
274
|
+
get('/session/:sessionId/element/:id/screenshot'),
|
|
275
|
+
],
|
|
276
|
+
[cmd.Name.SWITCH_TO_WINDOW, post('/session/:sessionId/window')],
|
|
277
|
+
[
|
|
278
|
+
cmd.Name.MAXIMIZE_WINDOW,
|
|
279
|
+
post('/session/:sessionId/window/current/maximize'),
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
cmd.Name.GET_WINDOW_POSITION,
|
|
283
|
+
get('/session/:sessionId/window/current/position'),
|
|
284
|
+
],
|
|
285
|
+
[
|
|
286
|
+
cmd.Name.SET_WINDOW_POSITION,
|
|
287
|
+
post('/session/:sessionId/window/current/position'),
|
|
288
|
+
],
|
|
289
|
+
[cmd.Name.GET_WINDOW_SIZE, get('/session/:sessionId/window/current/size')],
|
|
290
|
+
[cmd.Name.SET_WINDOW_SIZE, post('/session/:sessionId/window/current/size')],
|
|
291
|
+
[cmd.Name.SWITCH_TO_FRAME, post('/session/:sessionId/frame')],
|
|
292
|
+
[cmd.Name.SWITCH_TO_FRAME_PARENT, post('/session/:sessionId/frame/parent')],
|
|
293
|
+
[cmd.Name.GET_PAGE_SOURCE, get('/session/:sessionId/source')],
|
|
294
|
+
[cmd.Name.GET_TITLE, get('/session/:sessionId/title')],
|
|
295
|
+
[cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute')],
|
|
296
|
+
[cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute_async')],
|
|
297
|
+
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
298
|
+
[cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
|
|
299
|
+
[cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
|
|
300
|
+
[cmd.Name.ACCEPT_ALERT, post('/session/:sessionId/accept_alert')],
|
|
301
|
+
[cmd.Name.DISMISS_ALERT, post('/session/:sessionId/dismiss_alert')],
|
|
302
|
+
[cmd.Name.GET_ALERT_TEXT, get('/session/:sessionId/alert_text')],
|
|
303
|
+
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert_text')],
|
|
304
|
+
[cmd.Name.GET_LOG, post('/session/:sessionId/log')],
|
|
305
|
+
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/log/types')],
|
|
306
|
+
[cmd.Name.GET_SESSION_LOGS, post('/logs')],
|
|
307
|
+
[cmd.Name.UPLOAD_FILE, post('/session/:sessionId/se/file')],
|
|
308
|
+
])
|
|
266
309
|
|
|
267
310
|
/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
|
|
268
311
|
const W3C_COMMAND_MAP = new Map([
|
|
@@ -306,24 +349,59 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
306
349
|
[cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
|
|
307
350
|
[cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
|
|
308
351
|
[cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
|
|
309
|
-
[
|
|
310
|
-
|
|
352
|
+
[
|
|
353
|
+
cmd.Name.FIND_ELEMENTS_RELATIVE,
|
|
354
|
+
(cmd) => {
|
|
355
|
+
return toExecuteAtomCommand(cmd, Atom.FIND_ELEMENTS, 'args')
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
[
|
|
359
|
+
cmd.Name.FIND_CHILD_ELEMENT,
|
|
360
|
+
post('/session/:sessionId/element/:id/element'),
|
|
361
|
+
],
|
|
362
|
+
[
|
|
363
|
+
cmd.Name.FIND_CHILD_ELEMENTS,
|
|
364
|
+
post('/session/:sessionId/element/:id/elements'),
|
|
365
|
+
],
|
|
311
366
|
// Element interaction.
|
|
312
367
|
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
313
|
-
[
|
|
314
|
-
|
|
368
|
+
[
|
|
369
|
+
cmd.Name.GET_ELEMENT_PROPERTY,
|
|
370
|
+
get('/session/:sessionId/element/:id/property/:name'),
|
|
371
|
+
],
|
|
372
|
+
[
|
|
373
|
+
cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
|
374
|
+
get('/session/:sessionId/element/:id/css/:propertyName'),
|
|
375
|
+
],
|
|
315
376
|
[cmd.Name.GET_ELEMENT_RECT, get('/session/:sessionId/element/:id/rect')],
|
|
316
377
|
[cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
|
|
317
378
|
[cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
|
|
318
|
-
[
|
|
379
|
+
[
|
|
380
|
+
cmd.Name.SEND_KEYS_TO_ELEMENT,
|
|
381
|
+
post('/session/:sessionId/element/:id/value'),
|
|
382
|
+
],
|
|
319
383
|
[cmd.Name.GET_ELEMENT_TEXT, get('/session/:sessionId/element/:id/text')],
|
|
384
|
+
[
|
|
385
|
+
cmd.Name.GET_COMPUTED_ROLE,
|
|
386
|
+
get('/session/:sessionId/element/:id/computedrole'),
|
|
387
|
+
],
|
|
388
|
+
[
|
|
389
|
+
cmd.Name.GET_COMPUTED_LABEL,
|
|
390
|
+
get('/session/:sessionId/element/:id/computedlabel'),
|
|
391
|
+
],
|
|
320
392
|
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
321
|
-
[
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
393
|
+
[
|
|
394
|
+
cmd.Name.GET_ELEMENT_ATTRIBUTE,
|
|
395
|
+
(cmd) => {
|
|
396
|
+
return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name')
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
[
|
|
400
|
+
cmd.Name.IS_ELEMENT_DISPLAYED,
|
|
401
|
+
(cmd) => {
|
|
402
|
+
return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id')
|
|
403
|
+
},
|
|
404
|
+
],
|
|
327
405
|
// Cookie management.
|
|
328
406
|
[cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
|
|
329
407
|
[cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
|
|
@@ -337,12 +415,16 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
337
415
|
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
|
|
338
416
|
// Screenshots.
|
|
339
417
|
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
340
|
-
[
|
|
418
|
+
[
|
|
419
|
+
cmd.Name.TAKE_ELEMENT_SCREENSHOT,
|
|
420
|
+
get('/session/:sessionId/element/:id/screenshot'),
|
|
421
|
+
],
|
|
422
|
+
// print page.
|
|
423
|
+
[cmd.Name.PRINT_PAGE, post('/session/:sessionId/print')],
|
|
341
424
|
// Log extensions.
|
|
342
425
|
[cmd.Name.GET_LOG, post('/session/:sessionId/se/log')],
|
|
343
426
|
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/se/log/types')],
|
|
344
|
-
])
|
|
345
|
-
|
|
427
|
+
])
|
|
346
428
|
|
|
347
429
|
/**
|
|
348
430
|
* Handles sending HTTP messages to a remote end.
|
|
@@ -350,7 +432,6 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
350
432
|
* @interface
|
|
351
433
|
*/
|
|
352
434
|
class Client {
|
|
353
|
-
|
|
354
435
|
/**
|
|
355
436
|
* Sends a request to the server. The client will automatically follow any
|
|
356
437
|
* redirects returned by the server, fulfilling the returned promise with the
|
|
@@ -360,11 +441,9 @@ class Client {
|
|
|
360
441
|
* @return {!Promise<Response>} A promise that will be fulfilled with the
|
|
361
442
|
* server's response.
|
|
362
443
|
*/
|
|
363
|
-
send(httpRequest) {}
|
|
444
|
+
send(httpRequest) {} // eslint-disable-line
|
|
364
445
|
}
|
|
365
446
|
|
|
366
|
-
|
|
367
|
-
|
|
368
447
|
/**
|
|
369
448
|
* @param {Map<string, CommandSpec>} customCommands
|
|
370
449
|
* A map of custom command definitions.
|
|
@@ -374,46 +453,44 @@ class Client {
|
|
|
374
453
|
* command to execute.
|
|
375
454
|
*/
|
|
376
455
|
function buildRequest(customCommands, w3c, command) {
|
|
377
|
-
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
378
|
-
let spec = customCommands && customCommands.get(command.getName())
|
|
456
|
+
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
457
|
+
let spec = customCommands && customCommands.get(command.getName())
|
|
379
458
|
if (spec) {
|
|
380
|
-
return toHttpRequest(spec)
|
|
459
|
+
return toHttpRequest(spec)
|
|
381
460
|
}
|
|
382
461
|
|
|
383
462
|
if (w3c) {
|
|
384
|
-
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
463
|
+
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
385
464
|
if (typeof spec === 'function') {
|
|
386
|
-
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
387
|
-
let newCommand = spec(command)
|
|
388
|
-
return buildRequest(customCommands, w3c, newCommand)
|
|
465
|
+
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
466
|
+
let newCommand = spec(command)
|
|
467
|
+
return buildRequest(customCommands, w3c, newCommand)
|
|
389
468
|
} else if (spec) {
|
|
390
|
-
return toHttpRequest(spec)
|
|
469
|
+
return toHttpRequest(spec)
|
|
391
470
|
}
|
|
392
471
|
}
|
|
393
472
|
|
|
394
|
-
spec = COMMAND_MAP.get(command.getName())
|
|
473
|
+
spec = COMMAND_MAP.get(command.getName())
|
|
395
474
|
if (spec) {
|
|
396
|
-
return toHttpRequest(spec)
|
|
475
|
+
return toHttpRequest(spec)
|
|
397
476
|
}
|
|
398
477
|
throw new error.UnknownCommandError(
|
|
399
|
-
|
|
478
|
+
'Unrecognized command: ' + command.getName()
|
|
479
|
+
)
|
|
400
480
|
|
|
401
481
|
/**
|
|
402
482
|
* @param {CommandSpec} resource
|
|
403
483
|
* @return {!Request}
|
|
404
484
|
*/
|
|
405
485
|
function toHttpRequest(resource) {
|
|
406
|
-
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
407
|
-
let parameters = command.getParameters()
|
|
408
|
-
let path = buildPath(resource.path, parameters)
|
|
409
|
-
return new Request(resource.method, path, parameters)
|
|
486
|
+
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
487
|
+
let parameters = command.getParameters()
|
|
488
|
+
let path = buildPath(resource.path, parameters)
|
|
489
|
+
return new Request(resource.method, path, parameters)
|
|
410
490
|
}
|
|
411
491
|
}
|
|
412
492
|
|
|
413
|
-
|
|
414
|
-
const CLIENTS =
|
|
415
|
-
/** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
|
|
416
|
-
|
|
493
|
+
const CLIENTS = /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */ new WeakMap()
|
|
417
494
|
|
|
418
495
|
/**
|
|
419
496
|
* A command executor that communicates with the server using JSON over HTTP.
|
|
@@ -435,7 +512,7 @@ class Executor {
|
|
|
435
512
|
* to the client.
|
|
436
513
|
*/
|
|
437
514
|
constructor(client) {
|
|
438
|
-
CLIENTS.set(this, client)
|
|
515
|
+
CLIENTS.set(this, client)
|
|
439
516
|
|
|
440
517
|
/**
|
|
441
518
|
* Whether this executor should use the W3C wire protocol. The executor
|
|
@@ -444,13 +521,13 @@ class Executor {
|
|
|
444
521
|
* `true` to force the executor into W3C mode.
|
|
445
522
|
* @type {boolean}
|
|
446
523
|
*/
|
|
447
|
-
this.w3c = false
|
|
524
|
+
this.w3c = false
|
|
448
525
|
|
|
449
526
|
/** @private {Map<string, CommandSpec>} */
|
|
450
|
-
this.customCommands_ = null
|
|
527
|
+
this.customCommands_ = null
|
|
451
528
|
|
|
452
529
|
/** @private {!logging.Logger} */
|
|
453
|
-
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
530
|
+
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
454
531
|
}
|
|
455
532
|
|
|
456
533
|
/**
|
|
@@ -468,64 +545,65 @@ class Executor {
|
|
|
468
545
|
*/
|
|
469
546
|
defineCommand(name, method, path) {
|
|
470
547
|
if (!this.customCommands_) {
|
|
471
|
-
this.customCommands_ = new Map
|
|
548
|
+
this.customCommands_ = new Map()
|
|
472
549
|
}
|
|
473
|
-
this.customCommands_.set(name, {method, path})
|
|
550
|
+
this.customCommands_.set(name, { method, path })
|
|
474
551
|
}
|
|
475
552
|
|
|
476
553
|
/** @override */
|
|
477
554
|
async execute(command) {
|
|
478
|
-
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
479
|
-
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
555
|
+
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
556
|
+
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
480
557
|
|
|
481
|
-
let client = CLIENTS.get(this)
|
|
558
|
+
let client = CLIENTS.get(this)
|
|
482
559
|
if (promise.isPromise(client)) {
|
|
483
|
-
client = await client
|
|
484
|
-
CLIENTS.set(this, client)
|
|
560
|
+
client = await client
|
|
561
|
+
CLIENTS.set(this, client)
|
|
485
562
|
}
|
|
486
563
|
|
|
487
|
-
let response = await client.send(request)
|
|
488
|
-
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
564
|
+
let response = await client.send(request)
|
|
565
|
+
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
489
566
|
|
|
490
|
-
let httpResponse = /** @type {!Response} */(response)
|
|
491
|
-
let {isW3C, value} = parseHttpResponse(command, httpResponse)
|
|
567
|
+
let httpResponse = /** @type {!Response} */ (response)
|
|
568
|
+
let { isW3C, value } = parseHttpResponse(command, httpResponse)
|
|
492
569
|
|
|
493
570
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
494
571
|
if (!value || !value.sessionId) {
|
|
495
572
|
throw new error.WebDriverError(
|
|
496
|
-
|
|
573
|
+
`Unable to parse new session response: ${response.body}`
|
|
574
|
+
)
|
|
497
575
|
}
|
|
498
576
|
|
|
499
577
|
// The remote end is a W3C compliant server if there is no `status`
|
|
500
578
|
// field in the response.
|
|
501
579
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
502
|
-
this.w3c = this.w3c || isW3C
|
|
580
|
+
this.w3c = this.w3c || isW3C
|
|
503
581
|
}
|
|
504
582
|
|
|
505
583
|
// No implementations use the `capabilities` key yet...
|
|
506
|
-
let capabilities = value.capabilities || value.value
|
|
584
|
+
let capabilities = value.capabilities || value.value
|
|
507
585
|
return new Session(
|
|
508
|
-
|
|
586
|
+
/** @type {{sessionId: string}} */ (value).sessionId,
|
|
587
|
+
capabilities
|
|
588
|
+
)
|
|
509
589
|
}
|
|
510
590
|
|
|
511
|
-
return typeof value === 'undefined' ? null : value
|
|
591
|
+
return typeof value === 'undefined' ? null : value
|
|
512
592
|
}
|
|
513
593
|
}
|
|
514
594
|
|
|
515
|
-
|
|
516
595
|
/**
|
|
517
596
|
* @param {string} str .
|
|
518
597
|
* @return {?} .
|
|
519
598
|
*/
|
|
520
599
|
function tryParse(str) {
|
|
521
600
|
try {
|
|
522
|
-
return JSON.parse(str)
|
|
601
|
+
return JSON.parse(str)
|
|
523
602
|
} catch (ignored) {
|
|
524
603
|
// Do nothing.
|
|
525
604
|
}
|
|
526
605
|
}
|
|
527
606
|
|
|
528
|
-
|
|
529
607
|
/**
|
|
530
608
|
* Callback used to parse {@link Response} objects from a
|
|
531
609
|
* {@link HttpClient}.
|
|
@@ -541,51 +619,49 @@ function tryParse(str) {
|
|
|
541
619
|
function parseHttpResponse(command, httpResponse) {
|
|
542
620
|
if (httpResponse.status < 200) {
|
|
543
621
|
// This should never happen, but throw the raw response so users report it.
|
|
544
|
-
throw new error.WebDriverError(
|
|
545
|
-
`Unexpected HTTP response:\n${httpResponse}`);
|
|
622
|
+
throw new error.WebDriverError(`Unexpected HTTP response:\n${httpResponse}`)
|
|
546
623
|
}
|
|
547
624
|
|
|
548
|
-
let parsed = tryParse(httpResponse.body)
|
|
625
|
+
let parsed = tryParse(httpResponse.body)
|
|
549
626
|
if (parsed && typeof parsed === 'object') {
|
|
550
|
-
let value = parsed.value
|
|
627
|
+
let value = parsed.value
|
|
551
628
|
let isW3C =
|
|
552
|
-
|
|
553
|
-
|
|
629
|
+
value !== null &&
|
|
630
|
+
typeof value === 'object' &&
|
|
631
|
+
typeof parsed.status === 'undefined'
|
|
554
632
|
|
|
555
633
|
if (!isW3C) {
|
|
556
|
-
error.checkLegacyResponse(parsed)
|
|
634
|
+
error.checkLegacyResponse(parsed)
|
|
557
635
|
|
|
558
636
|
// Adjust legacy new session responses to look like W3C to simplify
|
|
559
637
|
// later processing.
|
|
560
638
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
561
|
-
value = parsed
|
|
639
|
+
value = parsed
|
|
562
640
|
}
|
|
563
|
-
|
|
564
641
|
} else if (httpResponse.status > 399) {
|
|
565
|
-
error.throwDecodedError(value)
|
|
642
|
+
error.throwDecodedError(value)
|
|
566
643
|
}
|
|
567
644
|
|
|
568
|
-
return {isW3C, value}
|
|
645
|
+
return { isW3C, value }
|
|
569
646
|
}
|
|
570
647
|
|
|
571
648
|
if (parsed !== undefined) {
|
|
572
|
-
return {isW3C: false, value: parsed}
|
|
649
|
+
return { isW3C: false, value: parsed }
|
|
573
650
|
}
|
|
574
651
|
|
|
575
|
-
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
652
|
+
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
576
653
|
|
|
577
654
|
// 404 represents an unknown command; anything else > 399 is a generic unknown
|
|
578
655
|
// error.
|
|
579
656
|
if (httpResponse.status == 404) {
|
|
580
|
-
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
657
|
+
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
581
658
|
} else if (httpResponse.status >= 400) {
|
|
582
|
-
throw new error.WebDriverError(value)
|
|
659
|
+
throw new error.WebDriverError(value)
|
|
583
660
|
}
|
|
584
661
|
|
|
585
|
-
return {isW3C: false, value: value || null}
|
|
662
|
+
return { isW3C: false, value: value || null }
|
|
586
663
|
}
|
|
587
664
|
|
|
588
|
-
|
|
589
665
|
/**
|
|
590
666
|
* Builds a fully qualified path using the given set of command parameters. Each
|
|
591
667
|
* path segment prefixed with ':' will be replaced by the value of the
|
|
@@ -596,33 +672,33 @@ function parseHttpResponse(command, httpResponse) {
|
|
|
596
672
|
* @return {string} The modified path.
|
|
597
673
|
*/
|
|
598
674
|
function buildPath(path, parameters) {
|
|
599
|
-
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
675
|
+
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
600
676
|
if (pathParameters) {
|
|
601
677
|
for (let i = 0; i < pathParameters.length; ++i) {
|
|
602
|
-
let key = pathParameters[i].substring(2)
|
|
678
|
+
let key = pathParameters[i].substring(2) // Trim the /:
|
|
603
679
|
if (key in parameters) {
|
|
604
|
-
let value = parameters[key]
|
|
680
|
+
let value = parameters[key]
|
|
605
681
|
if (WebElement.isId(value)) {
|
|
606
682
|
// When inserting a WebElement into the URL, only use its ID value,
|
|
607
683
|
// not the full JSON.
|
|
608
|
-
value = WebElement.extractId(value)
|
|
684
|
+
value = WebElement.extractId(value)
|
|
609
685
|
}
|
|
610
|
-
path = path.replace(pathParameters[i], '/' + value)
|
|
611
|
-
delete parameters[key]
|
|
686
|
+
path = path.replace(pathParameters[i], '/' + value)
|
|
687
|
+
delete parameters[key]
|
|
612
688
|
} else {
|
|
613
689
|
throw new error.InvalidArgumentError(
|
|
614
|
-
|
|
690
|
+
'Missing required parameter: ' + key
|
|
691
|
+
)
|
|
615
692
|
}
|
|
616
693
|
}
|
|
617
694
|
}
|
|
618
|
-
return path
|
|
695
|
+
return path
|
|
619
696
|
}
|
|
620
697
|
|
|
621
|
-
|
|
622
698
|
// PUBLIC API
|
|
623
699
|
|
|
624
|
-
exports.Executor = Executor
|
|
625
|
-
exports.Client = Client
|
|
626
|
-
exports.Request = Request
|
|
627
|
-
exports.Response = Response
|
|
628
|
-
exports.buildPath = buildPath
|
|
700
|
+
exports.Executor = Executor
|
|
701
|
+
exports.Client = Client
|
|
702
|
+
exports.Request = Request
|
|
703
|
+
exports.Response = Response
|
|
704
|
+
exports.buildPath = buildPath // Exported for testing.
|