Chromium Code Reviews| Index: pkg/webdriver/lib/webdriver.dart |
| =================================================================== |
| --- pkg/webdriver/lib/webdriver.dart (revision 19698) |
| +++ pkg/webdriver/lib/webdriver.dart (working copy) |
| @@ -7,6 +7,7 @@ |
| import 'dart:json' as json; |
| import 'dart:uri'; |
| import 'dart:io'; |
| +import 'dart:async'; |
| part 'src/base64decoder.dart'; |
| @@ -33,18 +34,18 @@ |
| * String id; |
| * WebDriverSession session; |
| * Future f = web_driver.newSession('chrome'); |
| - * f.chain((_session) { |
| + * f.then((_session) { |
| * session = _session; |
| * return session.setUrl('http://my.web.site.com'); |
| - * }).chain((_) { |
| + * }).then((_) { |
| * return session.findElement('id', 'username'); |
| - * }).chain((element) { |
| + * }).then((element) { |
| * id = element['ELEMENT']; |
| * return session.sendKeyStrokesToElement(id, |
| * [ 'j', 'o', 'e', ' ', 'u', 's', 'e', 'r' ]); |
| - * }).chain((_) { |
| + * }).then((_) { |
| * return session.submit(id); |
| - * }).chain((_) { |
| + * }).then((_) { |
| * return session.close(); |
| * }).then((_) { |
| * session = null; |
| @@ -222,7 +223,7 @@ |
| * If a number or string, "/params" is appended to the URL. |
| */ |
| void _serverRequest(String http_method, String command, Completer completer, |
| - [List successCodes, Map params, Function customHandler]) { |
| + {List successCodes, Map params, Function customHandler}) { |
| var status = 0; |
| var results = null; |
| var message = null; |
| @@ -230,88 +231,86 @@ |
| successCodes = [ 200, 204 ]; |
| } |
| try { |
| - if (params != null && params is List && http_method != 'POST') { |
| - throw new Exception( |
| - 'The http method called for ${command} is ${http_method} but it has ' |
| - 'to be POST if you want to pass the JSON params ' |
| - '${json.stringify(params)}'); |
| - } |
| - |
| var path = command; |
| - if (params != null && (params is num || params is String)) { |
| - path = '$path/$params'; |
| + if (params != null) { |
| + if (params is num || params is String) { |
| + path = '$path/$params'; |
| + params = null; |
| + } else if (http_method != 'POST') { |
| + throw new Exception( |
| + 'The http method called for ${command} is ${http_method} but it has ' |
| + 'to be POST if you want to pass the JSON params ' |
| + '${json.stringify(params)}'); |
| + } |
| } |
| var client = new HttpClient(); |
| - var connection = client.open(http_method, _host, _port, path); |
| - |
| - connection.onRequest = (r) { |
| - r.headers.add(HttpHeaders.ACCEPT, "application/json"); |
| - r.headers.add( |
| + client.open(http_method, _host, _port, path).then((req) { |
| + req.followRedirects = false; |
| + req.headers.add(HttpHeaders.ACCEPT, "application/json"); |
| + req.headers.add( |
| HttpHeaders.CONTENT_TYPE, 'application/json;charset=UTF-8'); |
| - OutputStream s = r.outputStream; |
| - if (params != null && params is Map) { |
| - s.writeString(json.stringify(params)); |
| + if (params != null) { |
| + var body = json.stringify(params); |
| + req.write(body); |
| } |
| - s.close(); |
| - }; |
| - connection.onError = (e) { |
| - if (completer != null) { |
| - completer.completeError(new WebDriverError(-1, e)); |
| - completer = null; |
| - } |
| - }; |
| - connection.followRedirects = false; |
| - connection.onResponse = (r) { |
| - StringInputStream s = new StringInputStream(r.inputStream); |
| - StringBuffer sbuf = new StringBuffer(); |
| - s.onData = () { |
| - var data = s.read(); |
| - if (data != null) { |
| - sbuf.write(data); |
| - } |
| - }; |
| - s.onClosed = () { |
| - var value = null; |
| - results = sbuf.toString().trim(); |
| - // For some reason we get a bunch of NULs on the end |
| - // of the text and the json.parse blows up on these, so |
| - // strip them. |
| - // These NULs can be seen in the TCP packet, so it is not |
| - // an issue with character encoding; it seems to be a bug |
| - // in WebDriver stack. |
| - for (var i = results.length; --i >= 0;) { |
| - var code = results.codeUnitAt(i); |
| - if (code != 0) { |
| - results = results.substring(0, i + 1); |
| - break; |
| + req.close().then((rsp) { |
| + List<int> body = new List<int>(); |
| + rsp.listen(body.addAll, onDone: () { |
| + var value = null; |
| + results = new String.fromCharCodes(body); |
| + // For some reason we get a bunch of NULs on the end |
| + // of the text and the json.parse blows up on these, so |
| + // strip them. |
| + // These NULs can be seen in the TCP packet, so it is not |
| + // an issue with character encoding; it seems to be a bug |
| + // in WebDriver stack. |
| + for (var i = results.length; --i >= 0;) { |
| + var code = results.codeUnitAt(i); |
| + if (code != 0) { |
| + results = results.substring(0, i + 1); |
| + break; |
| + } |
| } |
| + if (successCodes.indexOf(rsp.statusCode) < 0) { |
|
Siggi Cherem (dart-lang)
2013/03/09 00:04:59
weird indentation here?
gram
2013/03/09 00:12:36
Done.
|
| + throw 'Unexpected response ${rsp.statusCode}; $results'; |
| } |
| - if (successCodes.indexOf(r.statusCode) < 0) { |
| - throw 'Unexpected response ${r.statusCode}'; |
| - } |
| - if (status == 0 && results.length > 0) { |
| - // 4xx responses send plain text; others send JSON. |
| - if (r.statusCode < 400) { |
| - results = json.parse(results); |
| - status = results['status']; |
| + if (status == 0 && results.length > 0) { |
| + // 4xx responses send plain text; others send JSON. |
| + if (rsp.statusCode < 400) { |
| + results = json.parse(results); |
| + status = results['status']; |
| + } |
| + if (results is Map && (results as Map).containsKey('value')) { |
| + value = results['value']; |
| + } |
| + if (value is Map && value.containsKey('message')) { |
| + message = value['message']; |
| + } |
| } |
| - if (results is Map && (results as Map).containsKey('value')) { |
| - value = results['value']; |
| + if (status == 0) { |
| + if (customHandler != null) { |
| + customHandler(rsp, value); |
| + } else if (completer != null) { |
| + completer.complete(value); |
| + } |
| + } else { |
|
Siggi Cherem (dart-lang)
2013/03/09 00:04:59
empty else?
gram
2013/03/09 00:12:36
Done.
|
| } |
| - if (value is Map && value.containsKey('message')) { |
| - message = value['message']; |
| - } |
| + }); |
| + }) |
| + .catchError((e) { |
| + if (completer != null) { |
| + completer.completeError(new WebDriverError(-1, e)); |
| + completer = null; |
| } |
| - if (status == 0) { |
| - if (customHandler != null) { |
| - customHandler(r, value); |
| - } else if (completer != null) { |
| - completer.complete(value); |
| - } |
| - } |
| - }; |
| - }; |
| + }); |
| + }) |
| + .catchError((e) { |
| + if (completer != null) { |
| + completer.completeError(new WebDriverError(-1, e)); |
| + completer = null; |
| + } |
| + }); |
| } catch (e, s) { |
| completer.completeError( |
| new WebDriverError(-1, e), s); |
| @@ -319,21 +318,28 @@ |
| } |
| } |
| - Future _simpleCommand(method, extraPath, [successCodes, params]) { |
| + Future _get(String extraPath) { |
| var completer = new Completer(); |
| - _serverRequest(method, '${_path}/$extraPath', completer, |
| - successCodes, params: params); |
| + _serverRequest('GET', '${_path}/$extraPath', completer); |
| return completer.future; |
| } |
| - Future _get(extraPath, [successCodes]) => |
| - _simpleCommand('GET', extraPath, successCodes); |
| + Future _getCustom(String extraPath, Function customHandler) => |
| + _serverRequest('GET', '${_path}/$extraPath', null, |
| + customHandler: customHandler); |
| - Future _post(extraPath, [successCodes, params]) => |
| - _simpleCommand('POST', extraPath, successCodes, params); |
| + Future _post(String extraPath, [String params]) { |
| + var completer = new Completer(); |
| + _serverRequest('POST', '${_path}/$extraPath', completer, |
| + params: params); |
| + return completer.future; |
| + } |
| - Future _delete(extraPath, [successCodes]) => |
| - _simpleCommand('DELETE', extraPath, successCodes); |
| + Future _delete(String extraPath) { |
| + var completer = new Completer(); |
| + _serverRequest('DELETE', '${_path}/$extraPath', completer); |
| + return completer.future; |
| + } |
| } |
| class WebDriver extends WebDriverBase { |
| @@ -432,23 +438,26 @@ |
| additional_capabilities['browserName'] = browser; |
| - _serverRequest('POST', '${_path}/session', null, [ 302 ], |
| + _serverRequest('POST', '${_path}/session', null, |
| + successCodes: [ 302 ], |
| customHandler: (r, v) { |
| var url = r.headers.value(HttpHeaders.LOCATION); |
| var session = new WebDriverSession.fromUrl(url); |
| completer.complete(session); |
| - }, params: { 'desiredCapabilities': additional_capabilities }); |
| + }, |
| + params: { 'desiredCapabilities': additional_capabilities }); |
| return completer.future; |
| } |
| /** Get the set of currently active sessions. */ |
| Future<List<WebDriverSession>> getSessions() { |
| var completer = new Completer(); |
| - _get('sessions', (result) { |
| + _getCustom('sessions', (r, v) { |
| var _sessions = []; |
| - for (var session in result) { |
| - _sessions.add(new WebDriverSession.fromUrl( |
| - '${this._path}/session/${session["id"]}')); |
| + for (var session in v) { |
| + var url = 'http://${this._host}:${this._port}${this._path}/' |
| + 'session/${session["id"]}'; |
| + _sessions.add(new WebDriverSession.fromUrl(url)); |
| } |
| completer.complete(_sessions); |
| }); |
| @@ -472,7 +481,7 @@ |
| * NoSuchWindow - If the specified window cannot be found. |
| */ |
| Future<String> setSize(int width, int height) => |
| - _post('size', params: { 'width': width, 'height': height }); |
| + _post('size', { 'width': width, 'height': height }); |
| /** Get the window position. */ |
| Future<Map> getPosition() => _get('position'); |
| @@ -483,7 +492,7 @@ |
| * Potential Errors: NoSuchWindow. |
| */ |
| Future setPosition(int x, int y) => |
| - _post('position', params: { 'x': x, 'y': y }); |
| + _post('position', { 'x': x, 'y': y }); |
| /** Maximize the specified window if not already maximized. */ |
| Future maximize() => _post('maximize'); |
| @@ -503,7 +512,7 @@ |
| * for before it is aborted and a Timeout error is returned to the client. |
| */ |
| Future setScriptTimeout(t) => |
| - _post('timeouts', params: { 'type': 'script', 'ms': t }); |
| + _post('timeouts', { 'type': 'script', 'ms': t }); |
| /*Future<String> setImplicitWaitTimeout(t) => |
| simplePost('timeouts', { 'type': 'implicit', 'ms': t });*/ |
| @@ -513,7 +522,7 @@ |
| * before it is aborted and a Timeout error is returned to the client. |
| */ |
| Future setPageLoadTimeout(t) => |
| - _post('timeouts', params: { 'type': 'page load', 'ms': t }); |
| + _post('timeouts', { 'type': 'page load', 'ms': t }); |
| /** |
| * Set the amount of time, in milliseconds, that asynchronous scripts |
| @@ -521,7 +530,7 @@ |
| * before they are aborted and a Timeout error is returned to the client. |
| */ |
| Future setAsyncScriptTimeout(t) => |
| - _post('timeouts/async_script', params: { 'ms': t }); |
| + _post('timeouts/async_script', { 'ms': t }); |
| /** |
| * Set the amount of time the driver should wait when searching for elements. |
| @@ -535,7 +544,7 @@ |
| * wait of 0ms. |
| */ |
| Future setImplicitWaitTimeout(t) => |
| - _post('timeouts/implicit_wait', params: { 'ms': t }); |
| + _post('timeouts/implicit_wait', { 'ms': t }); |
| /** |
| * Retrieve the current window handle. |
| @@ -569,7 +578,7 @@ |
| * |
| * Potential Errors: NoSuchWindow. |
| */ |
| - Future setUrl(String url) => _post('url', params: { 'url': url }); |
| + Future setUrl(String url) => _post('url', { 'url': url }); |
| /** |
| * Navigate forwards in the browser history, if possible. |
| @@ -612,7 +621,7 @@ |
| * Potential Errors: NoSuchWindow, StaleElementReference, JavaScriptError. |
| */ |
| Future execute(String script, [List args]) => |
| - _post('execute', params: { 'script': script, 'args': args }); |
| + _post('execute', { 'script': script, 'args': args }); |
| /** |
| * Inject a snippet of JavaScript into the page for execution in the context |
| @@ -641,7 +650,7 @@ |
| * callback is not invoked before the timout expires). |
| */ |
| Future executeAsync(String script, [List args]) => |
| - _post('execute_async', params: { 'script': script, 'args': args }); |
| + _post('execute_async', { 'script': script, 'args': args }); |
| /** |
| * Take a screenshot of the current page (PNG). |
| @@ -704,7 +713,7 @@ |
| * Potential Errors: ImeActivationFailedException, ImeNotAvailableException. |
| */ |
| Future activateIme(String engine) => |
| - _post('ime/activate', params: { 'engine': engine }); |
| + _post('ime/activate', { 'engine': engine }); |
| /** |
| * Change focus to another frame on the page. If the frame id is null, |
| @@ -714,7 +723,7 @@ |
| * |
| * Potential Errors: NoSuchWindow, NoSuchFrame. |
| */ |
| - Future setFrameFocus(id) => _post('frame', params: { 'id': id }); |
| + Future setFrameFocus(id) => _post('frame', { 'id': id }); |
| /** |
| * Change focus to another window. The window to change focus to may be |
| @@ -723,8 +732,7 @@ |
| * |
| * Potential Errors: NoSuchWindow. |
| */ |
| - Future setWindowFocus(name) => |
| - _post('window', params: { 'name': name }); |
| + Future setWindowFocus(name) => _post('window', { 'name': name }); |
| /** |
| * Close the current window. |
| @@ -762,8 +770,7 @@ |
| * current page's domain. See [getCookies] for the structure of a cookie |
| * Map. |
| */ |
| - Future setCookie(Map cookie) => |
| - _post('cookie', params: { 'cookie': cookie }); |
| + Future setCookie(Map cookie) => _post('cookie', { 'cookie': cookie }); |
| /** |
| * Delete all cookies visible to the current page. |
| @@ -827,7 +834,7 @@ |
| * using XPath and the input expression is invalid). |
| */ |
| Future<String> findElement(String strategy, String searchValue) => |
| - _post('element', params: { 'using': strategy, 'value' : searchValue }); |
| + _post('element', { 'using': strategy, 'value' : searchValue }); |
| /** |
| * Search for multiple elements on the page, starting from the document root. |
| @@ -838,7 +845,7 @@ |
| * Potential Errors: NoSuchWindow, XPathLookupError. |
| */ |
| Future<List<String>> findElements(String strategy, String searchValue) => |
| - _post('elements', params: { 'using': strategy, 'value' : searchValue }); |
| + _post('elements', { 'using': strategy, 'value' : searchValue }); |
| /** |
| * Get the element on the page that currently has focus. The element will |
| @@ -857,8 +864,7 @@ |
| */ |
| Future<String> |
| findElementFromId(String id, String strategy, String searchValue) { |
| - _post('element/$id/element', |
| - params: { 'using': strategy, 'value' : searchValue }); |
| + _post('element/$id/element', { 'using': strategy, 'value' : searchValue }); |
| } |
| /** |
| @@ -1002,7 +1008,7 @@ |
| * Potential Errors: NoSuchWindow, StaleElementReference, ElementNotVisible. |
| */ |
| Future sendKeyStrokesToElement(String id, List<String> keys) => |
| - _post('element/$id/value', params: { 'value': keys }); |
| + _post('element/$id/value', { 'value': keys }); |
| /** |
| * Send a sequence of key strokes to the active element. This command is |
| @@ -1013,8 +1019,7 @@ |
| * |
| * Potential Errors: NoSuchWindow. |
| */ |
| - Future sendKeyStrokes(List<String> keys) => |
| - _post('keys', params: { 'value': keys }); |
| + Future sendKeyStrokes(List<String> keys) => _post('keys', { 'value': keys }); |
| /** |
| * Query for an element's tag name, as a lower-case string. |
| @@ -1119,7 +1124,7 @@ |
| * Potential Errors: NoAlertPresent. |
| */ |
| Future sendKeyStrokesToPrompt(String text) => |
| - _post('alert_text', params: { 'text': text }); |
| + _post('alert_text', { 'text': text }); |
| /** |
| * Accepts the currently displayed alert dialog. Usually, this is equivalent |
| @@ -1146,7 +1151,7 @@ |
| * into view. |
| */ |
| Future moveTo(String id, int x, int y) => |
| - _post('moveto', params: { 'element': id, 'xoffset': x, 'yoffset' : y}); |
| + _post('moveto', { 'element': id, 'xoffset': x, 'yoffset' : y}); |
| /** |
| * Click a mouse button (at the coordinates set by the last [moveTo] command). |
| @@ -1156,8 +1161,7 @@ |
| * |
| * [button] should be 0 for left, 1 for middle, or 2 for right. |
| */ |
| - Future clickMouse([button = 0]) => |
| - _post('click', params: { 'button' : button }); |
| + Future clickMouse([button = 0]) => _post('click', { 'button' : button }); |
| /** |
| * Click and hold the left mouse button (at the coordinates set by the last |
| @@ -1167,8 +1171,7 @@ |
| * |
| * [button] should be 0 for left, 1 for middle, or 2 for right. |
| */ |
| - Future buttonDown([button = 0]) => |
| - _post('click', params: { 'button' : button }); |
| + Future buttonDown([button = 0]) => _post('click', { 'button' : button }); |
| /** |
| * Releases the mouse button previously held (where the mouse is currently |
| @@ -1178,27 +1181,22 @@ |
| * |
| * [button] should be 0 for left, 1 for middle, or 2 for right. |
| */ |
| - Future buttonUp([button = 0]) => |
| - _post('click', params: { 'button' : button }); |
| + Future buttonUp([button = 0]) => _post('click', { 'button' : button }); |
| /** Double-clicks at the current mouse coordinates (set by [moveTo]). */ |
| Future doubleClick() => _post('doubleclick'); |
| /** Single tap on the touch enabled device on the element with id [id]. */ |
| - Future touchClick(String id) => |
| - _post('touch/click', params: { 'element': id }); |
| + Future touchClick(String id) => _post('touch/click', { 'element': id }); |
| /** Finger down on the screen. */ |
| - Future touchDown(int x, int y) => |
| - _post('touch/down', params: { 'x': x, 'y': y }); |
| + Future touchDown(int x, int y) => _post('touch/down', { 'x': x, 'y': y }); |
| /** Finger up on the screen. */ |
| - Future touchUp(int x, int y) => |
| - _post('touch/up', params: { 'x': x, 'y': y }); |
| + Future touchUp(int x, int y) => _post('touch/up', { 'x': x, 'y': y }); |
| /** Finger move on the screen. */ |
| - Future touchMove(int x, int y) => |
| - _post('touch/move', params: { 'x': x, 'y': y }); |
| + Future touchMove(int x, int y) => _post('touch/move', { 'x': x, 'y': y }); |
| /** |
| * Scroll on the touch screen using finger based motion events. If [id] is |
| @@ -1206,21 +1204,20 @@ |
| */ |
| Future touchScroll(int xOffset, int yOffset, [String id = null]) { |
| if (id == null) { |
| - return _post('touch/scroll', |
| - params: { 'xoffset': xOffset, 'yoffset': yOffset }); |
| + return _post('touch/scroll', { 'xoffset': xOffset, 'yoffset': yOffset }); |
| } else { |
| return _post('touch/scroll', |
| - params: { 'element': id, 'xoffset': xOffset, 'yoffset': yOffset }); |
| + { 'element': id, 'xoffset': xOffset, 'yoffset': yOffset }); |
| } |
| } |
| /** Double tap on the touch screen using finger motion events. */ |
| Future touchDoubleClick(String id) => |
| - _post('touch/doubleclick', params: { 'element': id }); |
| + _post('touch/doubleclick', { 'element': id }); |
| /** Long press on the touch screen using finger motion events. */ |
| Future touchLongClick(String id) => |
| - _post('touch/longclick', params: { 'element': id }); |
| + _post('touch/longclick', { 'element': id }); |
| /** |
| * Flick on the touch screen using finger based motion events, starting |
| @@ -1228,7 +1225,7 @@ |
| */ |
| Future touchFlickFrom(String id, int xOffset, int yOffset, int speed) => |
| _post('touch/flick', |
| - params: { 'element': id, 'xoffset': xOffset, 'yoffset': yOffset, |
| + { 'element': id, 'xoffset': xOffset, 'yoffset': yOffset, |
| 'speed': speed }); |
| /** |
| @@ -1236,7 +1233,7 @@ |
| * instead of [touchFlickFrom] if you don'tr care where the flick starts. |
| */ |
| Future touchFlick(int xSpeed, int ySpeed) => |
| - _post('touch/flick', params: { 'xSpeed': xSpeed, 'ySpeed': ySpeed }); |
| + _post('touch/flick', { 'xSpeed': xSpeed, 'ySpeed': ySpeed }); |
| /** |
| * Get the current geo location. Returns a [Map] with latitude, |
| @@ -1246,7 +1243,7 @@ |
| /** Set the current geo location. */ |
| Future setLocation(double latitude, double longitude, double altitude) => |
| - _post('location', params: |
| + _post('location', |
| { 'latitude': latitude, |
| 'longitude': longitude, |
| 'altitude': altitude }); |
| @@ -1265,7 +1262,7 @@ |
| * Potential Errors: NoSuchWindow. |
| */ |
| Future setLocalStorageItem(String key, String value) => |
| - _post('local_storage', params: { 'key': key, 'value': value }); |
| + _post('local_storage', { 'key': key, 'value': value }); |
| /** |
| * Clear the local storage. |
| @@ -1310,7 +1307,7 @@ |
| * Potential Errors: NoSuchWindow. |
| */ |
| Future setSessionStorageItem(String key, String value) => |
| - _post('session_storage', params: { 'key': key, 'value': value }); |
| + _post('session_storage', { 'key': key, 'value': value }); |
| /** |
| * Clear the session storage. |
| @@ -1353,6 +1350,5 @@ |
| * 'level' (String) - The log level of the entry, for example, "INFO". |
| * 'message' (String) - The log message. |
| */ |
| - Future<List<Map>> getLogs(String type) => |
| - _post('log', params: { 'type': type }); |
| + Future<List<Map>> getLogs(String type) => _post('log', { 'type': type }); |
| } |