selenium-webdriver 4.0.0-alpha.4 → 4.0.0-alpha.8
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/.eslintrc.js +27 -0
- package/.prettierrc +4 -0
- package/CHANGES.md +22 -1
- package/README.md +4 -4
- package/chrome.js +72 -590
- package/chromium.js +1024 -0
- package/devtools/CDPConnection.js +37 -0
- package/devtools/generator/protocol-dts-generator.js +429 -0
- package/edge.js +124 -86
- package/firefox.js +201 -202
- 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 +141 -151
- package/io/zip.js +75 -65
- package/lib/README +5 -0
- package/lib/actions.js +138 -121
- package/lib/atoms/find-elements.js +123 -0
- package/lib/atoms/get-attribute.js +79 -60
- package/lib/atoms/is-displayed.js +72 -73
- package/{example/chrome_mobile_emulation.js → lib/atoms/make-atoms-module.js} +16 -23
- package/lib/by.js +192 -65
- package/lib/capabilities.js +82 -78
- package/lib/command.js +18 -20
- package/lib/error.js +134 -168
- package/lib/http.js +324 -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 -162
- package/lib/webdriver.js +733 -522
- package/net/index.js +30 -36
- package/net/portprober.js +31 -144
- package/opera.js +406 -0
- package/package.json +18 -11
- package/proxy.js +2 -2
- package/remote/index.js +190 -187
- package/safari.js +40 -49
- package/LICENSE +0 -202
- package/NOTICE +0 -2
- package/example/chrome_android.js +0 -42
- package/example/firefox_channels.js +0 -77
- package/example/google_search.js +0 -50
- package/example/google_search_test.js +0 -72
- package/example/headless.js +0 -55
- package/example/logging.js +0 -68
- package/testing/index.js +0 -498
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
|
-
let ret = []
|
|
72
|
-
headers.forEach(function(value, name) {
|
|
73
|
-
ret.push(`${name.toLowerCase()}: ${value}`)
|
|
74
|
-
})
|
|
75
|
-
return ret.join('\n')
|
|
79
|
+
let 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,151 @@ 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
|
+
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
238
|
+
[
|
|
239
|
+
cmd.Name.IS_ELEMENT_SELECTED,
|
|
240
|
+
get('/session/:sessionId/element/:id/selected'),
|
|
241
|
+
],
|
|
242
|
+
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
243
|
+
[
|
|
244
|
+
cmd.Name.IS_ELEMENT_DISPLAYED,
|
|
245
|
+
get('/session/:sessionId/element/:id/displayed'),
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
cmd.Name.GET_ELEMENT_LOCATION,
|
|
249
|
+
get('/session/:sessionId/element/:id/location'),
|
|
250
|
+
],
|
|
251
|
+
[cmd.Name.GET_ELEMENT_SIZE, get('/session/:sessionId/element/:id/size')],
|
|
252
|
+
[
|
|
253
|
+
cmd.Name.GET_ELEMENT_ATTRIBUTE,
|
|
254
|
+
get('/session/:sessionId/element/:id/attribute/:name'),
|
|
255
|
+
],
|
|
256
|
+
[
|
|
257
|
+
cmd.Name.GET_ELEMENT_PROPERTY,
|
|
258
|
+
get('/session/:sessionId/element/:id/property/:name'),
|
|
259
|
+
],
|
|
260
|
+
[
|
|
261
|
+
cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
|
262
|
+
get('/session/:sessionId/element/:id/css/:propertyName'),
|
|
263
|
+
],
|
|
264
|
+
[
|
|
265
|
+
cmd.Name.TAKE_ELEMENT_SCREENSHOT,
|
|
266
|
+
get('/session/:sessionId/element/:id/screenshot'),
|
|
267
|
+
],
|
|
268
|
+
[cmd.Name.SWITCH_TO_WINDOW, post('/session/:sessionId/window')],
|
|
269
|
+
[
|
|
270
|
+
cmd.Name.MAXIMIZE_WINDOW,
|
|
271
|
+
post('/session/:sessionId/window/current/maximize'),
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
cmd.Name.GET_WINDOW_POSITION,
|
|
275
|
+
get('/session/:sessionId/window/current/position'),
|
|
276
|
+
],
|
|
277
|
+
[
|
|
278
|
+
cmd.Name.SET_WINDOW_POSITION,
|
|
279
|
+
post('/session/:sessionId/window/current/position'),
|
|
280
|
+
],
|
|
281
|
+
[cmd.Name.GET_WINDOW_SIZE, get('/session/:sessionId/window/current/size')],
|
|
282
|
+
[cmd.Name.SET_WINDOW_SIZE, post('/session/:sessionId/window/current/size')],
|
|
283
|
+
[cmd.Name.SWITCH_TO_FRAME, post('/session/:sessionId/frame')],
|
|
284
|
+
[cmd.Name.SWITCH_TO_FRAME_PARENT, post('/session/:sessionId/frame/parent')],
|
|
285
|
+
[cmd.Name.GET_PAGE_SOURCE, get('/session/:sessionId/source')],
|
|
286
|
+
[cmd.Name.GET_TITLE, get('/session/:sessionId/title')],
|
|
287
|
+
[cmd.Name.EXECUTE_SCRIPT, post('/session/:sessionId/execute')],
|
|
288
|
+
[cmd.Name.EXECUTE_ASYNC_SCRIPT, post('/session/:sessionId/execute_async')],
|
|
289
|
+
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
290
|
+
[cmd.Name.GET_TIMEOUT, get('/session/:sessionId/timeouts')],
|
|
291
|
+
[cmd.Name.SET_TIMEOUT, post('/session/:sessionId/timeouts')],
|
|
292
|
+
[cmd.Name.ACCEPT_ALERT, post('/session/:sessionId/accept_alert')],
|
|
293
|
+
[cmd.Name.DISMISS_ALERT, post('/session/:sessionId/dismiss_alert')],
|
|
294
|
+
[cmd.Name.GET_ALERT_TEXT, get('/session/:sessionId/alert_text')],
|
|
295
|
+
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert_text')],
|
|
296
|
+
[cmd.Name.GET_LOG, post('/session/:sessionId/log')],
|
|
297
|
+
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/log/types')],
|
|
298
|
+
[cmd.Name.GET_SESSION_LOGS, post('/logs')],
|
|
299
|
+
[cmd.Name.UPLOAD_FILE, post('/session/:sessionId/se/file')],
|
|
300
|
+
[cmd.Name.LEGACY_ACTION_CLICK, post('/session/:sessionId/click')],
|
|
301
|
+
[
|
|
302
|
+
cmd.Name.LEGACY_ACTION_DOUBLE_CLICK,
|
|
303
|
+
post('/session/:sessionId/doubleclick'),
|
|
304
|
+
],
|
|
305
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_DOWN, post('/session/:sessionId/buttondown')],
|
|
306
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_UP, post('/session/:sessionId/buttonup')],
|
|
307
|
+
[cmd.Name.LEGACY_ACTION_MOUSE_MOVE, post('/session/:sessionId/moveto')],
|
|
308
|
+
[cmd.Name.LEGACY_ACTION_SEND_KEYS, post('/session/:sessionId/keys')],
|
|
309
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_DOWN, post('/session/:sessionId/touch/down')],
|
|
310
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_UP, post('/session/:sessionId/touch/up')],
|
|
311
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_MOVE, post('/session/:sessionId/touch/move')],
|
|
312
|
+
[
|
|
313
|
+
cmd.Name.LEGACY_ACTION_TOUCH_SCROLL,
|
|
314
|
+
post('/session/:sessionId/touch/scroll'),
|
|
315
|
+
],
|
|
316
|
+
[
|
|
317
|
+
cmd.Name.LEGACY_ACTION_TOUCH_LONG_PRESS,
|
|
318
|
+
post('/session/:sessionId/touch/longclick'),
|
|
319
|
+
],
|
|
320
|
+
[cmd.Name.LEGACY_ACTION_TOUCH_FLICK, post('/session/:sessionId/touch/flick')],
|
|
321
|
+
[
|
|
322
|
+
cmd.Name.LEGACY_ACTION_TOUCH_SINGLE_TAP,
|
|
323
|
+
post('/session/:sessionId/touch/click'),
|
|
324
|
+
],
|
|
325
|
+
[
|
|
326
|
+
cmd.Name.LEGACY_ACTION_TOUCH_DOUBLE_TAP,
|
|
327
|
+
post('/session/:sessionId/touch/doubleclick'),
|
|
328
|
+
],
|
|
329
|
+
])
|
|
265
330
|
|
|
266
331
|
/** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
|
|
267
332
|
const W3C_COMMAND_MAP = new Map([
|
|
@@ -305,23 +370,51 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
305
370
|
[cmd.Name.GET_ACTIVE_ELEMENT, get('/session/:sessionId/element/active')],
|
|
306
371
|
[cmd.Name.FIND_ELEMENT, post('/session/:sessionId/element')],
|
|
307
372
|
[cmd.Name.FIND_ELEMENTS, post('/session/:sessionId/elements')],
|
|
308
|
-
[
|
|
309
|
-
|
|
373
|
+
[
|
|
374
|
+
cmd.Name.FIND_ELEMENTS_RELATIVE,
|
|
375
|
+
(cmd) => {
|
|
376
|
+
return toExecuteAtomCommand(cmd, Atom.FIND_ELEMENTS, 'args')
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
[
|
|
380
|
+
cmd.Name.FIND_CHILD_ELEMENT,
|
|
381
|
+
post('/session/:sessionId/element/:id/element'),
|
|
382
|
+
],
|
|
383
|
+
[
|
|
384
|
+
cmd.Name.FIND_CHILD_ELEMENTS,
|
|
385
|
+
post('/session/:sessionId/element/:id/elements'),
|
|
386
|
+
],
|
|
310
387
|
// Element interaction.
|
|
311
388
|
[cmd.Name.GET_ELEMENT_TAG_NAME, get('/session/:sessionId/element/:id/name')],
|
|
312
|
-
[
|
|
389
|
+
[
|
|
390
|
+
cmd.Name.GET_ELEMENT_PROPERTY,
|
|
391
|
+
get('/session/:sessionId/element/:id/property/:name'),
|
|
392
|
+
],
|
|
393
|
+
[
|
|
394
|
+
cmd.Name.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
|
395
|
+
get('/session/:sessionId/element/:id/css/:propertyName'),
|
|
396
|
+
],
|
|
313
397
|
[cmd.Name.GET_ELEMENT_RECT, get('/session/:sessionId/element/:id/rect')],
|
|
314
398
|
[cmd.Name.CLEAR_ELEMENT, post('/session/:sessionId/element/:id/clear')],
|
|
315
399
|
[cmd.Name.CLICK_ELEMENT, post('/session/:sessionId/element/:id/click')],
|
|
316
|
-
[
|
|
400
|
+
[
|
|
401
|
+
cmd.Name.SEND_KEYS_TO_ELEMENT,
|
|
402
|
+
post('/session/:sessionId/element/:id/value'),
|
|
403
|
+
],
|
|
317
404
|
[cmd.Name.GET_ELEMENT_TEXT, get('/session/:sessionId/element/:id/text')],
|
|
318
405
|
[cmd.Name.IS_ELEMENT_ENABLED, get('/session/:sessionId/element/:id/enabled')],
|
|
319
|
-
[
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
406
|
+
[
|
|
407
|
+
cmd.Name.GET_ELEMENT_ATTRIBUTE,
|
|
408
|
+
(cmd) => {
|
|
409
|
+
return toExecuteAtomCommand(cmd, Atom.GET_ATTRIBUTE, 'id', 'name')
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
[
|
|
413
|
+
cmd.Name.IS_ELEMENT_DISPLAYED,
|
|
414
|
+
(cmd) => {
|
|
415
|
+
return toExecuteAtomCommand(cmd, Atom.IS_DISPLAYED, 'id')
|
|
416
|
+
},
|
|
417
|
+
],
|
|
325
418
|
// Cookie management.
|
|
326
419
|
[cmd.Name.GET_ALL_COOKIES, get('/session/:sessionId/cookie')],
|
|
327
420
|
[cmd.Name.ADD_COOKIE, post('/session/:sessionId/cookie')],
|
|
@@ -335,9 +428,16 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
335
428
|
[cmd.Name.SET_ALERT_TEXT, post('/session/:sessionId/alert/text')],
|
|
336
429
|
// Screenshots.
|
|
337
430
|
[cmd.Name.SCREENSHOT, get('/session/:sessionId/screenshot')],
|
|
338
|
-
[
|
|
339
|
-
|
|
340
|
-
|
|
431
|
+
[
|
|
432
|
+
cmd.Name.TAKE_ELEMENT_SCREENSHOT,
|
|
433
|
+
get('/session/:sessionId/element/:id/screenshot'),
|
|
434
|
+
],
|
|
435
|
+
// print page.
|
|
436
|
+
[cmd.Name.PRINT_PAGE, post('/session/:sessionId/print')],
|
|
437
|
+
// Log extensions.
|
|
438
|
+
[cmd.Name.GET_LOG, post('/session/:sessionId/se/log')],
|
|
439
|
+
[cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/se/log/types')],
|
|
440
|
+
])
|
|
341
441
|
|
|
342
442
|
/**
|
|
343
443
|
* Handles sending HTTP messages to a remote end.
|
|
@@ -345,7 +445,6 @@ const W3C_COMMAND_MAP = new Map([
|
|
|
345
445
|
* @interface
|
|
346
446
|
*/
|
|
347
447
|
class Client {
|
|
348
|
-
|
|
349
448
|
/**
|
|
350
449
|
* Sends a request to the server. The client will automatically follow any
|
|
351
450
|
* redirects returned by the server, fulfilling the returned promise with the
|
|
@@ -355,11 +454,9 @@ class Client {
|
|
|
355
454
|
* @return {!Promise<Response>} A promise that will be fulfilled with the
|
|
356
455
|
* server's response.
|
|
357
456
|
*/
|
|
358
|
-
send(httpRequest) {}
|
|
457
|
+
send(httpRequest) {} // eslint-disable-line
|
|
359
458
|
}
|
|
360
459
|
|
|
361
|
-
|
|
362
|
-
|
|
363
460
|
/**
|
|
364
461
|
* @param {Map<string, CommandSpec>} customCommands
|
|
365
462
|
* A map of custom command definitions.
|
|
@@ -369,46 +466,44 @@ class Client {
|
|
|
369
466
|
* command to execute.
|
|
370
467
|
*/
|
|
371
468
|
function buildRequest(customCommands, w3c, command) {
|
|
372
|
-
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
373
|
-
let spec = customCommands && customCommands.get(command.getName())
|
|
469
|
+
LOG.finest(() => `Translating command: ${command.getName()}`)
|
|
470
|
+
let spec = customCommands && customCommands.get(command.getName())
|
|
374
471
|
if (spec) {
|
|
375
|
-
return toHttpRequest(spec)
|
|
472
|
+
return toHttpRequest(spec)
|
|
376
473
|
}
|
|
377
474
|
|
|
378
475
|
if (w3c) {
|
|
379
|
-
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
476
|
+
spec = W3C_COMMAND_MAP.get(command.getName())
|
|
380
477
|
if (typeof spec === 'function') {
|
|
381
|
-
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
382
|
-
let newCommand = spec(command)
|
|
383
|
-
return buildRequest(customCommands, w3c, newCommand)
|
|
478
|
+
LOG.finest(() => `Transforming command for W3C: ${command.getName()}`)
|
|
479
|
+
let newCommand = spec(command)
|
|
480
|
+
return buildRequest(customCommands, w3c, newCommand)
|
|
384
481
|
} else if (spec) {
|
|
385
|
-
return toHttpRequest(spec)
|
|
482
|
+
return toHttpRequest(spec)
|
|
386
483
|
}
|
|
387
484
|
}
|
|
388
485
|
|
|
389
|
-
spec = COMMAND_MAP.get(command.getName())
|
|
486
|
+
spec = COMMAND_MAP.get(command.getName())
|
|
390
487
|
if (spec) {
|
|
391
|
-
return toHttpRequest(spec)
|
|
488
|
+
return toHttpRequest(spec)
|
|
392
489
|
}
|
|
393
490
|
throw new error.UnknownCommandError(
|
|
394
|
-
|
|
491
|
+
'Unrecognized command: ' + command.getName()
|
|
492
|
+
)
|
|
395
493
|
|
|
396
494
|
/**
|
|
397
495
|
* @param {CommandSpec} resource
|
|
398
496
|
* @return {!Request}
|
|
399
497
|
*/
|
|
400
498
|
function toHttpRequest(resource) {
|
|
401
|
-
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
402
|
-
let parameters = command.getParameters()
|
|
403
|
-
let path = buildPath(resource.path, parameters)
|
|
404
|
-
return new Request(resource.method, path, parameters)
|
|
499
|
+
LOG.finest(() => `Building HTTP request: ${JSON.stringify(resource)}`)
|
|
500
|
+
let parameters = command.getParameters()
|
|
501
|
+
let path = buildPath(resource.path, parameters)
|
|
502
|
+
return new Request(resource.method, path, parameters)
|
|
405
503
|
}
|
|
406
504
|
}
|
|
407
505
|
|
|
408
|
-
|
|
409
|
-
const CLIENTS =
|
|
410
|
-
/** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */new WeakMap;
|
|
411
|
-
|
|
506
|
+
const CLIENTS = /** !WeakMap<!Executor, !(Client|IThenable<!Client>)> */ new WeakMap()
|
|
412
507
|
|
|
413
508
|
/**
|
|
414
509
|
* A command executor that communicates with the server using JSON over HTTP.
|
|
@@ -430,7 +525,7 @@ class Executor {
|
|
|
430
525
|
* to the client.
|
|
431
526
|
*/
|
|
432
527
|
constructor(client) {
|
|
433
|
-
CLIENTS.set(this, client)
|
|
528
|
+
CLIENTS.set(this, client)
|
|
434
529
|
|
|
435
530
|
/**
|
|
436
531
|
* Whether this executor should use the W3C wire protocol. The executor
|
|
@@ -439,13 +534,13 @@ class Executor {
|
|
|
439
534
|
* `true` to force the executor into W3C mode.
|
|
440
535
|
* @type {boolean}
|
|
441
536
|
*/
|
|
442
|
-
this.w3c = false
|
|
537
|
+
this.w3c = false
|
|
443
538
|
|
|
444
539
|
/** @private {Map<string, CommandSpec>} */
|
|
445
|
-
this.customCommands_ = null
|
|
540
|
+
this.customCommands_ = null
|
|
446
541
|
|
|
447
542
|
/** @private {!logging.Logger} */
|
|
448
|
-
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
543
|
+
this.log_ = logging.getLogger('webdriver.http.Executor')
|
|
449
544
|
}
|
|
450
545
|
|
|
451
546
|
/**
|
|
@@ -463,64 +558,65 @@ class Executor {
|
|
|
463
558
|
*/
|
|
464
559
|
defineCommand(name, method, path) {
|
|
465
560
|
if (!this.customCommands_) {
|
|
466
|
-
this.customCommands_ = new Map
|
|
561
|
+
this.customCommands_ = new Map()
|
|
467
562
|
}
|
|
468
|
-
this.customCommands_.set(name, {method, path})
|
|
563
|
+
this.customCommands_.set(name, { method, path })
|
|
469
564
|
}
|
|
470
565
|
|
|
471
566
|
/** @override */
|
|
472
567
|
async execute(command) {
|
|
473
|
-
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
474
|
-
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
568
|
+
let request = buildRequest(this.customCommands_, this.w3c, command)
|
|
569
|
+
this.log_.finer(() => `>>> ${request.method} ${request.path}`)
|
|
475
570
|
|
|
476
|
-
let client = CLIENTS.get(this)
|
|
571
|
+
let client = CLIENTS.get(this)
|
|
477
572
|
if (promise.isPromise(client)) {
|
|
478
|
-
client = await client
|
|
479
|
-
CLIENTS.set(this, client)
|
|
573
|
+
client = await client
|
|
574
|
+
CLIENTS.set(this, client)
|
|
480
575
|
}
|
|
481
576
|
|
|
482
|
-
let response = await client.send(request)
|
|
483
|
-
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
577
|
+
let response = await client.send(request)
|
|
578
|
+
this.log_.finer(() => `>>>\n${request}\n<<<\n${response}`)
|
|
484
579
|
|
|
485
|
-
let httpResponse = /** @type {!Response} */(response)
|
|
486
|
-
let {isW3C, value} = parseHttpResponse(command, httpResponse)
|
|
580
|
+
let httpResponse = /** @type {!Response} */ (response)
|
|
581
|
+
let { isW3C, value } = parseHttpResponse(command, httpResponse)
|
|
487
582
|
|
|
488
583
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
489
584
|
if (!value || !value.sessionId) {
|
|
490
585
|
throw new error.WebDriverError(
|
|
491
|
-
|
|
586
|
+
`Unable to parse new session response: ${response.body}`
|
|
587
|
+
)
|
|
492
588
|
}
|
|
493
589
|
|
|
494
590
|
// The remote end is a W3C compliant server if there is no `status`
|
|
495
591
|
// field in the response.
|
|
496
592
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
497
|
-
this.w3c = this.w3c || isW3C
|
|
593
|
+
this.w3c = this.w3c || isW3C
|
|
498
594
|
}
|
|
499
595
|
|
|
500
596
|
// No implementations use the `capabilities` key yet...
|
|
501
|
-
let capabilities = value.capabilities || value.value
|
|
597
|
+
let capabilities = value.capabilities || value.value
|
|
502
598
|
return new Session(
|
|
503
|
-
|
|
599
|
+
/** @type {{sessionId: string}} */ (value).sessionId,
|
|
600
|
+
capabilities
|
|
601
|
+
)
|
|
504
602
|
}
|
|
505
603
|
|
|
506
|
-
return typeof value === 'undefined' ? null : value
|
|
604
|
+
return typeof value === 'undefined' ? null : value
|
|
507
605
|
}
|
|
508
606
|
}
|
|
509
607
|
|
|
510
|
-
|
|
511
608
|
/**
|
|
512
609
|
* @param {string} str .
|
|
513
610
|
* @return {?} .
|
|
514
611
|
*/
|
|
515
612
|
function tryParse(str) {
|
|
516
613
|
try {
|
|
517
|
-
return JSON.parse(str)
|
|
614
|
+
return JSON.parse(str)
|
|
518
615
|
} catch (ignored) {
|
|
519
616
|
// Do nothing.
|
|
520
617
|
}
|
|
521
618
|
}
|
|
522
619
|
|
|
523
|
-
|
|
524
620
|
/**
|
|
525
621
|
* Callback used to parse {@link Response} objects from a
|
|
526
622
|
* {@link HttpClient}.
|
|
@@ -536,51 +632,49 @@ function tryParse(str) {
|
|
|
536
632
|
function parseHttpResponse(command, httpResponse) {
|
|
537
633
|
if (httpResponse.status < 200) {
|
|
538
634
|
// This should never happen, but throw the raw response so users report it.
|
|
539
|
-
throw new error.WebDriverError(
|
|
540
|
-
`Unexpected HTTP response:\n${httpResponse}`);
|
|
635
|
+
throw new error.WebDriverError(`Unexpected HTTP response:\n${httpResponse}`)
|
|
541
636
|
}
|
|
542
637
|
|
|
543
|
-
let parsed = tryParse(httpResponse.body)
|
|
638
|
+
let parsed = tryParse(httpResponse.body)
|
|
544
639
|
if (parsed && typeof parsed === 'object') {
|
|
545
|
-
let value = parsed.value
|
|
640
|
+
let value = parsed.value
|
|
546
641
|
let isW3C =
|
|
547
|
-
|
|
548
|
-
|
|
642
|
+
value !== null &&
|
|
643
|
+
typeof value === 'object' &&
|
|
644
|
+
typeof parsed.status === 'undefined'
|
|
549
645
|
|
|
550
646
|
if (!isW3C) {
|
|
551
|
-
error.checkLegacyResponse(parsed)
|
|
647
|
+
error.checkLegacyResponse(parsed)
|
|
552
648
|
|
|
553
649
|
// Adjust legacy new session responses to look like W3C to simplify
|
|
554
650
|
// later processing.
|
|
555
651
|
if (command.getName() === cmd.Name.NEW_SESSION) {
|
|
556
|
-
value = parsed
|
|
652
|
+
value = parsed
|
|
557
653
|
}
|
|
558
|
-
|
|
559
654
|
} else if (httpResponse.status > 399) {
|
|
560
|
-
error.throwDecodedError(value)
|
|
655
|
+
error.throwDecodedError(value)
|
|
561
656
|
}
|
|
562
657
|
|
|
563
|
-
return {isW3C, value}
|
|
658
|
+
return { isW3C, value }
|
|
564
659
|
}
|
|
565
660
|
|
|
566
661
|
if (parsed !== undefined) {
|
|
567
|
-
return {isW3C: false, value: parsed}
|
|
662
|
+
return { isW3C: false, value: parsed }
|
|
568
663
|
}
|
|
569
664
|
|
|
570
|
-
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
665
|
+
let value = httpResponse.body.replace(/\r\n/g, '\n')
|
|
571
666
|
|
|
572
667
|
// 404 represents an unknown command; anything else > 399 is a generic unknown
|
|
573
668
|
// error.
|
|
574
669
|
if (httpResponse.status == 404) {
|
|
575
|
-
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
670
|
+
throw new error.UnsupportedOperationError(command.getName() + ': ' + value)
|
|
576
671
|
} else if (httpResponse.status >= 400) {
|
|
577
|
-
throw new error.WebDriverError(value)
|
|
672
|
+
throw new error.WebDriverError(value)
|
|
578
673
|
}
|
|
579
674
|
|
|
580
|
-
return {isW3C: false, value: value || null}
|
|
675
|
+
return { isW3C: false, value: value || null }
|
|
581
676
|
}
|
|
582
677
|
|
|
583
|
-
|
|
584
678
|
/**
|
|
585
679
|
* Builds a fully qualified path using the given set of command parameters. Each
|
|
586
680
|
* path segment prefixed with ':' will be replaced by the value of the
|
|
@@ -591,33 +685,33 @@ function parseHttpResponse(command, httpResponse) {
|
|
|
591
685
|
* @return {string} The modified path.
|
|
592
686
|
*/
|
|
593
687
|
function buildPath(path, parameters) {
|
|
594
|
-
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
688
|
+
let pathParameters = path.match(/\/:(\w+)\b/g)
|
|
595
689
|
if (pathParameters) {
|
|
596
690
|
for (let i = 0; i < pathParameters.length; ++i) {
|
|
597
|
-
let key = pathParameters[i].substring(2)
|
|
691
|
+
let key = pathParameters[i].substring(2) // Trim the /:
|
|
598
692
|
if (key in parameters) {
|
|
599
|
-
let value = parameters[key]
|
|
693
|
+
let value = parameters[key]
|
|
600
694
|
if (WebElement.isId(value)) {
|
|
601
695
|
// When inserting a WebElement into the URL, only use its ID value,
|
|
602
696
|
// not the full JSON.
|
|
603
|
-
value = WebElement.extractId(value)
|
|
697
|
+
value = WebElement.extractId(value)
|
|
604
698
|
}
|
|
605
|
-
path = path.replace(pathParameters[i], '/' + value)
|
|
606
|
-
delete parameters[key]
|
|
699
|
+
path = path.replace(pathParameters[i], '/' + value)
|
|
700
|
+
delete parameters[key]
|
|
607
701
|
} else {
|
|
608
702
|
throw new error.InvalidArgumentError(
|
|
609
|
-
|
|
703
|
+
'Missing required parameter: ' + key
|
|
704
|
+
)
|
|
610
705
|
}
|
|
611
706
|
}
|
|
612
707
|
}
|
|
613
|
-
return path
|
|
708
|
+
return path
|
|
614
709
|
}
|
|
615
710
|
|
|
616
|
-
|
|
617
711
|
// PUBLIC API
|
|
618
712
|
|
|
619
|
-
exports.Executor = Executor
|
|
620
|
-
exports.Client = Client
|
|
621
|
-
exports.Request = Request
|
|
622
|
-
exports.Response = Response
|
|
623
|
-
exports.buildPath = buildPath
|
|
713
|
+
exports.Executor = Executor
|
|
714
|
+
exports.Client = Client
|
|
715
|
+
exports.Request = Request
|
|
716
|
+
exports.Response = Response
|
|
717
|
+
exports.buildPath = buildPath // Exported for testing.
|