selenium-webdriver 4.0.0-alpha.6 → 4.0.0-beta.2
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 +46 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +2 -0
- package/chrome.js +69 -80
- 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 +34 -39
- package/io/index.js +142 -152
- package/io/zip.js +75 -65
- package/lib/actions.js +139 -121
- 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 -20
- package/lib/error.js +134 -168
- package/lib/http.js +337 -232
- package/lib/input.js +319 -296
- package/lib/logging.js +126 -137
- package/lib/promise.js +60 -66
- 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 +190 -187
- package/safari.js +40 -49
- package/testing/index.js +504 -0
- package/lib/README +0 -5
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,159 @@ 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
|
+
[cmd.Name.LEGACY_ACTION_CLICK, post('/session/:sessionId/click')],
|
|
309
|
+
[
|
|
310
|
+
cmd.Name.LEGACY_ACTION_DOUBLE_CLICK,
|
|
311
|
+
post('/session/:sessionId/doubleclick'),
|
|
312
|
+
],
|
|
313
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_DOWN, post('/session/:sessionId/buttondown')],
|
|
314
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_UP, post('/session/:sessionId/buttonup')],
|
|
315
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_MOVE, post('/session/:sessionId/moveto')],
|
|
316
|
+
[cmd.Name.LEGACY_ACTION_SEND_KEYS, post('/session/:sessionId/keys')],
|
|
317
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_DOWN, post('/session/:sessionId/touch/down')],
|
|
318
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_UP, post('/session/:sessionId/touch/up')],
|
|
319
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_MOVE, post('/session/:sessionId/touch/move')],
|
|
320
|
+
[
|
|
321
|
+
cmd.Name.LEGACY_ACTION_TOUCH_SCROLL,
|
|
322
|
+
post('/session/:sessionId/touch/scroll'),
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
cmd.Name.LEGACY_ACTION_TOUCH_LONG_PRESS,
|
|
326
|
+
post('/session/:sessionId/touch/longclick'),
|
|
327
|
+
],
|
|
328
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_FLICK, post('/session/:sessionId/touch/flick')],
|
|
329
|
+
[
|
|
330
|
+
cmd.Name.LEGACY_ACTION_TOUCH_SINGLE_TAP,
|
|
331
|
+
post('/session/:sessionId/touch/click'),
|
|
332
|
+
],
|
|
333
|
+
[
|
|
334
|
+
cmd.Name.LEGACY_ACTION_TOUCH_DOUBLE_TAP,
|
|
335
|
+
post('/session/:sessionId/touch/doubleclick'),
|
|
336
|
+
],
|
|
337
|
+
])
|
|
266
338
|
|
|
267
339
|
/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
|
|
268
340
|
const W3C_COMMAND_MAP = new Map([
|
|
@@ -306,24 +378,59 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
306
378
|
[cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
|
|
307
379
|
[cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
|
|
308
380
|
[cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
|
|
309
|
-
[
|
|
310
|
-
|
|
381
|
+
[
|
|
382
|
+
cmd.Name.FIND_ELEMENTS_RELATIVE,
|
|
383
|
+
(cmd) => {
|
|
384
|
+
return toExecuteAtomCommand(cmd, Atom.FIND_ELEMENTS, 'args')
|
|
385
|
+
},
|
|
386
|
+
],
|
|
387
|
+
[
|
|
388
|
+
cmd.Name.FIND_CHILD_ELEMENT,
|
|
389
|
+
post('/session/:sessionId/element/:id/element'),
|
|
390
|
+
],
|
|
391
|
+
[
|
|
392
|
+
cmd.Name.FIND_CHILD_ELEMENTS,
|
|
393
|
+
post('/session/:sessionId/element/:id/elements'),
|
|
394
|
+
],
|
|
311
395
|
// Element interaction.
|
|
312
396
|
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
313
|
-
[
|
|
314
|
-
|
|
397
|
+
[
|
|
398
|
+
cmd.Name.GET_ELEMENT_PROPERTY,
|
|
399
|
+
get('/session/:sessionId/element/:id/property/:name'),
|
|
400
|
+
],
|
|
401
|
+
[
|
|
402
|
+
cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
|
403
|
+
get('/session/:sessionId/element/:id/css/:propertyName'),
|
|
404
|
+
],
|
|
315
405
|
[cmd.Name.GET_ELEMENT_RECT, get('/session/:sessionId/element/:id/rect')],
|
|
316
406
|
[cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
|
|
317
407
|
[cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
|
|
318
|
-
[
|
|
408
|
+
[
|
|
409
|
+
cmd.Name.SEND_KEYS_TO_ELEMENT,
|
|
410
|
+
post('/session/:sessionId/element/:id/value'),
|
|
411
|
+
],
|
|
319
412
|
[cmd.Name.GET_ELEMENT_TEXT, get('/session/:sessionId/element/:id/text')],
|
|
413
|
+
[
|
|
414
|
+
cmd.Name.GET_COMPUTED_ROLE,
|
|
415
|
+
get('/session/:sessionId/element/:id/computedrole'),
|
|
416
|
+
],
|
|
417
|
+
[
|
|
418
|
+
cmd.Name.GET_COMPUTED_LABEL,
|
|
419
|
+
get('/session/:sessionId/element/:id/computedlabel'),
|
|
420
|
+
],
|
|
320
421
|
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
321
|
-
[
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
422
|
+
[
|
|
423
|
+
cmd.Name.GET_ELEMENT_ATTRIBUTE,
|
|
424
|
+
(cmd) => {
|
|
425
|
+
return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name')
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
[
|
|
429
|
+
cmd.Name.IS_ELEMENT_DISPLAYED,
|
|
430
|
+
(cmd) => {
|
|
431
|
+
return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id')
|
|
432
|
+
},
|
|
433
|
+
],
|
|
327
434
|
// Cookie management.
|
|
328
435
|
[cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
|
|
329
436
|
[cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
|
|
@@ -337,12 +444,16 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
337
444
|
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
|
|
338
445
|
// Screenshots.
|
|
339
446
|
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
340
|
-
[
|
|
447
|
+
[
|
|
448
|
+
cmd.Name.TAKE_ELEMENT_SCREENSHOT,
|
|
449
|
+
get('/session/:sessionId/element/:id/screenshot'),
|
|
450
|
+
],
|
|
451
|
+
// print page.
|
|
452
|
+
[cmd.Name.PRINT_PAGE, post('/session/:sessionId/print')],
|
|
341
453
|
// Log extensions.
|
|
342
454
|
[cmd.Name.GET_LOG, post('/session/:sessionId/se/log')],
|
|
343
455
|
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/se/log/types')],
|
|
344
|
-
])
|
|
345
|
-
|
|
456
|
+
])
|
|
346
457
|
|
|
347
458
|
/**
|
|
348
459
|
* Handles sending HTTP messages to a remote end.
|
|
@@ -350,7 +461,6 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
350
461
|
* @interface
|
|
351
462
|
*/
|
|
352
463
|
class Client {
|
|
353
|
-
|
|
354
464
|
/**
|
|
355
465
|
* Sends a request to the server. The client will automatically follow any
|
|
356
466
|
* redirects returned by the server, fulfilling the returned promise with the
|
|
@@ -360,11 +470,9 @@ class Client {
|
|
|
360
470
|
* @return {!Promise<Response>} A promise that will be fulfilled with the
|
|
361
471
|
* server's response.
|
|
362
472
|
*/
|
|
363
|
-
send(httpRequest) {}
|
|
473
|
+
send(httpRequest) {} // eslint-disable-line
|
|
364
474
|
}
|
|
365
475
|
|
|
366
|
-
|
|
367
|
-
|
|
368
476
|
/**
|
|
369
477
|
* @param {Map<string, CommandSpec>} customCommands
|
|
370
478
|
* A map of custom command definitions.
|
|
@@ -374,46 +482,44 @@ class Client {
|
|
|
374
482
|
* command to execute.
|
|
375
483
|
*/
|
|
376
484
|
function buildRequest(customCommands, w3c, command) {
|
|
377
|
-
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
378
|
-
let spec = customCommands && customCommands.get(command.getName())
|
|
485
|
+
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
486
|
+
let spec = customCommands && customCommands.get(command.getName())
|
|
379
487
|
if (spec) {
|
|
380
|
-
return toHttpRequest(spec)
|
|
488
|
+
return toHttpRequest(spec)
|
|
381
489
|
}
|
|
382
490
|
|
|
383
491
|
if (w3c) {
|
|
384
|
-
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
492
|
+
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
385
493
|
if (typeof spec === 'function') {
|
|
386
|
-
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
387
|
-
let newCommand = spec(command)
|
|
388
|
-
return buildRequest(customCommands, w3c, newCommand)
|
|
494
|
+
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
495
|
+
let newCommand = spec(command)
|
|
496
|
+
return buildRequest(customCommands, w3c, newCommand)
|
|
389
497
|
} else if (spec) {
|
|
390
|
-
return toHttpRequest(spec)
|
|
498
|
+
return toHttpRequest(spec)
|
|
391
499
|
}
|
|
392
500
|
}
|
|
393
501
|
|
|
394
|
-
spec = COMMAND_MAP.get(command.getName())
|
|
502
|
+
spec = COMMAND_MAP.get(command.getName())
|
|
395
503
|
if (spec) {
|
|
396
|
-
return toHttpRequest(spec)
|
|
504
|
+
return toHttpRequest(spec)
|
|
397
505
|
}
|
|
398
506
|
throw new error.UnknownCommandError(
|
|
399
|
-
|
|
507
|
+
'Unrecognized command: ' + command.getName()
|
|
508
|
+
)
|
|
400
509
|
|
|
401
510
|
/**
|
|
402
511
|
* @param {CommandSpec} resource
|
|
403
512
|
* @return {!Request}
|
|
404
513
|
*/
|
|
405
514
|
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)
|
|
515
|
+
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
516
|
+
let parameters = command.getParameters()
|
|
517
|
+
let path = buildPath(resource.path, parameters)
|
|
518
|
+
return new Request(resource.method, path, parameters)
|
|
410
519
|
}
|
|
411
520
|
}
|
|
412
521
|
|
|
413
|
-
|
|
414
|
-
const CLIENTS =
|
|
415
|
-
/** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
|
|
416
|
-
|
|
522
|
+
const CLIENTS = /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */ new WeakMap()
|
|
417
523
|
|
|
418
524
|
/**
|
|
419
525
|
* A command executor that communicates with the server using JSON over HTTP.
|
|
@@ -435,7 +541,7 @@ class Executor {
|
|
|
435
541
|
* to the client.
|
|
436
542
|
*/
|
|
437
543
|
constructor(client) {
|
|
438
|
-
CLIENTS.set(this, client)
|
|
544
|
+
CLIENTS.set(this, client)
|
|
439
545
|
|
|
440
546
|
/**
|
|
441
547
|
* Whether this executor should use the W3C wire protocol. The executor
|
|
@@ -444,13 +550,13 @@ class Executor {
|
|
|
444
550
|
* `true` to force the executor into W3C mode.
|
|
445
551
|
* @type {boolean}
|
|
446
552
|
*/
|
|
447
|
-
this.w3c = false
|
|
553
|
+
this.w3c = false
|
|
448
554
|
|
|
449
555
|
/** @private {Map<string, CommandSpec>} */
|
|
450
|
-
this.customCommands_ = null
|
|
556
|
+
this.customCommands_ = null
|
|
451
557
|
|
|
452
558
|
/** @private {!logging.Logger} */
|
|
453
|
-
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
559
|
+
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
454
560
|
}
|
|
455
561
|
|
|
456
562
|
/**
|
|
@@ -468,64 +574,65 @@ class Executor {
|
|
|
468
574
|
*/
|
|
469
575
|
defineCommand(name, method, path) {
|
|
470
576
|
if (!this.customCommands_) {
|
|
471
|
-
this.customCommands_ = new Map
|
|
577
|
+
this.customCommands_ = new Map()
|
|
472
578
|
}
|
|
473
|
-
this.customCommands_.set(name, {method, path})
|
|
579
|
+
this.customCommands_.set(name, { method, path })
|
|
474
580
|
}
|
|
475
581
|
|
|
476
582
|
/** @override */
|
|
477
583
|
async execute(command) {
|
|
478
|
-
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
479
|
-
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
584
|
+
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
585
|
+
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
480
586
|
|
|
481
|
-
let client = CLIENTS.get(this)
|
|
587
|
+
let client = CLIENTS.get(this)
|
|
482
588
|
if (promise.isPromise(client)) {
|
|
483
|
-
client = await client
|
|
484
|
-
CLIENTS.set(this, client)
|
|
589
|
+
client = await client
|
|
590
|
+
CLIENTS.set(this, client)
|
|
485
591
|
}
|
|
486
592
|
|
|
487
|
-
let response = await client.send(request)
|
|
488
|
-
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
593
|
+
let response = await client.send(request)
|
|
594
|
+
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
489
595
|
|
|
490
|
-
let httpResponse = /** @type {!Response} */(response)
|
|
491
|
-
let {isW3C, value} = parseHttpResponse(command, httpResponse)
|
|
596
|
+
let httpResponse = /** @type {!Response} */ (response)
|
|
597
|
+
let { isW3C, value } = parseHttpResponse(command, httpResponse)
|
|
492
598
|
|
|
493
599
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
494
600
|
if (!value || !value.sessionId) {
|
|
495
601
|
throw new error.WebDriverError(
|
|
496
|
-
|
|
602
|
+
`Unable to parse new session response: ${response.body}`
|
|
603
|
+
)
|
|
497
604
|
}
|
|
498
605
|
|
|
499
606
|
// The remote end is a W3C compliant server if there is no `status`
|
|
500
607
|
// field in the response.
|
|
501
608
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
502
|
-
this.w3c = this.w3c || isW3C
|
|
609
|
+
this.w3c = this.w3c || isW3C
|
|
503
610
|
}
|
|
504
611
|
|
|
505
612
|
// No implementations use the `capabilities` key yet...
|
|
506
|
-
let capabilities = value.capabilities || value.value
|
|
613
|
+
let capabilities = value.capabilities || value.value
|
|
507
614
|
return new Session(
|
|
508
|
-
|
|
615
|
+
/** @type {{sessionId: string}} */ (value).sessionId,
|
|
616
|
+
capabilities
|
|
617
|
+
)
|
|
509
618
|
}
|
|
510
619
|
|
|
511
|
-
return typeof value === 'undefined' ? null : value
|
|
620
|
+
return typeof value === 'undefined' ? null : value
|
|
512
621
|
}
|
|
513
622
|
}
|
|
514
623
|
|
|
515
|
-
|
|
516
624
|
/**
|
|
517
625
|
* @param {string} str .
|
|
518
626
|
* @return {?} .
|
|
519
627
|
*/
|
|
520
628
|
function tryParse(str) {
|
|
521
629
|
try {
|
|
522
|
-
return JSON.parse(str)
|
|
630
|
+
return JSON.parse(str)
|
|
523
631
|
} catch (ignored) {
|
|
524
632
|
// Do nothing.
|
|
525
633
|
}
|
|
526
634
|
}
|
|
527
635
|
|
|
528
|
-
|
|
529
636
|
/**
|
|
530
637
|
* Callback used to parse {@link Response} objects from a
|
|
531
638
|
* {@link HttpClient}.
|
|
@@ -541,51 +648,49 @@ function tryParse(str) {
|
|
|
541
648
|
function parseHttpResponse(command, httpResponse) {
|
|
542
649
|
if (httpResponse.status < 200) {
|
|
543
650
|
// This should never happen, but throw the raw response so users report it.
|
|
544
|
-
throw new error.WebDriverError(
|
|
545
|
-
`Unexpected HTTP response:\n${httpResponse}`);
|
|
651
|
+
throw new error.WebDriverError(`Unexpected HTTP response:\n${httpResponse}`)
|
|
546
652
|
}
|
|
547
653
|
|
|
548
|
-
let parsed = tryParse(httpResponse.body)
|
|
654
|
+
let parsed = tryParse(httpResponse.body)
|
|
549
655
|
if (parsed && typeof parsed === 'object') {
|
|
550
|
-
let value = parsed.value
|
|
656
|
+
let value = parsed.value
|
|
551
657
|
let isW3C =
|
|
552
|
-
|
|
553
|
-
|
|
658
|
+
value !== null &&
|
|
659
|
+
typeof value === 'object' &&
|
|
660
|
+
typeof parsed.status === 'undefined'
|
|
554
661
|
|
|
555
662
|
if (!isW3C) {
|
|
556
|
-
error.checkLegacyResponse(parsed)
|
|
663
|
+
error.checkLegacyResponse(parsed)
|
|
557
664
|
|
|
558
665
|
// Adjust legacy new session responses to look like W3C to simplify
|
|
559
666
|
// later processing.
|
|
560
667
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
561
|
-
value = parsed
|
|
668
|
+
value = parsed
|
|
562
669
|
}
|
|
563
|
-
|
|
564
670
|
} else if (httpResponse.status > 399) {
|
|
565
|
-
error.throwDecodedError(value)
|
|
671
|
+
error.throwDecodedError(value)
|
|
566
672
|
}
|
|
567
673
|
|
|
568
|
-
return {isW3C, value}
|
|
674
|
+
return { isW3C, value }
|
|
569
675
|
}
|
|
570
676
|
|
|
571
677
|
if (parsed !== undefined) {
|
|
572
|
-
return {isW3C: false, value: parsed}
|
|
678
|
+
return { isW3C: false, value: parsed }
|
|
573
679
|
}
|
|
574
680
|
|
|
575
|
-
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
681
|
+
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
576
682
|
|
|
577
683
|
// 404 represents an unknown command; anything else > 399 is a generic unknown
|
|
578
684
|
// error.
|
|
579
685
|
if (httpResponse.status == 404) {
|
|
580
|
-
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
686
|
+
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
581
687
|
} else if (httpResponse.status >= 400) {
|
|
582
|
-
throw new error.WebDriverError(value)
|
|
688
|
+
throw new error.WebDriverError(value)
|
|
583
689
|
}
|
|
584
690
|
|
|
585
|
-
return {isW3C: false, value: value || null}
|
|
691
|
+
return { isW3C: false, value: value || null }
|
|
586
692
|
}
|
|
587
693
|
|
|
588
|
-
|
|
589
694
|
/**
|
|
590
695
|
* Builds a fully qualified path using the given set of command parameters. Each
|
|
591
696
|
* path segment prefixed with ':' will be replaced by the value of the
|
|
@@ -596,33 +701,33 @@ function parseHttpResponse(command, httpResponse) {
|
|
|
596
701
|
* @return {string} The modified path.
|
|
597
702
|
*/
|
|
598
703
|
function buildPath(path, parameters) {
|
|
599
|
-
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
704
|
+
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
600
705
|
if (pathParameters) {
|
|
601
706
|
for (let i = 0; i < pathParameters.length; ++i) {
|
|
602
|
-
let key = pathParameters[i].substring(2)
|
|
707
|
+
let key = pathParameters[i].substring(2) // Trim the /:
|
|
603
708
|
if (key in parameters) {
|
|
604
|
-
let value = parameters[key]
|
|
709
|
+
let value = parameters[key]
|
|
605
710
|
if (WebElement.isId(value)) {
|
|
606
711
|
// When inserting a WebElement into the URL, only use its ID value,
|
|
607
712
|
// not the full JSON.
|
|
608
|
-
value = WebElement.extractId(value)
|
|
713
|
+
value = WebElement.extractId(value)
|
|
609
714
|
}
|
|
610
|
-
path = path.replace(pathParameters[i], '/' + value)
|
|
611
|
-
delete parameters[key]
|
|
715
|
+
path = path.replace(pathParameters[i], '/' + value)
|
|
716
|
+
delete parameters[key]
|
|
612
717
|
} else {
|
|
613
718
|
throw new error.InvalidArgumentError(
|
|
614
|
-
|
|
719
|
+
'Missing required parameter: ' + key
|
|
720
|
+
)
|
|
615
721
|
}
|
|
616
722
|
}
|
|
617
723
|
}
|
|
618
|
-
return path
|
|
724
|
+
return path
|
|
619
725
|
}
|
|
620
726
|
|
|
621
|
-
|
|
622
727
|
// PUBLIC API
|
|
623
728
|
|
|
624
|
-
exports.Executor = Executor
|
|
625
|
-
exports.Client = Client
|
|
626
|
-
exports.Request = Request
|
|
627
|
-
exports.Response = Response
|
|
628
|
-
exports.buildPath = buildPath
|
|
729
|
+
exports.Executor = Executor
|
|
730
|
+
exports.Client = Client
|
|
731
|
+
exports.Request = Request
|
|
732
|
+
exports.Response = Response
|
|
733
|
+
exports.buildPath = buildPath // Exported for testing.
|