Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/javascript/webdriver/http/http.js
2868 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
/**
19
* @fileoverview Defines a `webdriver.CommandExecutor` that communicates
20
* with a server over HTTP.
21
*/
22
23
goog.provide('webdriver.http.Client');
24
goog.provide('webdriver.http.Executor');
25
goog.provide('webdriver.http.Request');
26
goog.provide('webdriver.http.Response');
27
28
goog.require('bot.ErrorCode');
29
goog.require('goog.array');
30
goog.require('webdriver.CommandExecutor');
31
goog.require('webdriver.CommandName');
32
goog.require('webdriver.logging');
33
34
35
36
/**
37
* Interface used for sending individual HTTP requests to the server.
38
* @interface
39
*/
40
webdriver.http.Client = function() {
41
};
42
43
44
/**
45
* Sends a request to the server. The client will automatically follow any
46
* redirects returned by the server, fulfilling the returned promise with the
47
* final response.
48
*
49
* @param {!webdriver.http.Request} request The request to send.
50
* @return {!goog.Promise<!webdriver.http.Response>} A promise that
51
* will be fulfilled with the server's response.
52
*/
53
webdriver.http.Client.prototype.send = function(request) {};
54
55
56
57
/**
58
* A command executor that communicates with a server using the WebDriver
59
* command protocol.
60
* @param {!webdriver.http.Client} client The client to use when sending
61
* requests to the server.
62
* @constructor
63
* @implements {webdriver.CommandExecutor}
64
*/
65
webdriver.http.Executor = function(client) {
66
67
/**
68
* Client used to communicate with the server.
69
* @private {!webdriver.http.Client}
70
*/
71
this.client_ = client;
72
73
/**
74
* @private {!Object<{method:string, path:string}>}
75
*/
76
this.customCommands_ = {};
77
78
/**
79
* @private {!webdriver.logging.Logger}
80
*/
81
this.log_ = webdriver.logging.getLogger('webdriver.http.Executor');
82
};
83
84
85
/**
86
* Defines a new command for use with this executor. When a command is sent,
87
* the `path` will be preprocessed using the command's parameters; any
88
* path segments prefixed with ":" will be replaced by the parameter of the
89
* same name. For example, given "/person/:name" and the parameters
90
* "{name: 'Bob'}", the final command path will be "/person/Bob".
91
*
92
* @param {string} name The command name.
93
* @param {string} method The HTTP method to use when sending this command.
94
* @param {string} path The path to send the command to, relative to
95
* the WebDriver server's command root and of the form
96
* "/path/:variable/segment".
97
*/
98
webdriver.http.Executor.prototype.defineCommand = function(
99
name, method, path) {
100
this.customCommands_[name] = {method: method, path: path};
101
};
102
103
104
/** @override */
105
webdriver.http.Executor.prototype.execute = function(command) {
106
var resource =
107
this.customCommands_[command.getName()] ||
108
webdriver.http.Executor.COMMAND_MAP_[command.getName()];
109
if (!resource) {
110
throw new Error('Unrecognized command: ' + command.getName());
111
}
112
113
var parameters = command.getParameters();
114
var path = webdriver.http.Executor.buildPath_(resource.path, parameters);
115
var request = new webdriver.http.Request(resource.method, path, parameters);
116
117
var log = this.log_;
118
log.finer(function() {
119
return '>>>\n' + request;
120
});
121
122
return this.client_.send(request).then(function(response) {
123
log.finer(function() {
124
return '<<<\n' + response;
125
});
126
return webdriver.http.Executor.parseHttpResponse_(
127
/** @type {!webdriver.http.Response} */ (response));
128
});
129
};
130
131
132
/**
133
* Builds a fully qualified path using the given set of command parameters. Each
134
* path segment prefixed with ':' will be replaced by the value of the
135
* corresponding parameter. All parameters spliced into the path will be
136
* removed from the parameter map.
137
* @param {string} path The original resource path.
138
* @param {!Object.<*>} parameters The parameters object to splice into
139
* the path.
140
* @return {string} The modified path.
141
* @private
142
*/
143
webdriver.http.Executor.buildPath_ = function(path, parameters) {
144
var pathParameters = path.match(/\/:(\w+)\b/g);
145
if (pathParameters) {
146
for (var i = 0; i < pathParameters.length; ++i) {
147
var key = pathParameters[i].substring(2); // Trim the /:
148
if (key in parameters) {
149
var value = parameters[key];
150
// TODO: move webdriver.WebElement.ELEMENT definition to a
151
// common file so we can reference it here without pulling in all of
152
// webdriver.WebElement's dependencies.
153
if (value && value['ELEMENT']) {
154
// When inserting a WebElement into the URL, only use its ID value,
155
// not the full JSON.
156
value = value['ELEMENT'];
157
}
158
path = path.replace(pathParameters[i], '/' + value);
159
delete parameters[key];
160
} else {
161
throw new Error('Missing required parameter: ' + key);
162
}
163
}
164
}
165
return path;
166
};
167
168
169
/**
170
* Callback used to parse {@link webdriver.http.Response} objects from a
171
* {@link webdriver.http.Client}.
172
* @param {!webdriver.http.Response} httpResponse The HTTP response to parse.
173
* @return {!bot.response.ResponseObject} The parsed response.
174
* @private
175
*/
176
webdriver.http.Executor.parseHttpResponse_ = function(httpResponse) {
177
try {
178
return /** @type {!bot.response.ResponseObject} */ (JSON.parse(
179
httpResponse.body));
180
} catch (ex) {
181
// Whoops, looks like the server sent us a malformed response. We'll need
182
// to manually build a response object based on the response code.
183
}
184
185
var response = {
186
'status': bot.ErrorCode.SUCCESS,
187
'value': httpResponse.body.replace(/\r\n/g, '\n')
188
};
189
190
if (!(httpResponse.status > 199 && httpResponse.status < 300)) {
191
// 404 represents an unknown command; anything else is a generic unknown
192
// error.
193
response['status'] = httpResponse.status == 404 ?
194
bot.ErrorCode.UNKNOWN_COMMAND :
195
bot.ErrorCode.UNKNOWN_ERROR;
196
}
197
198
return response;
199
};
200
201
202
/**
203
* Maps command names to resource locator.
204
* @private {!Object.<{method:string, path:string}>}
205
* @const
206
*/
207
webdriver.http.Executor.COMMAND_MAP_ = (function() {
208
return new Builder().
209
put(webdriver.CommandName.GET_SERVER_STATUS, get('/status')).
210
put(webdriver.CommandName.NEW_SESSION, post('/session')).
211
put(webdriver.CommandName.GET_SESSIONS, get('/sessions')).
212
put(webdriver.CommandName.DESCRIBE_SESSION, get('/session/:sessionId')).
213
put(webdriver.CommandName.QUIT, del('/session/:sessionId')).
214
put(webdriver.CommandName.CLOSE, del('/session/:sessionId/window')).
215
put(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE,
216
get('/session/:sessionId/window_handle')).
217
put(webdriver.CommandName.GET_WINDOW_HANDLES,
218
get('/session/:sessionId/window_handles')).
219
put(webdriver.CommandName.GET_CURRENT_URL,
220
get('/session/:sessionId/url')).
221
put(webdriver.CommandName.GET, post('/session/:sessionId/url')).
222
put(webdriver.CommandName.GO_BACK, post('/session/:sessionId/back')).
223
put(webdriver.CommandName.GO_FORWARD,
224
post('/session/:sessionId/forward')).
225
put(webdriver.CommandName.REFRESH,
226
post('/session/:sessionId/refresh')).
227
put(webdriver.CommandName.ADD_COOKIE,
228
post('/session/:sessionId/cookie')).
229
put(webdriver.CommandName.GET_ALL_COOKIES,
230
get('/session/:sessionId/cookie')).
231
put(webdriver.CommandName.DELETE_ALL_COOKIES,
232
del('/session/:sessionId/cookie')).
233
put(webdriver.CommandName.DELETE_COOKIE,
234
del('/session/:sessionId/cookie/:name')).
235
put(webdriver.CommandName.FIND_ELEMENT,
236
post('/session/:sessionId/element')).
237
put(webdriver.CommandName.FIND_ELEMENTS,
238
post('/session/:sessionId/elements')).
239
put(webdriver.CommandName.GET_ACTIVE_ELEMENT,
240
post('/session/:sessionId/element/active')).
241
put(webdriver.CommandName.FIND_CHILD_ELEMENT,
242
post('/session/:sessionId/element/:id/element')).
243
put(webdriver.CommandName.FIND_CHILD_ELEMENTS,
244
post('/session/:sessionId/element/:id/elements')).
245
put(webdriver.CommandName.CLEAR_ELEMENT,
246
post('/session/:sessionId/element/:id/clear')).
247
put(webdriver.CommandName.CLICK_ELEMENT,
248
post('/session/:sessionId/element/:id/click')).
249
put(webdriver.CommandName.SEND_KEYS_TO_ELEMENT,
250
post('/session/:sessionId/element/:id/value')).
251
put(webdriver.CommandName.SUBMIT_ELEMENT,
252
post('/session/:sessionId/element/:id/submit')).
253
put(webdriver.CommandName.GET_ELEMENT_TEXT,
254
get('/session/:sessionId/element/:id/text')).
255
put(webdriver.CommandName.GET_ELEMENT_TAG_NAME,
256
get('/session/:sessionId/element/:id/name')).
257
put(webdriver.CommandName.IS_ELEMENT_SELECTED,
258
get('/session/:sessionId/element/:id/selected')).
259
put(webdriver.CommandName.IS_ELEMENT_ENABLED,
260
get('/session/:sessionId/element/:id/enabled')).
261
put(webdriver.CommandName.IS_ELEMENT_DISPLAYED,
262
get('/session/:sessionId/element/:id/displayed')).
263
put(webdriver.CommandName.GET_ELEMENT_LOCATION,
264
get('/session/:sessionId/element/:id/location')).
265
put(webdriver.CommandName.GET_ELEMENT_SIZE,
266
get('/session/:sessionId/element/:id/size')).
267
put(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE,
268
get('/session/:sessionId/element/:id/attribute/:name')).
269
put(webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
270
get('/session/:sessionId/element/:id/css/:propertyName')).
271
put(webdriver.CommandName.ELEMENT_EQUALS,
272
get('/session/:sessionId/element/:id/equals/:other')).
273
put(webdriver.CommandName.TAKE_ELEMENT_SCREENSHOT,
274
get('/session/:sessionId/element/:id/screenshot')).
275
put(webdriver.CommandName.SWITCH_TO_WINDOW,
276
post('/session/:sessionId/window')).
277
put(webdriver.CommandName.MAXIMIZE_WINDOW,
278
post('/session/:sessionId/window/:windowHandle/maximize')).
279
put(webdriver.CommandName.GET_WINDOW_POSITION,
280
get('/session/:sessionId/window/:windowHandle/position')).
281
put(webdriver.CommandName.SET_WINDOW_POSITION,
282
post('/session/:sessionId/window/:windowHandle/position')).
283
put(webdriver.CommandName.GET_WINDOW_SIZE,
284
get('/session/:sessionId/window/:windowHandle/size')).
285
put(webdriver.CommandName.SET_WINDOW_SIZE,
286
post('/session/:sessionId/window/:windowHandle/size')).
287
put(webdriver.CommandName.SWITCH_TO_FRAME,
288
post('/session/:sessionId/frame')).
289
put(webdriver.CommandName.GET_PAGE_SOURCE,
290
get('/session/:sessionId/source')).
291
put(webdriver.CommandName.GET_TITLE,
292
get('/session/:sessionId/title')).
293
put(webdriver.CommandName.EXECUTE_SCRIPT,
294
post('/session/:sessionId/execute')).
295
put(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT,
296
post('/session/:sessionId/execute_async')).
297
put(webdriver.CommandName.SCREENSHOT,
298
get('/session/:sessionId/screenshot')).
299
put(webdriver.CommandName.SET_TIMEOUT,
300
post('/session/:sessionId/timeouts')).
301
put(webdriver.CommandName.SET_SCRIPT_TIMEOUT,
302
post('/session/:sessionId/timeouts/async_script')).
303
put(webdriver.CommandName.IMPLICITLY_WAIT,
304
post('/session/:sessionId/timeouts/implicit_wait')).
305
put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
306
put(webdriver.CommandName.CLICK, post('/session/:sessionId/click')).
307
put(webdriver.CommandName.DOUBLE_CLICK,
308
post('/session/:sessionId/doubleclick')).
309
put(webdriver.CommandName.MOUSE_DOWN,
310
post('/session/:sessionId/buttondown')).
311
put(webdriver.CommandName.MOUSE_UP, post('/session/:sessionId/buttonup')).
312
put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
313
put(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT,
314
post('/session/:sessionId/keys')).
315
put(webdriver.CommandName.TOUCH_SINGLE_TAP,
316
post('/session/:sessionId/touch/click')).
317
put(webdriver.CommandName.TOUCH_DOUBLE_TAP,
318
post('/session/:sessionId/touch/doubleclick')).
319
put(webdriver.CommandName.TOUCH_DOWN,
320
post('/session/:sessionId/touch/down')).
321
put(webdriver.CommandName.TOUCH_UP,
322
post('/session/:sessionId/touch/up')).
323
put(webdriver.CommandName.TOUCH_MOVE,
324
post('/session/:sessionId/touch/move')).
325
put(webdriver.CommandName.TOUCH_SCROLL,
326
post('/session/:sessionId/touch/scroll')).
327
put(webdriver.CommandName.TOUCH_LONG_PRESS,
328
post('/session/:sessionId/touch/longclick')).
329
put(webdriver.CommandName.TOUCH_FLICK,
330
post('/session/:sessionId/touch/flick')).
331
put(webdriver.CommandName.ACCEPT_ALERT,
332
post('/session/:sessionId/accept_alert')).
333
put(webdriver.CommandName.DISMISS_ALERT,
334
post('/session/:sessionId/dismiss_alert')).
335
put(webdriver.CommandName.GET_ALERT_TEXT,
336
get('/session/:sessionId/alert_text')).
337
put(webdriver.CommandName.SET_ALERT_TEXT,
338
post('/session/:sessionId/alert_text')).
339
put(webdriver.CommandName.GET_LOG, post('/session/:sessionId/log')).
340
put(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES,
341
get('/session/:sessionId/log/types')).
342
put(webdriver.CommandName.GET_SESSION_LOGS, post('/logs')).
343
put(webdriver.CommandName.UPLOAD_FILE, post('/session/:sessionId/file')).
344
build();
345
346
/** @constructor */
347
function Builder() {
348
var map = {};
349
350
this.put = function(name, resource) {
351
map[name] = resource;
352
return this;
353
};
354
355
this.build = function() {
356
return map;
357
};
358
}
359
360
function post(path) { return resource('POST', path); }
361
function del(path) { return resource('DELETE', path); }
362
function get(path) { return resource('GET', path); }
363
function resource(method, path) { return {method: method, path: path}; }
364
})();
365
366
367
/**
368
* Converts a headers object to a HTTP header block string.
369
* @param {!Object.<string>} headers The headers object to convert.
370
* @return {string} The headers as a string.
371
* @private
372
*/
373
webdriver.http.headersToString_ = function(headers) {
374
var ret = [];
375
for (var key in headers) {
376
ret.push(key + ': ' + headers[key]);
377
}
378
return ret.join('\n');
379
};
380
381
382
383
/**
384
* Describes a partial HTTP request. This class is a "partial" request and only
385
* defines the path on the server to send a request to. It is each
386
* {@link webdriver.http.Client}'s responsibility to build the full URL for the
387
* final request.
388
* @param {string} method The HTTP method to use for the request.
389
* @param {string} path Path on the server to send the request to.
390
* @param {Object=} opt_data This request's JSON data.
391
* @constructor
392
*/
393
webdriver.http.Request = function(method, path, opt_data) {
394
395
/**
396
* The HTTP method to use for the request.
397
* @type {string}
398
*/
399
this.method = method;
400
401
/**
402
* The path on the server to send the request to.
403
* @type {string}
404
*/
405
this.path = path;
406
407
/**
408
* This request's body.
409
* @type {!Object}
410
*/
411
this.data = opt_data || {};
412
413
/**
414
* The headers to send with the request.
415
* @type {!Object.<(string|number)>}
416
*/
417
this.headers = {'Accept': 'application/json; charset=utf-8'};
418
};
419
420
421
/** @override */
422
webdriver.http.Request.prototype.toString = function() {
423
return [
424
this.method + ' ' + this.path + ' HTTP/1.1',
425
webdriver.http.headersToString_(this.headers),
426
'',
427
JSON.stringify(this.data)
428
].join('\n');
429
};
430
431
432
433
/**
434
* Represents a HTTP response.
435
* @param {number} status The response code.
436
* @param {!Object.<string>} headers The response headers. All header
437
* names will be converted to lowercase strings for consistent lookups.
438
* @param {string} body The response body.
439
* @constructor
440
*/
441
webdriver.http.Response = function(status, headers, body) {
442
443
/**
444
* The HTTP response code.
445
* @type {number}
446
*/
447
this.status = status;
448
449
/**
450
* The response body.
451
* @type {string}
452
*/
453
this.body = body;
454
455
/**
456
* The response body.
457
* @type {!Object.<string>}
458
*/
459
this.headers = {};
460
for (var header in headers) {
461
this.headers[header.toLowerCase()] = headers[header];
462
}
463
};
464
465
466
/**
467
* Builds a {@link webdriver.http.Response} from a {@link XMLHttpRequest} or
468
* {@link XDomainRequest} response object.
469
* @param {!(XDomainRequest|XMLHttpRequest)} xhr The request to parse.
470
* @return {!webdriver.http.Response} The parsed response.
471
*/
472
webdriver.http.Response.fromXmlHttpRequest = function(xhr) {
473
var headers = {};
474
475
// getAllResponseHeaders is only available on XMLHttpRequest objects.
476
if (xhr.getAllResponseHeaders) {
477
var tmp = xhr.getAllResponseHeaders();
478
if (tmp) {
479
tmp = tmp.replace(/\r\n/g, '\n').split('\n');
480
goog.array.forEach(tmp, function(header) {
481
var parts = header.split(/\s*:\s*/, 2);
482
if (parts[0]) {
483
headers[parts[0]] = parts[1] || '';
484
}
485
});
486
}
487
}
488
489
// If xhr is a XDomainRequest object, it will not have a status.
490
// However, if we're parsing the response from a XDomainRequest, then
491
// that request must have been a success, so we can assume status == 200.
492
var status = xhr.status || 200;
493
return new webdriver.http.Response(status, headers,
494
xhr.responseText.replace(/\0/g, ''));
495
};
496
497
498
/** @override */
499
webdriver.http.Response.prototype.toString = function() {
500
var headers = webdriver.http.headersToString_(this.headers);
501
var ret = ['HTTP/1.1 ' + this.status, headers];
502
503
if (headers) {
504
ret.push('');
505
}
506
507
if (this.body) {
508
ret.push(this.body);
509
}
510
511
return ret.join('\n');
512
};
513
514