selenium-webdriver 4.0.0-alpha.5 → 4.0.0-beta.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 +35 -1
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +4 -4
- package/chrome.js +72 -592
- package/chromium.js +829 -0
- package/devtools/CDPConnection.js +37 -0
- package/edge.js +124 -86
- package/example/chrome_android.js +16 -13
- package/example/chrome_mobile_emulation.js +18 -14
- package/example/firefox_channels.js +35 -28
- package/example/google_search.js +9 -9
- package/example/google_search_test.js +20 -22
- package/example/headless.js +27 -19
- package/example/logging.js +34 -38
- package/firefox.js +200 -200
- package/http/index.js +131 -115
- 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 +140 -122
- package/lib/atoms/find-elements.js +124 -0
- package/lib/atoms/get-attribute.js +79 -59
- package/lib/atoms/is-displayed.js +72 -73
- package/lib/atoms/mutation-listener.js +38 -0
- package/lib/by.js +192 -65
- package/lib/capabilities.js +82 -78
- package/lib/command.js +20 -20
- package/lib/error.js +134 -168
- package/lib/http.js +337 -230
- package/lib/input.js +325 -302
- package/lib/logging.js +127 -138
- 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 +1024 -541
- package/net/index.js +30 -36
- package/net/portprober.js +31 -144
- package/opera.js +406 -0
- package/package.json +19 -12
- package/proxy.js +2 -2
- package/remote/index.js +190 -187
- package/safari.js +40 -49
- package/testing/index.js +166 -155
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(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,93 +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
|
-
|
|
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
|
+
])
|
|
265
338
|
|
|
266
339
|
/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
|
|
267
340
|
const W3C_COMMAND_MAP = new Map([
|
|
@@ -305,23 +378,59 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
305
378
|
[cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
|
|
306
379
|
[cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
|
|
307
380
|
[cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
|
|
308
|
-
[
|
|
309
|
-
|
|
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
|
+
],
|
|
310
395
|
// Element interaction.
|
|
311
396
|
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
312
|
-
[
|
|
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
|
+
],
|
|
313
405
|
[cmd.Name.GET_ELEMENT_RECT, get('/session/:sessionId/element/:id/rect')],
|
|
314
406
|
[cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
|
|
315
407
|
[cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
|
|
316
|
-
[
|
|
408
|
+
[
|
|
409
|
+
cmd.Name.SEND_KEYS_TO_ELEMENT,
|
|
410
|
+
post('/session/:sessionId/element/:id/value'),
|
|
411
|
+
],
|
|
317
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
|
+
],
|
|
318
421
|
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
319
|
-
[
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
],
|
|
325
434
|
// Cookie management.
|
|
326
435
|
[cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
|
|
327
436
|
[cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
|
|
@@ -335,12 +444,16 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
335
444
|
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
|
|
336
445
|
// Screenshots.
|
|
337
446
|
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
338
|
-
[
|
|
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')],
|
|
339
453
|
// Log extensions.
|
|
340
454
|
[cmd.Name.GET_LOG, post('/session/:sessionId/se/log')],
|
|
341
455
|
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/se/log/types')],
|
|
342
|
-
])
|
|
343
|
-
|
|
456
|
+
])
|
|
344
457
|
|
|
345
458
|
/**
|
|
346
459
|
* Handles sending HTTP messages to a remote end.
|
|
@@ -348,7 +461,6 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
348
461
|
* @interface
|
|
349
462
|
*/
|
|
350
463
|
class Client {
|
|
351
|
-
|
|
352
464
|
/**
|
|
353
465
|
* Sends a request to the server. The client will automatically follow any
|
|
354
466
|
* redirects returned by the server, fulfilling the returned promise with the
|
|
@@ -358,11 +470,9 @@ class Client {
|
|
|
358
470
|
* @return {!Promise<Response>} A promise that will be fulfilled with the
|
|
359
471
|
* server's response.
|
|
360
472
|
*/
|
|
361
|
-
send(httpRequest) {}
|
|
473
|
+
send(httpRequest) {} // eslint-disable-line
|
|
362
474
|
}
|
|
363
475
|
|
|
364
|
-
|
|
365
|
-
|
|
366
476
|
/**
|
|
367
477
|
* @param {Map<string, CommandSpec>} customCommands
|
|
368
478
|
* A map of custom command definitions.
|
|
@@ -372,46 +482,44 @@ class Client {
|
|
|
372
482
|
* command to execute.
|
|
373
483
|
*/
|
|
374
484
|
function buildRequest(customCommands, w3c, command) {
|
|
375
|
-
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
376
|
-
let spec = customCommands && customCommands.get(command.getName())
|
|
485
|
+
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
486
|
+
let spec = customCommands && customCommands.get(command.getName())
|
|
377
487
|
if (spec) {
|
|
378
|
-
return toHttpRequest(spec)
|
|
488
|
+
return toHttpRequest(spec)
|
|
379
489
|
}
|
|
380
490
|
|
|
381
491
|
if (w3c) {
|
|
382
|
-
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
492
|
+
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
383
493
|
if (typeof spec === 'function') {
|
|
384
|
-
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
385
|
-
let newCommand = spec(command)
|
|
386
|
-
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)
|
|
387
497
|
} else if (spec) {
|
|
388
|
-
return toHttpRequest(spec)
|
|
498
|
+
return toHttpRequest(spec)
|
|
389
499
|
}
|
|
390
500
|
}
|
|
391
501
|
|
|
392
|
-
spec = COMMAND_MAP.get(command.getName())
|
|
502
|
+
spec = COMMAND_MAP.get(command.getName())
|
|
393
503
|
if (spec) {
|
|
394
|
-
return toHttpRequest(spec)
|
|
504
|
+
return toHttpRequest(spec)
|
|
395
505
|
}
|
|
396
506
|
throw new error.UnknownCommandError(
|
|
397
|
-
|
|
507
|
+
'Unrecognized command: ' + command.getName()
|
|
508
|
+
)
|
|
398
509
|
|
|
399
510
|
/**
|
|
400
511
|
* @param {CommandSpec} resource
|
|
401
512
|
* @return {!Request}
|
|
402
513
|
*/
|
|
403
514
|
function toHttpRequest(resource) {
|
|
404
|
-
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
405
|
-
let parameters = command.getParameters()
|
|
406
|
-
let path = buildPath(resource.path, parameters)
|
|
407
|
-
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)
|
|
408
519
|
}
|
|
409
520
|
}
|
|
410
521
|
|
|
411
|
-
|
|
412
|
-
const CLIENTS =
|
|
413
|
-
/** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
|
|
414
|
-
|
|
522
|
+
const CLIENTS = /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */ new WeakMap()
|
|
415
523
|
|
|
416
524
|
/**
|
|
417
525
|
* A command executor that communicates with the server using JSON over HTTP.
|
|
@@ -433,7 +541,7 @@ class Executor {
|
|
|
433
541
|
* to the client.
|
|
434
542
|
*/
|
|
435
543
|
constructor(client) {
|
|
436
|
-
CLIENTS.set(this, client)
|
|
544
|
+
CLIENTS.set(this, client)
|
|
437
545
|
|
|
438
546
|
/**
|
|
439
547
|
* Whether this executor should use the W3C wire protocol. The executor
|
|
@@ -442,13 +550,13 @@ class Executor {
|
|
|
442
550
|
* `true` to force the executor into W3C mode.
|
|
443
551
|
* @type {boolean}
|
|
444
552
|
*/
|
|
445
|
-
this.w3c = false
|
|
553
|
+
this.w3c = false
|
|
446
554
|
|
|
447
555
|
/** @private {Map<string, CommandSpec>} */
|
|
448
|
-
this.customCommands_ = null
|
|
556
|
+
this.customCommands_ = null
|
|
449
557
|
|
|
450
558
|
/** @private {!logging.Logger} */
|
|
451
|
-
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
559
|
+
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
452
560
|
}
|
|
453
561
|
|
|
454
562
|
/**
|
|
@@ -466,64 +574,65 @@ class Executor {
|
|
|
466
574
|
*/
|
|
467
575
|
defineCommand(name, method, path) {
|
|
468
576
|
if (!this.customCommands_) {
|
|
469
|
-
this.customCommands_ = new Map
|
|
577
|
+
this.customCommands_ = new Map()
|
|
470
578
|
}
|
|
471
|
-
this.customCommands_.set(name, {method, path})
|
|
579
|
+
this.customCommands_.set(name, { method, path })
|
|
472
580
|
}
|
|
473
581
|
|
|
474
582
|
/** @override */
|
|
475
583
|
async execute(command) {
|
|
476
|
-
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
477
|
-
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
584
|
+
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
585
|
+
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
478
586
|
|
|
479
|
-
let client = CLIENTS.get(this)
|
|
587
|
+
let client = CLIENTS.get(this)
|
|
480
588
|
if (promise.isPromise(client)) {
|
|
481
|
-
client = await client
|
|
482
|
-
CLIENTS.set(this, client)
|
|
589
|
+
client = await client
|
|
590
|
+
CLIENTS.set(this, client)
|
|
483
591
|
}
|
|
484
592
|
|
|
485
|
-
let response = await client.send(request)
|
|
486
|
-
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
593
|
+
let response = await client.send(request)
|
|
594
|
+
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
487
595
|
|
|
488
|
-
let httpResponse = /** @type {!Response} */(response)
|
|
489
|
-
let {isW3C, value} = parseHttpResponse(command, httpResponse)
|
|
596
|
+
let httpResponse = /** @type {!Response} */ (response)
|
|
597
|
+
let { isW3C, value } = parseHttpResponse(command, httpResponse)
|
|
490
598
|
|
|
491
599
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
492
600
|
if (!value || !value.sessionId) {
|
|
493
601
|
throw new error.WebDriverError(
|
|
494
|
-
|
|
602
|
+
`Unable to parse new session response: ${response.body}`
|
|
603
|
+
)
|
|
495
604
|
}
|
|
496
605
|
|
|
497
606
|
// The remote end is a W3C compliant server if there is no `status`
|
|
498
607
|
// field in the response.
|
|
499
608
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
500
|
-
this.w3c = this.w3c || isW3C
|
|
609
|
+
this.w3c = this.w3c || isW3C
|
|
501
610
|
}
|
|
502
611
|
|
|
503
612
|
// No implementations use the `capabilities` key yet...
|
|
504
|
-
let capabilities = value.capabilities || value.value
|
|
613
|
+
let capabilities = value.capabilities || value.value
|
|
505
614
|
return new Session(
|
|
506
|
-
|
|
615
|
+
/** @type {{sessionId: string}} */ (value).sessionId,
|
|
616
|
+
capabilities
|
|
617
|
+
)
|
|
507
618
|
}
|
|
508
619
|
|
|
509
|
-
return typeof value === 'undefined' ? null : value
|
|
620
|
+
return typeof value === 'undefined' ? null : value
|
|
510
621
|
}
|
|
511
622
|
}
|
|
512
623
|
|
|
513
|
-
|
|
514
624
|
/**
|
|
515
625
|
* @param {string} str .
|
|
516
626
|
* @return {?} .
|
|
517
627
|
*/
|
|
518
628
|
function tryParse(str) {
|
|
519
629
|
try {
|
|
520
|
-
return JSON.parse(str)
|
|
630
|
+
return JSON.parse(str)
|
|
521
631
|
} catch (ignored) {
|
|
522
632
|
// Do nothing.
|
|
523
633
|
}
|
|
524
634
|
}
|
|
525
635
|
|
|
526
|
-
|
|
527
636
|
/**
|
|
528
637
|
* Callback used to parse {@link Response} objects from a
|
|
529
638
|
* {@link HttpClient}.
|
|
@@ -539,51 +648,49 @@ function tryParse(str) {
|
|
|
539
648
|
function parseHttpResponse(command, httpResponse) {
|
|
540
649
|
if (httpResponse.status < 200) {
|
|
541
650
|
// This should never happen, but throw the raw response so users report it.
|
|
542
|
-
throw new error.WebDriverError(
|
|
543
|
-
`Unexpected HTTP response:\n${httpResponse}`);
|
|
651
|
+
throw new error.WebDriverError(`Unexpected HTTP response:\n${httpResponse}`)
|
|
544
652
|
}
|
|
545
653
|
|
|
546
|
-
let parsed = tryParse(httpResponse.body)
|
|
654
|
+
let parsed = tryParse(httpResponse.body)
|
|
547
655
|
if (parsed && typeof parsed === 'object') {
|
|
548
|
-
let value = parsed.value
|
|
656
|
+
let value = parsed.value
|
|
549
657
|
let isW3C =
|
|
550
|
-
|
|
551
|
-
|
|
658
|
+
value !== null &&
|
|
659
|
+
typeof value === 'object' &&
|
|
660
|
+
typeof parsed.status === 'undefined'
|
|
552
661
|
|
|
553
662
|
if (!isW3C) {
|
|
554
|
-
error.checkLegacyResponse(parsed)
|
|
663
|
+
error.checkLegacyResponse(parsed)
|
|
555
664
|
|
|
556
665
|
// Adjust legacy new session responses to look like W3C to simplify
|
|
557
666
|
// later processing.
|
|
558
667
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
559
|
-
value = parsed
|
|
668
|
+
value = parsed
|
|
560
669
|
}
|
|
561
|
-
|
|
562
670
|
} else if (httpResponse.status > 399) {
|
|
563
|
-
error.throwDecodedError(value)
|
|
671
|
+
error.throwDecodedError(value)
|
|
564
672
|
}
|
|
565
673
|
|
|
566
|
-
return {isW3C, value}
|
|
674
|
+
return { isW3C, value }
|
|
567
675
|
}
|
|
568
676
|
|
|
569
677
|
if (parsed !== undefined) {
|
|
570
|
-
return {isW3C: false, value: parsed}
|
|
678
|
+
return { isW3C: false, value: parsed }
|
|
571
679
|
}
|
|
572
680
|
|
|
573
|
-
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
681
|
+
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
574
682
|
|
|
575
683
|
// 404 represents an unknown command; anything else > 399 is a generic unknown
|
|
576
684
|
// error.
|
|
577
685
|
if (httpResponse.status == 404) {
|
|
578
|
-
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
686
|
+
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
579
687
|
} else if (httpResponse.status >= 400) {
|
|
580
|
-
throw new error.WebDriverError(value)
|
|
688
|
+
throw new error.WebDriverError(value)
|
|
581
689
|
}
|
|
582
690
|
|
|
583
|
-
return {isW3C: false, value: value || null}
|
|
691
|
+
return { isW3C: false, value: value || null }
|
|
584
692
|
}
|
|
585
693
|
|
|
586
|
-
|
|
587
694
|
/**
|
|
588
695
|
* Builds a fully qualified path using the given set of command parameters. Each
|
|
589
696
|
* path segment prefixed with ':' will be replaced by the value of the
|
|
@@ -594,33 +701,33 @@ function parseHttpResponse(command, httpResponse) {
|
|
|
594
701
|
* @return {string} The modified path.
|
|
595
702
|
*/
|
|
596
703
|
function buildPath(path, parameters) {
|
|
597
|
-
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
704
|
+
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
598
705
|
if (pathParameters) {
|
|
599
706
|
for (let i = 0; i < pathParameters.length; ++i) {
|
|
600
|
-
let key = pathParameters[i].substring(2)
|
|
707
|
+
let key = pathParameters[i].substring(2) // Trim the /:
|
|
601
708
|
if (key in parameters) {
|
|
602
|
-
let value = parameters[key]
|
|
709
|
+
let value = parameters[key]
|
|
603
710
|
if (WebElement.isId(value)) {
|
|
604
711
|
// When inserting a WebElement into the URL, only use its ID value,
|
|
605
712
|
// not the full JSON.
|
|
606
|
-
value = WebElement.extractId(value)
|
|
713
|
+
value = WebElement.extractId(value)
|
|
607
714
|
}
|
|
608
|
-
path = path.replace(pathParameters[i], '/' + value)
|
|
609
|
-
delete parameters[key]
|
|
715
|
+
path = path.replace(pathParameters[i], '/' + value)
|
|
716
|
+
delete parameters[key]
|
|
610
717
|
} else {
|
|
611
718
|
throw new error.InvalidArgumentError(
|
|
612
|
-
|
|
719
|
+
'Missing required parameter: ' + key
|
|
720
|
+
)
|
|
613
721
|
}
|
|
614
722
|
}
|
|
615
723
|
}
|
|
616
|
-
return path
|
|
724
|
+
return path
|
|
617
725
|
}
|
|
618
726
|
|
|
619
|
-
|
|
620
727
|
// PUBLIC API
|
|
621
728
|
|
|
622
|
-
exports.Executor = Executor
|
|
623
|
-
exports.Client = Client
|
|
624
|
-
exports.Request = Request
|
|
625
|
-
exports.Response = Response
|
|
626
|
-
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.
|