Merge branch 'master' of https://github.com/google/webdriver.dart
diff --git a/lib/pageloader.dart b/lib/pageloader.dart
deleted file mode 100644
index 136d7a5..0000000
--- a/lib/pageloader.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-library pageloader;
-
-import 'dart:async';
-import 'dart:mirrors';
-
-import 'webdriver.dart';
-
-part 'src/pageloader/annotations.dart';
-part 'src/pageloader/core.dart';
diff --git a/lib/src/alert.dart b/lib/src/alert.dart
index bf46a28..796b78f 100644
--- a/lib/src/alert.dart
+++ b/lib/src/alert.dart
@@ -8,8 +8,8 @@
*/
final String text;
- Alert._(this.text, prefix, commandProcessor)
- : super(prefix, commandProcessor);
+ Alert._(this.text, driver)
+ : super(driver, '');
/**
* Accepts the currently displayed alert (may not be the alert for which
@@ -18,7 +18,9 @@
* Throws [WebDriverError] no such alert exception if there isn't currently an
* alert.
*/
- Future<Alert> accept() => _post('accept_alert').then((_) => this);
+ Future accept() async {
+ await _post('accept_alert');
+ }
/**
* Dismisses the currently displayed alert (may not be the alert for which
@@ -27,7 +29,9 @@
* Throws [WebDriverError] no such alert exception if there isn't currently an
* alert.
*/
- Future<Alert> dismiss() => _post('dismiss_alert').then((_) => this);
+ Future dismiss() async {
+ await _post('dismiss_alert');
+ }
/**
* Sends keys to the currently displayed alert (may not be the alert for which
@@ -36,6 +40,7 @@
* Throws [WebDriverError] no such alert exception if there isn't currently an
* alert.
*/
- Future<Alert> sendKeys(String keysToSend) =>
- _post('alert_text', { 'text': keysToSend }).then((_) => this);
+ Future sendKeys(String keysToSend) async {
+ await _post('alert_text', { 'text': keysToSend });
+ }
}
diff --git a/lib/src/command_processor.dart b/lib/src/command_processor.dart
index 4345235..e8695ef 100644
--- a/lib/src/command_processor.dart
+++ b/lib/src/command_processor.dart
@@ -1,137 +1,49 @@
part of webdriver;
-class CommandProcessor {
- final Uri _uri;
+final ContentType _contentTypeJson = new ContentType("application", "json", charset: "utf-8");
+const _defaultDecoder = const JsonDecoder();
- String get _host => _uri.host;
- int get _port => _uri.port;
- String get path => _uri.path;
+class _CommandProcessor {
- String get url => _uri.toString();
+ final HttpClient client = new HttpClient();
- CommandProcessor([
- String host = 'localhost',
- int port = 4444,
- String path = '/wd/hub']) :
- _uri = new Uri(scheme: 'http', host: host, port: port, path: path) {
- assert(!this._host.isEmpty);
+ Future<Object> post(Uri uri, dynamic params, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+ HttpClientRequest request = await client.postUrl(uri);
+ _setUpRequest(request);
+ request.headers.contentType = _contentTypeJson;
+ request.encoding = UTF8;
+ request.write(JSON.encode(params));
+ return await _processResponse(await request.close(), decoder);
}
- void _failRequest(Completer completer, error, [stackTrace]) {
- completer
- .completeError(new WebDriverError(-1, error.toString()), stackTrace);
+ Future<Object> get(Uri uri, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+ HttpClientRequest request = await client.getUrl(uri);
+ _setUpRequest(request);
+ return await _processResponse(await request.close(), decoder);
}
- /**
- * Execute a request to the WebDriver server. [httpMethod] should be
- * one of 'GET', 'POST', or 'DELETE'. [command] is the text to append
- * to the base URL path to get the full URL. [params] are the additional
- * parameters. If a [List] or [Map] they will be posted as JSON parameters.
- * If a number or string, "/params" is appended to the URL.
- */
- Future _serverRequest(String httpMethod, String command, {params}) {
- const successCodes = const [ HttpStatus.OK, HttpStatus.NO_CONTENT ];
- var completer = new Completer();
+ Future<Object> delete(Uri uri, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+ HttpClientRequest request = await client.deleteUrl(uri);
+ _setUpRequest(request);
+ return await _processResponse(await request.close(), decoder);
+ }
- try {
- var path = command;
- if (params != null) {
- if (params is num || params is String) {
- path = '$path/$params';
- params = null;
- } else if (httpMethod != 'POST') {
- throw new Exception(
- 'The http method called for ${command} is ${httpMethod} but it '
- 'must be POST if you want to pass the JSON params '
- '${JSON.encode(params)}');
- }
- }
-
- var client = new HttpClient();
- client.open(httpMethod, _host, _port, path).then((req) {
- req.followRedirects = false;
- req.headers.add(HttpHeaders.ACCEPT, "application/json");
- req.headers.contentType = _CONTENT_TYPE_JSON;
- if (params != null) {
- var body = UTF8.encode(JSON.encode(params));
- req.contentLength = body.length;
- req.add(body);
- } else {
- req.contentLength = 0;
- }
- return req.close();
- }).then((HttpClientResponse rsp) {
- return rsp.transform(new Utf8Decoder())
- .fold(new StringBuffer(), (buffer, data) => buffer..write(data))
- .then((StringBuffer buffer) {
- // 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.
- var results = buffer.toString()
- .replaceAll(new RegExp('\u{0}*\$'), '');
-
- var status = 0;
- var message = null;
- var value = null;
- // 4xx responses send plain text; others send JSON
- if (HttpStatus.BAD_REQUEST <= rsp.statusCode
- && rsp.statusCode < HttpStatus.INTERNAL_SERVER_ERROR) {
- if (rsp.statusCode == HttpStatus.NOT_FOUND) {
- status = 9; // UnkownCommand
- } else {
- status = 13; // UnknownError
- }
- message = results;
- } else if (!results.isEmpty) {
- results = JSON.decode(results);
- if (results.containsKey('status')) {
- status = results['status'];
- }
- if (results.containsKey('value')) {
- value = results['value'];
- if (value is Map && value.containsKey('message')) {
- message = results['message'];
- }
- }
- }
-
- if (status != 0) {
- completer.completeError(new WebDriverError(status, message));
- } else if (!successCodes.contains(rsp.statusCode)) {
- completer.completeError(new WebDriverError(-1,
- 'Unexpected response ${rsp.statusCode}; $results'));
- } else {
- completer.complete(value);
- }
- });
- }).catchError((error, s) => _failRequest(completer, error, s));
- } catch (e, s) {
- _failRequest(completer, e, s);
+ _processResponse(HttpClientResponse response, Converter<String, dynamic> decoder) async {
+ var respBody = decoder.convert(await UTF8.decodeStream(response));
+ if (response.statusCode < 200 || response.statusCode > 299 || (respBody is Map && respBody['status'] != 0)) {
+ throw new WebDriverException(httpStatusCode: response.statusCode, httpReasonPhrase: response.reasonPhrase, jsonResp: respBody);
}
- return completer.future;
+ if (respBody is Map) {
+ return respBody['value'];
+ }
+ return respBody;
}
- Future get(String extraPath) => _serverRequest('GET', _command(extraPath));
-
- Future post(String extraPath, [params]) =>
- _serverRequest('POST', _command(extraPath), params: params);
-
- Future delete(String extraPath) =>
- _serverRequest('DELETE', _command(extraPath));
-
- String _command(String extraPath) {
- var command;
- if (extraPath.startsWith('/')) {
- command = '${path}$extraPath';
- } else {
- command = '${path}/$extraPath';
- }
- if (command.endsWith('/')) {
- command = command.substring(0, command.length - 1);
- }
- return command;
+ void _setUpRequest(HttpClientRequest request) {
+ request.followRedirects = false;
+ request.headers.add(HttpHeaders.ACCEPT, "application/json");
+ request.headers.add(HttpHeaders.ACCEPT, "application/json");
+ request.headers.add(HttpHeaders.ACCEPT_CHARSET, UTF8.name);
+ request.headers.add(HttpHeaders.CACHE_CONTROL, "no-cache");
}
-}
+}
\ No newline at end of file
diff --git a/lib/src/common.dart b/lib/src/common.dart
index dc14341..04e2c79 100644
--- a/lib/src/common.dart
+++ b/lib/src/common.dart
@@ -1,8 +1,6 @@
part of webdriver;
const String _ELEMENT = 'ELEMENT';
-final ContentType _CONTENT_TYPE_JSON =
- new ContentType("application", "json", charset: "utf-8");
/**
* Simple class to provide access to indexed properties such as WebElement
@@ -10,8 +8,8 @@
*/
class Attributes extends _WebDriverBase {
- Attributes._(command, prefix, commandProcessor)
- : super('$prefix/$command', commandProcessor);
+ Attributes._(driver, command)
+ : super(driver, command);
Future<String> operator [](String name) => _get(name);
}
@@ -47,7 +45,7 @@
abstract class SearchContext {
/// Searches for multiple elements within the context.
- Future<List<WebElement>> findElements(By by);
+ Stream<WebElement> findElements(By by);
/**
* Searchs for an element within the context.
@@ -60,25 +58,25 @@
abstract class _WebDriverBase {
final String _prefix;
- final CommandProcessor _commandProcessor;
+ final WebDriver driver;
- _WebDriverBase(this._prefix, this._commandProcessor);
+ _WebDriverBase(this.driver, this._prefix);
Future _post(String command, [param]) =>
- _commandProcessor.post(_command(command), param);
+ driver._post(resolve(command), param);
- Future _get(String command) => _commandProcessor.get(_command(command));
+ Future _get(String command) => driver._get(resolve(command));
- Future _delete(String command) => _commandProcessor.delete(_command(command));
+ Future _delete(String command) => driver._delete(resolve(command));
- String _command(String command) {
+ String resolve(command) {
+ if (_prefix == null || _prefix.isEmpty) {
+ return command;
+ }
if (command == null || command.isEmpty) {
return _prefix;
- } else if (_prefix == null || _prefix.isEmpty) {
- return '$command';
- } else {
- return '$_prefix/$command';
}
+ return '$_prefix/$command';
}
}
diff --git a/lib/src/exception.dart b/lib/src/exception.dart
new file mode 100644
index 0000000..675f890
--- /dev/null
+++ b/lib/src/exception.dart
@@ -0,0 +1,206 @@
+/*
+Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+part of webdriver;
+
+abstract class WebDriverException {
+ /**
+ * Either the status value returned in the JSON response (preferred) or the
+ * HTTP status code.
+ */
+ final int statusCode;
+
+ /**
+ * A message describing the error.
+ */
+ final String message;
+
+ factory WebDriverException(
+ {int httpStatusCode, String httpReasonPhrase, dynamic jsonResp}) {
+ if (jsonResp is Map) {
+ var status = jsonResp['status'];
+ var message = jsonResp['value']['message'];
+
+ switch (status) {
+ case 0:
+ throw new StateError(
+ 'Not a WebDriverError Status: 0 Message: $message');
+ case 6: // NoSuchDriver
+ return new NoSuchDriverException(status, message);
+ case 7: // NoSuchElement
+ return new NoSuchElementException(status, message);
+ case 8: // NoSuchFrame
+ return new NoSuchFrameException(status, message);
+ case 9: // UnknownCommand
+ return new UnknownCommandException(status, message);
+ case 10: // StaleElementReferenceException
+ return new StaleElementReferenceException(status, message);
+ case 11: // ElementNotVisible
+ return new ElementNotVisibleException(status, message);
+ case 12: // InvalidElementState
+ return new InvalidElementStateException(status, message);
+ case 15: // ElementIsNotSelectable
+ return new ElementIsNotSelectableException(status, message);
+ case 17: // JavaScriptError
+ return new JavaScriptException(status, message);
+ case 19: // XPathLookupError
+ return new XPathLookupException(status, message);
+ case 21: // Timeout
+ return new TimeoutException(status, message);
+ case 23: // NoSuchWindow
+ return new NoSuchWindowException(status, message);
+ case 24: // InvalidCookieDomain
+ return new InvalidCookieDomainException(status, message);
+ case 25: // UnableToSetCookie
+ return new UnableToSetCookieException(status, message);
+ case 26: // UnexpectedAlertOpen
+ return new UnexpectedAlertOpenException(status, message);
+ case 27: // NoAlertOpenError
+ return new NoAlertOpenException(status, message);
+ case 29: // InvalidElementCoordinates
+ return new InvalidElementCoordinatesException(status, message);
+ case 30: // IMENotAvailable
+ return new IMENotAvailableException(status, message);
+ case 31: // IMEEngineActivationFailed
+ return new IMEEngineActivationFailedException(status, message);
+ case 32: // InvalidSelector
+ return new InvalidSelectorException(status, message);
+ case 33: // SessionNotCreatedException
+ return new SessionNotCreatedException(status, message);
+ case 34: // MoveTargetOutOfBounds
+ return new MoveTargetOutOfBoundsException(status, message);
+ case 13: // UnknownError
+ default: // new error?
+ return new UnknownException(status, message);
+ }
+ }
+ if (jsonResp != null) {
+ return new InvalidRequestException(httpStatusCode, jsonResp);
+ }
+ return new InvalidRequestException(httpStatusCode, httpReasonPhrase);
+ }
+
+ const WebDriverException._(this.statusCode, this.message);
+
+ String toString() => '$runtimeType ($statusCode): $message';
+}
+
+class InvalidRequestException extends WebDriverException {
+ InvalidRequestException(statusCode, message) : super._(statusCode, message);
+}
+
+class UnknownException extends WebDriverException {
+ UnknownException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchDriverException extends WebDriverException {
+ NoSuchDriverException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchElementException extends WebDriverException {
+ NoSuchElementException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchFrameException extends WebDriverException {
+ NoSuchFrameException(statusCode, message) : super._(statusCode, message);
+}
+
+class UnknownCommandException extends WebDriverException {
+ UnknownCommandException(statusCode, message) : super._(statusCode, message);
+}
+
+class StaleElementReferenceException extends WebDriverException {
+ StaleElementReferenceException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class ElementNotVisibleException extends WebDriverException {
+ ElementNotVisibleException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidElementStateException extends WebDriverException {
+ InvalidElementStateException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class ElementIsNotSelectableException extends WebDriverException {
+ ElementIsNotSelectableException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class JavaScriptException extends WebDriverException {
+ JavaScriptException(statusCode, message) : super._(statusCode, message);
+}
+
+class XPathLookupException extends WebDriverException {
+ XPathLookupException(statusCode, message) : super._(statusCode, message);
+}
+
+class TimeoutException extends WebDriverException {
+ TimeoutException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchWindowException extends WebDriverException {
+ NoSuchWindowException(statusCode, message) : super._(statusCode, message);
+}
+
+class InvalidCookieDomainException extends WebDriverException {
+ InvalidCookieDomainException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnableToSetCookieException extends WebDriverException {
+ UnableToSetCookieException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnexpectedAlertOpenException extends WebDriverException {
+ UnexpectedAlertOpenException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class NoAlertOpenException extends WebDriverException {
+ NoAlertOpenException(statusCode, message) : super._(statusCode, message);
+}
+
+class InvalidElementCoordinatesException extends WebDriverException {
+ InvalidElementCoordinatesException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class IMENotAvailableException extends WebDriverException {
+ IMENotAvailableException(statusCode, message) : super._(statusCode, message);
+}
+
+class IMEEngineActivationFailedException extends WebDriverException {
+ IMEEngineActivationFailedException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidSelectorException extends WebDriverException {
+ InvalidSelectorException(statusCode, message) : super._(statusCode, message);
+}
+
+class SessionNotCreatedException extends WebDriverException {
+ SessionNotCreatedException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class MoveTargetOutOfBoundsException extends WebDriverException {
+ MoveTargetOutOfBoundsException(statusCode, message)
+ : super._(statusCode, message);
+}
diff --git a/lib/src/keyboard.dart b/lib/src/keyboard.dart
index 421c456..7835a9e 100644
--- a/lib/src/keyboard.dart
+++ b/lib/src/keyboard.dart
@@ -1,43 +1,16 @@
part of webdriver;
-class Keyboard extends _WebDriverBase implements Future {
+class Keyboard extends _WebDriverBase {
- Future _future;
-
- Keyboard._(prefix, commandProcessor, [this._future])
- : super(prefix, commandProcessor) {
- if (_future == null) {
- _future = new Future.value();
- }
- }
+ Keyboard._(driver)
+ : super(driver, '');
/**
- * Send a [keysToSend] to the active element.
+ * Send [keysToSend] to the active element.
*/
- Keyboard sendKeys(dynamic keysToSend) {
- if (keysToSend is String) {
- keysToSend = [ keysToSend ];
- }
- return _createNext((_) => _post(
+ Future sendKeys(String keysToSend) async {
+ await _post(
'keys',
- { 'value' : keysToSend as List<String>}));
+ { 'value' : [ keysToSend ]});
}
-
- Keyboard _createNext(f(value)) {
- return new Keyboard._(_prefix, _commandProcessor, _future.then(f));
- }
-
- Stream asStream() => _future.asStream();
-
- Future catchError(onError(error), {bool test(error)}) =>
- _future.catchError(onError, test: test);
-
- Future then(onValue(value), {onError(error)}) =>
- _future.then(onValue, onError: onError);
-
- Future whenComplete(action()) =>
- _future.whenComplete(action);
-
- Future timeout(Duration timeLimit, {onTimeout()}) =>
- _future.timeout(timeLimit, onTimeout: onTimeout);
}
diff --git a/lib/src/mouse.dart b/lib/src/mouse.dart
index 241858a..7108e49 100644
--- a/lib/src/mouse.dart
+++ b/lib/src/mouse.dart
@@ -1,58 +1,51 @@
part of webdriver;
-class Mouse extends _WebDriverBase implements Future {
+class Mouse extends _WebDriverBase {
static const int LEFT = 0;
static const int MIDDLE = 1;
static const int RIGHT = 2;
- Future _future;
-
- Mouse._(prefix, commandProcessor, [this._future])
- : super(prefix, commandProcessor) {
- if (_future == null) {
- _future = new Future.value();
- }
- }
+ Mouse._(driver)
+ : super(driver, '');
/// Click any mouse button (at the coordinates set by the last moveTo).
- Mouse click([int button]) {
+ Future click([int button]) async {
var json = {};
if (button is num) {
json['button'] = button.clamp(0, 2).floor();
}
- return _createNext((_) => _post('click', json));
+ await _post('click', json);
}
/**
* Click and hold any mouse button (at the coordinates set by the last
* moveTo command).
*/
- Mouse down([int button]) {
+ Future down([int button]) async {
var json = {};
if (button is num) {
json['button'] = button.clamp(0, 2).floor();
}
- return _createNext((_) =>
- _post('buttondown', json));
+ await _post('buttondown', json);
}
/**
* Releases the mouse button previously held (where the mouse is currently
* at).
*/
- Mouse up([int button]) {
+ Future up([int button]) async {
var json = {};
if (button is num) {
json['button'] = button.clamp(0, 2).floor();
}
- return _createNext((_) =>
- _post('buttonup', json));
+ await _post('buttonup', json);
}
/// Double-clicks at the current mouse coordinates (set by moveTo).
- Mouse doubleClick() =>
- _createNext((_) => _post('doubleclick'));
+ Future doubleClick() async {
+ await _post('doubleclick');
+ }
/**
* Move the mouse.
@@ -68,33 +61,15 @@
*
* All other combinations of parameters are illegal.
*/
- Mouse moveTo({WebElement element, int xOffset, int yOffset}) {
+ Future moveTo({WebElement element, int xOffset, int yOffset}) async {
var json = {};
if (element is WebElement) {
- json['element'] = element._elementId;
+ json['element'] = element.id;
}
if (xOffset is num && yOffset is num) {
json['xoffset'] = xOffset.floor();
json['yoffset'] = yOffset.floor();
}
- return _createNext((_) => _post('moveto', json));
+ await _post('moveto', json);
}
-
- Mouse _createNext(f(value)) {
- return new Mouse._(_prefix, _commandProcessor, _future.then(f));
- }
-
- Stream asStream() => _future.asStream();
-
- Future catchError(onError(error), {bool test(error)}) =>
- _future.catchError(onError, test: test);
-
- Future then(onValue(value), {onError(error)}) =>
- _future.then(onValue, onError: onError);
-
- Future whenComplete(action()) =>
- _future.whenComplete(action);
-
- Future timeout(Duration timeLimit, {onTimeout()}) =>
- _future.timeout(timeLimit, onTimeout: onTimeout);
}
diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart
index 020c9ed..407da8f 100644
--- a/lib/src/navigation.dart
+++ b/lib/src/navigation.dart
@@ -2,14 +2,20 @@
class Navigation extends _WebDriverBase {
- Navigation._(prefix, commandProcessor) : super(prefix, commandProcessor);
+ Navigation._(driver) : super(driver, '');
/// Navigate forwards in the browser history, if possible.
- Future<Navigation> forward() => _post('forward').then((_) => this);
+ Future forward() async {
+ await _post('forward');
+ }
/// Navigate backwards in the browser history, if possible.
- Future<Navigation> back() => _post('back').then((_) => this);
+ Future back() async {
+ await _post('back');
+ }
/// Refresh the current page.
- Future<Navigation> refresh() => _post('refresh').then((_) => this);
+ Future refresh() async {
+ await _post('refresh');
+ }
}
diff --git a/lib/src/options.dart b/lib/src/options.dart
index 1e79d2c..22da230 100644
--- a/lib/src/options.dart
+++ b/lib/src/options.dart
@@ -2,24 +2,31 @@
class Cookies extends _WebDriverBase {
- Cookies._(prefix, commandProcessor)
- : super('$prefix/cookie', commandProcessor);
+ Cookies._(driver)
+ : super(driver, 'cookie');
/// Set a cookie.
- Future<Cookies> add(Cookie cookie) => _post('', { 'cookie': cookie })
- .then((_) => this);
+ Future add(Cookie cookie) async {
+ await _post('', { 'cookie': cookie });
+ }
/// Delete the cookie with the given [name].
- Future<Cookies> delete(String name) => _delete('$name').then((_) => this);
+ Future delete(String name) async {
+ await _delete('$name');
+ }
/// Delete all cookies visible to the current page.
- Future<Cookies> deleteAll() => _delete('').then((_) => this);
+ Future deleteAll() async {
+ await _delete('');
+ }
/// Retrieve all cookies visible to the current page.
- Future<List<Cookie>> get all =>
- _get('')
- .then((cookies) =>
- cookies.map((cookie) => new Cookie.fromJson(cookie)).toList());
+ Stream<Cookie> get all async* {
+ var cookies = await _get('');
+ for (var cookie in cookies) {
+ yield new Cookie.fromJson(cookie);
+ }
+ }
}
class Cookie {
@@ -77,32 +84,22 @@
class Timeouts extends _WebDriverBase {
- Timeouts._(prefix, commandProcessor)
- : super('$prefix/timeouts', commandProcessor);
+ Timeouts._(driver)
+ : super(driver, 'timeouts');
- Future<Timeouts> _set(String type, Duration duration) =>
- _post('', { 'type' : type, 'ms': duration.inMilliseconds})
- .then((_) => this);
+ Future _set(String type, Duration duration) async {
+ await _post('', { 'type' : type, 'ms': duration.inMilliseconds});
+ }
/// Set the script timeout.
- Future<Timeouts> setScriptTimeout(Duration duration) =>
+ Future setScriptTimeout(Duration duration) =>
_set('script', duration);
/// Set the implicit timeout.
- Future<Timeouts> setImplicitTimeout(Duration duration) =>
+ Future setImplicitTimeout(Duration duration) =>
_set('implicit', duration);
/// Set the page load timeout.
- Future<Timeouts> setPageLoadTimeout(Duration duration) =>
+ Future setPageLoadTimeout(Duration duration) =>
_set('page load', duration);
-
- /// Set the async script timeout.
- Future<Timeouts> setAsyncScriptTimeout(Duration duration) =>
- _post('async_script', { 'ms': duration.inMilliseconds})
- .then((_) => this);
-
- /// Set the implicit wait timeout.
- Future<Timeouts> setImplicitWaitTimeout(Duration duration) =>
- _post('implicit_wait', { 'ms': duration.inMilliseconds})
- .then((_) => this);
}
diff --git a/lib/src/pageloader/annotations.dart b/lib/src/pageloader/annotations.dart
deleted file mode 100644
index 9821692..0000000
--- a/lib/src/pageloader/annotations.dart
+++ /dev/null
@@ -1,163 +0,0 @@
-part of pageloader;
-
-/**
- * Annotate a field as representing a [List] of [type]. If [type] is
- * not supplied, defaults to [WebElement].
- */
-// Hack because I can't figure out how to get the full type for fields.
-class ListOf {
- final Type type;
-
- const ListOf([this.type = WebElement]);
-}
-
-/// Finders identify an initial set of [WebElement]s to be used for a field.
-abstract class Finder extends _FilterFinder {
- const Finder();
-
- /// returns the [List<WebElement>] that should be considered for a field.
- Future<List<WebElement>> findElements(WebDriver driver, SearchContext context);
-}
-
-/// Filters reduce the set of [WebElement]s to be used for a field.
-abstract class Filter extends _FilterFinder {
-
- const Filter();
-
- /// Returns a subset of [elements] that should be kept for a field.
- Future<List<WebElement>> filter(List<WebElement> elements);
-}
-
-/**
- * Convenience class for [Filter]s that only need information about a specific
- * [WebElement] to determine whether to keep it or not.
- */
-abstract class ElementFilter extends Filter {
-
- const ElementFilter();
-
- @override
- Future<List<WebElement>> filter(List<WebElement> elements) =>
- Future.wait(elements.map(keep))
- .then((keeps) {
- var i = 0;
- var newElements = new List<WebElement>();
- for (var keep in keeps) {
- if (keep) {
- newElements.add(elements[i]);
- }
- i++;
- }
- return newElements;
- });
-
- /// Return [true] if you want to keep [element].
- Future<bool> keep(WebElement element);
-}
-
-abstract class _FilterFinder {
- const _FilterFinder();
-
- /**
- * Returns a set of [FilterFinderOption]s that control the behavior of this
- * [Filter] or [Finder].
- */
- List<FilterFinderOption> get options => const [];
-}
-
-/**
- * [Filter] that keeps [WebElement]s based on their visibility. Overrides the
- * default visibility filter used by [PageLoader].
- */
-class WithState extends ElementFilter {
-
- final _displayed;
-
- const WithState._(this._displayed);
-
- /// Keep all [WebElement]s regardless of whether they are visible or not.
- const WithState.present() : this._(null);
-
- /**
- * Keep only [WebElement]s that are visible. This is the default for
- * [PageLoader] so should generally not be necessary.
- */
- const WithState.visible() : this._(true);
-
- /// Keep only [WebElement]s that are invisible.
- const WithState.invisible() : this._(false);
-
- @override
- Future<bool> keep(WebElement element) {
- if (_displayed == null) {
- return new Future.value(true);
- } else {
- return element.displayed.then((displayed) => displayed == _displayed);
- }
- }
-
- @override
- List<FilterFinderOption> get options =>
- const [ FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING ];
-}
-
-/**
- * Matches the root [WebElement] being used for constructing the current page
- * object.
- */
-class Root extends Finder {
- const Root();
-
- @override
- Future<List<WebElement>> findElements(WebDriver driver, SearchContext context) {
- if (context is WebElement) {
- return new Future.value([ context ]);
- } else {
- return context.findElements(const By.xpath('/*'));
- }
- }
-
- @override
- List<FilterFinderOption> get options =>
- const [ FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING ];
-}
-
-/**
- * Keeps only [WebElement]s that have the given attribute with the given value.
- */
-class WithAttribute extends ElementFilter {
-
- final String name;
- final String value;
-
- /**
- * @param value String or null if checking for the absence of an attribute.
- */
- const WithAttribute(this.name, this.value);
-
- @override
- Future<bool> keep(WebElement element) => element.attributes[name]
- .then((attribute) => attribute == value);
-}
-
-class _ByFinder extends Finder {
- final By _by;
-
- const _ByFinder(this._by);
-
- @override
- Future<List<WebElement>> findElements(WebDriver driver, SearchContext context) {
- return context.findElements(_by);
- }
-}
-
-/// Enum of options for that can be returned by [_FilterFinder.options].
-class FilterFinderOption {
- final String option;
-
- const FilterFinderOption._(this.option);
-
- /// Disable the default implicit display filtering for a field.
- static const FilterFinderOption DISABLE_IMPLICIT_DISPLAY_FILTERING =
- const FilterFinderOption._('DISABLE_IMPLICIT_DISPLAY_FILTERING');
-}
diff --git a/lib/src/pageloader/core.dart b/lib/src/pageloader/core.dart
deleted file mode 100644
index 31f50ac..0000000
--- a/lib/src/pageloader/core.dart
+++ /dev/null
@@ -1,189 +0,0 @@
-part of pageloader;
-
-/**
- * Mechanism for specifying hierarchical page objects using annotations on
- * fields in simple Dart objects.
- */
-class PageLoader {
- final WebDriver _driver;
-
- PageLoader(this._driver);
-
- /**
- * Creates a new instance of [type] and binds annotated fields to
- * corresponding [WebElement]s.
- */
- Future getInstance(Type type) =>
- _getInstance(reflectClass(type), _driver);
-
- Future _getInstance(ClassMirror type, SearchContext context) {
- var fieldInfos = _fieldInfos(type);
- var instance = _reflectedInstance(type);
-
- var fieldFutures = fieldInfos.map((info) =>
- info.setField(instance, context, this));
-
- return Future.wait(fieldFutures).then((_) => instance.reflectee);
- }
-
- InstanceMirror _reflectedInstance(ClassMirror aClass) {
- InstanceMirror page;
-
- Iterable<MethodMirror> ctors = aClass.instanceMembers.values.where(
- (member) => member.isConstructor);
- for (MethodMirror constructor in ctors) {
- if (constructor.parameters.isEmpty) {
- page = aClass.newInstance(constructor.constructorName, []);
- break;
- }
- }
-
- if (page == null) {
- throw new StateError('$aClass has no acceptable constructors');
- }
- return page;
- }
-
- Iterable<_FieldInfo> _fieldInfos(ClassMirror type) {
- var infos = <_FieldInfo>[];
-
- while (type != null) {
- for (DeclarationMirror decl in type.declarations.values) {
- _FieldInfo info = new _FieldInfo(_driver, decl);
- if (info != null) {
- infos.add(info);
- }
- }
- type = type.superclass;
- }
-
- return infos;
- }
-}
-
-class _FieldInfo {
- final WebDriver _driver;
- final Symbol _fieldName;
- final Finder _finder;
- final List<Filter> _filters;
- final TypeMirror _instanceType;
- final bool _isList;
-
- factory _FieldInfo(WebDriver driver, DeclarationMirror field) {
- var finder;
- var filters = new List<Filter>();
- var type;
- var name;
-
- if (field is VariableMirror && !field.isFinal) {
- type = field.type;
- name = field.simpleName;
- } else if (field is MethodMirror && field.isSetter) {
- type = field.parameters.first.type;
- // HACK to get correct symbol name for operating with setField.
- name = field.simpleName.toString();
- name = new Symbol(name.substring(8, name.length - 3));
- } else {
- return null;
- }
-
- var isList = false;
-
- if (type.simpleName == const Symbol('List')) {
- isList = true;
- type = null;
- }
-
- var implicitDisplayFiltering = true;
-
- for (InstanceMirror metadatum in field.metadata) {
- if (!metadatum.hasReflectee) {
- continue;
- }
- var datum = metadatum.reflectee;
-
- if (datum is By) {
- if (finder != null) {
- throw new StateError('Cannot have multiple finders on field');
- }
- finder = new _ByFinder(datum);
- } else if (datum is Finder) {
- if (finder != null) {
- throw new StateError('Cannot have multiple finders on field');
- }
- finder = datum;
- } else if (datum is Filter) {
- filters.add(datum);
- } else if (datum is ListOf) {
- if (type != null && type.simpleName != const Symbol('dynamic')) {
- throw new StateError('Field type is not compatible with ListOf');
- }
- isList = true;
- type = reflectClass(datum.type);
- }
-
- if (datum is _FilterFinder &&
- datum.options.contains(
- FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING)) {
- implicitDisplayFiltering = false;
- }
- }
-
- if (type == null || type.simpleName == const Symbol('dynamic')) {
- type = reflectClass(WebElement);
- }
-
- if (implicitDisplayFiltering) {
- filters.insert(0, new WithState.visible());
- }
-
- if (finder != null) {
- return new _FieldInfo._(driver, name, finder, filters, type, isList);
- } else {
- return null;
- }
- }
-
- _FieldInfo._(
- this._driver,
- this._fieldName,
- this._finder,
- this._filters,
- this._instanceType,
- this._isList);
-
- Future setField(
- InstanceMirror instance,
- SearchContext context,
- PageLoader loader) {
- var future = _getElements(context);
-
- if (_instanceType.simpleName != const Symbol('WebElement')) {
- future = future.then((elements) =>
- Future.wait(elements.map((element) =>
- loader._getInstance(_instanceType, element))));
- }
-
- if (!_isList) {
- future = future.then((objects) => objects.first);
- }
-
- return future.then((value) => instance.setField(_fieldName, value));
- }
-
- Future<List<WebElement>> _getElements(SearchContext context) {
- var future = _finder.findElements(_driver, context);
- for (var filter in _filters) {
- future = future.then(filter.filter);
- }
- if (!_isList) {
- future = future.then((elements) {
- if (elements.length != 1) {
- throw new StateError('multiple or no elements found for field');
- }
- return elements.take(1);
- });
- }
- return future;
- }
-}
diff --git a/lib/src/target_locator.dart b/lib/src/target_locator.dart
index 05966e5..e00ddac 100644
--- a/lib/src/target_locator.dart
+++ b/lib/src/target_locator.dart
@@ -2,13 +2,13 @@
class TargetLocator extends _WebDriverBase {
- TargetLocator._(prefix, commandProcessor) : super(prefix, commandProcessor);
+ TargetLocator._(driver) : super(driver, '');
/**
* Change focus to another frame on the page.
*
* If [frame] is a:
- * int: select by its zero-based indexed
+ * int: select by its zero-based index
* String: select frame by the name of the frame window or the id of the
* frame or iframe tag.
* WebElement: select the frrame for a previously found frame or iframe
@@ -18,8 +18,9 @@
* Throws [WebDriverError] no such frame if the specified frame can't be
* found.
*/
- Future<TargetLocator> frame([frame]) =>
- _post('frame', { 'id': frame}).then((_) => this);
+ Future frame([frame]) async {
+ await _post('frame', { 'id': frame});
+ }
/**
* Switch the focus of future commands for this driver to the window with the
@@ -28,15 +29,24 @@
* Throws [WebDriverError] no such window if the specified window can't be
* found.
*/
- Future<TargetLocator> window(String window) =>
- _post('window', { 'name': window}).then((_) => this);
+ Future window(dynamic window) async {
+ if (window is Window) {
+ await _post('window', { 'name': window.handle});
+ } else if (window is String){
+ await _post('window', { 'name': window });
+ } else {
+ throw 'Unsupported type: ${window.runtimeType}';
+ }
+ }
/**
* Switches to the currently active modal dialog for this particular driver
* instance.
*
- * Throws WebDriverEror no alert present if their is not currently an alert.
+ * Throws WebDriverEror no alert present if there is not currently an alert.
*/
- Future<Alert> get alert => _get('alert_text')
- .then((text) => new Alert._(text, _prefix, _commandProcessor));
+ Future<Alert> get alert async {
+ var text = await _get('alert_text');
+ return new Alert._(text, driver);
+ }
}
diff --git a/lib/src/touch.dart b/lib/src/touch.dart
deleted file mode 100644
index 30760d6..0000000
--- a/lib/src/touch.dart
+++ /dev/null
@@ -1,101 +0,0 @@
-part of webdriver;
-
-class Touch extends _WebDriverBase implements Future {
-
- Future _future;
-
- Touch._(prefix, commandProcessor, [this._future])
- : super('$prefix/touch', commandProcessor) {
- if (_future == null) {
- _future = new Future.value();
- }
- }
-
- /**
- * Single tap on the touch enabled device.
- */
- Touch click(WebElement element) =>
- _createNext((_) => _post('click', element));
-
- /**
- * Finger down on the screen.
- */
- Touch down(Point point) =>
- _createNext((_) => _post('down', point));
-
- /**
- * Finger up on the screen.
- */
- Touch up(Point point) =>
- _createNext((_) => _post('up', point));
-
- /**
- * Finger move on the screen.
- */
- Touch move(Point point) =>
- _createNext((_) => _post('move', point));
-
- /**
- * Scroll on the touch screen using finger based motion events.
- *
- * If start is specified, will start scrolling from that location, otherwise
- * will start scrolling from an arbitrary location.
- */
- Touch scroll(int xOffset, int yOffset, [WebElement start]) {
- var json = { 'xoffset': xOffset.floor(), 'yoffset': yOffset.floor()};
- if (start is WebElement) {
- json['element'] = start._elementId;
- }
- return _createNext((_) => _post('scroll', json));
- }
-
- /**
- * Double tap on the touch screen using finger motion events.
- */
- Touch doubleClick(WebElement element) =>
- _createNext((_) => _post('doubleclick', element));
-
- /**
- * Long press on the touch screen using finger motion events.
- */
- Touch longClick(WebElement element) =>
- _createNext((_) => _post('longclick', element));
-
- /**
- * Flick on the touch screen using finger motion events.
- */
- Touch flickElement(WebElement start, int xOffset, int yOffset, int speed) =>
- _createNext((_) => _post('flick', {
- 'element': start._elementId,
- 'xoffset': xOffset.floor(),
- 'yoffset': yOffset.floor(),
- 'speed': speed.floor()
- }));
-
- /**
- * Flick on the touch screen using finger motion events.
- */
- Touch flick(int xSpeed, int ySpeed) =>
- _createNext((_) => _post('flick', {
- 'xspeed': xSpeed.floor(),
- 'yspeed': ySpeed.floor()
- }));
-
- Touch _createNext(f(value)) {
- return new Touch._(_prefix, _commandProcessor, _future.then(f));
- }
-
- Stream asStream() => _future.asStream();
-
- Future catchError(onError(error), {bool test(error)}) =>
- _future.catchError(onError, test: test);
-
- Future then(onValue(value), {onError(error)}) =>
- _future.then(onValue, onError: onError);
-
- Future whenComplete(action()) =>
- _future.whenComplete(action);
-
- Future timeout(Duration timeLimit, {onTimeout()}) =>
- _future.timeout(timeLimit, onTimeout: onTimeout);
-}
diff --git a/lib/src/web_driver.dart b/lib/src/web_driver.dart
index 4152c12..a9e0ae3 100644
--- a/lib/src/web_driver.dart
+++ b/lib/src/web_driver.dart
@@ -1,84 +1,36 @@
part of webdriver;
-class WebDriver extends _WebDriverBase implements SearchContext {
+class WebDriver implements SearchContext {
- WebDriver._(commandProcessor) : super('', commandProcessor) ;
+ final _CommandProcessor _commandProcessor;
+ final Uri _prefix;
+ final Map<String, dynamic> capabilities;
+
+ WebDriver._(this._commandProcessor, this._prefix, this.capabilities);
/// Creates a WebDriver instance connected to the specified WebDriver server.
static Future<WebDriver> createDriver({
- String url: 'http://localhost:4444/wd/hub',
- Map<String, dynamic> desiredCapabilities}) {
+ Uri uri,
+ Map<String, dynamic> desiredCapabilities}) async {
+
+ if (uri == null) {
+ uri = Uri.parse('http://localhost:4444/wd/hub');
+ }
+
+ var commandProcessor = new _CommandProcessor();
if (desiredCapabilities == null) {
desiredCapabilities = Capabilities.empty;
}
- var client = new HttpClient();
- var requestUri = Uri.parse(url + "/session");
- return client.openUrl('POST', requestUri).then((req) {
- req.followRedirects = false;
- req.headers.add(HttpHeaders.ACCEPT, "application/json");
- req.headers.contentType = _CONTENT_TYPE_JSON;
- var body = UTF8.encode(
- JSON.encode({'desiredCapabilities': desiredCapabilities}));
- req.contentLength = body.length;
- req.add(body);
- return req.close();
- }).then((rsp) {
- // Starting in Selenium 2.34.0, the post/redirect/get pattern is no
- // longer used when creating a new session.
- if (300 <= rsp.statusCode && rsp.statusCode <= 399) {
- return Uri.parse(rsp.headers.value(HttpHeaders.LOCATION));
- }
-
- return rsp.transform(new Utf8Decoder())
- .fold(new StringBuffer(), (buffer, data) => buffer..write(data))
- .then((StringBuffer buffer) {
- // Strip NULs that WebDriver seems to include in some responses.
- var results = buffer.toString()
- .replaceAll(new RegExp('\u{0}*\$'), '');
-
- var status = 0;
-
- // 4xx responses send plain text; others send JSON
- if (HttpStatus.BAD_REQUEST <= rsp.statusCode
- && rsp.statusCode < HttpStatus.INTERNAL_SERVER_ERROR) {
- status = 13; // UnknownError
- if (rsp.statusCode == HttpStatus.NOT_FOUND) {
- status = 9; // UnkownCommand
- }
- throw new WebDriverError(status, results);
- }
-
- var respObj = JSON.decode(results);
- status = respObj['status'];
- if (status != 0) {
- var value = respObj['value'];
- var message =
- value.containsKey('message') ? value['message'] : null;
- throw new WebDriverError(status, message);
- }
-
- return Uri.parse(url + '/session/' + respObj['sessionId']);
- });
- }).then((sessionUrl) {
- var host = sessionUrl.host;
- var port = sessionUrl.port;
- if (host.isEmpty) {
- host = requestUri.host;
- if (port == 0) {
- port = requestUri.port;
- }
- }
- CommandProcessor processor = new CommandProcessor(
- host, port, sessionUrl.path);
- return new WebDriver._(processor);
- });
+ var response = await commandProcessor.post(uri.resolve('session'), { 'desiredCapabilities' : desiredCapabilities });
+ return new WebDriver._(commandProcessor, uri.resolve(response['id']), new UnmodifiableMapView(response['capabilities']));
}
/// Navigate to the specified url.
- Future<WebDriver> get(String url) =>
- _post('url', {'url': url}).then((_) => this);
+ Future get(String url) async {
+ await _post('url', {'url': url});
+ }
/// The current url.
Future<String> get currentUrl => _get('url');
@@ -88,20 +40,27 @@
/// Search for multiple elements within the entire current page.
@override
- Future<List<WebElement>> findElements(By by) => _post('elements', by)
- .then((response) =>
- response.map((element) =>
- new WebElement._(element, _prefix, _commandProcessor))
- .toList());
+ Stream<WebElement> findElements(By by) async* {
+ var elements = await _post('elements', by);
+ int i = 0;
+
+ for (var element in elements) {
+ yield new WebElement._(this, element['ELEMENT'], this, by, i);
+ i++;
+ }
+ }
+
/**
* Search for an element within the entire current page.
*
* Throws [WebDriverError] no such element if a matching element is not found.
*/
- Future<WebElement> findElement(By by) => _post('element', by)
- .then((element) =>
- new WebElement._(element, _prefix, _commandProcessor));
+ Future<WebElement> findElement(By by) async {
+ var element = await _post('element', by);
+ return new WebElement._(this, element['ELEMENT'], this, by);
+ }
+
/// An artist's rendition of the current page's source.
Future<String> get pageSource => _get('source');
@@ -110,48 +69,47 @@
Future close() => _delete('window');
/// Quit the browser.
- Future quit() => _delete(_prefix);
+ Future quit() => _delete('');
/// Handles for all of the currently displayed tabs/windows.
- Future<List<String>> get windowHandles => _get('window_handles');
+ Stream<Window> get windows async* {
+ var handles = await _get('window_handles');
+
+ for (var handle in handles) {
+ yield new Window._(this, handle);
+ }
+ }
/// Handle for the active tab/window.
- Future<String> get windowHandle => _get('window_handle');
+ Future<Window> get window async {
+ var handle = await _get('window_handle');
+ return new Window._(this, handle);
+ }
/**
* The currently focused element, or the body element if no
* element has focus.
*/
- Future<WebElement> get activeElement => _get('element/active')
- .then((element) {
- if (element == null) {
- return null;
- } else {
- new WebElement._(element['ELEMENT'], _prefix, _commandProcessor);
- }
- });
+ Future<WebElement> get activeElement async {
+ var element = await _get('element/active');
+ if (element != null) {
+ return new WebElement._(this, element['ELEMENT'], this, 'activeElement');
+ }
+ return null;
+ }
TargetLocator get switchTo =>
- new TargetLocator._(_prefix, _commandProcessor);
+ new TargetLocator._(this);
- Navigation get navigate => new Navigation._(_prefix, _commandProcessor);
+ Navigation get navigate => new Navigation._(this);
- Cookies get cookies => new Cookies._(_prefix, _commandProcessor);
+ Cookies get cookies => new Cookies._(this);
- Timeouts get timeouts => new Timeouts._(_prefix, _commandProcessor);
+ Timeouts get timeouts => new Timeouts._(this);
- Keyboard get keyboard => new Keyboard._(_prefix, _commandProcessor);
+ Keyboard get keyboard => new Keyboard._(this);
- Mouse get mouse => new Mouse._(_prefix, _commandProcessor);
-
- Touch get touch => new Touch._(_prefix, _commandProcessor);
-
- Window get window =>
- new Window._( 'current', _prefix, _commandProcessor);
-
- Future<List<Window>> get windows => windowHandles
- .then((windows) => windows.map((window) =>
- new Window._(window, _prefix, _commandProcessor)).toList());
+ Mouse get mouse => new Mouse._(this);
/// Take a screenshot of the current page as PNG.
Future<List<int>> captureScreenshot() => _get('screenshot')
@@ -208,7 +166,7 @@
dynamic _recursiveElementify(result) {
if (result is Map) {
if (result.length == 1 && result.containsKey(_ELEMENT)) {
- return new WebElement._(result, _prefix, _commandProcessor);
+ return new WebElement._(this, result['ELEMENT'], this, 'javascript');
} else {
var newResult = {};
result.forEach((key, value) {
@@ -222,4 +180,10 @@
return result;
}
}
+
+ Future _post(String command, [params]) => _commandProcessor.post(_prefix.resolve(command), params);
+
+ Future _get(String command) => _commandProcessor.get(_prefix.resolve(command));
+
+ Future _delete(String command) => _commandProcessor.delete(_prefix.resolve(command));
}
diff --git a/lib/src/web_element.dart b/lib/src/web_element.dart
index 46f2d33..ed0127a 100644
--- a/lib/src/web_element.dart
+++ b/lib/src/web_element.dart
@@ -1,32 +1,36 @@
part of webdriver;
class WebElement extends _WebDriverBase implements SearchContext {
- String _elementId;
- String _originalPrefix;
+ final String id;
- WebElement._(element, prefix, commandProcessor)
- : super('$prefix/element/${element[_ELEMENT]}', commandProcessor) {
- this._elementId = element[_ELEMENT];
- this._originalPrefix = prefix;
- }
+ // These fields represent information about how the element was found.
+ final SearchContext context;
+ final dynamic /* String | By */ locator;
+ final int index;
+
+ WebElement._(driver, id, [this.context, this.locator, this.index])
+ : this.id = id,
+ super(driver, 'element/$id');
/// Click on this element.
- Future<WebElement> click() => _post('click').then((_) => this);
+ Future click() async {
+ await _post('click');
+ }
/// Submit this element if it is part of a form.
- Future<WebElement> submit() => _post('submit').then((_) => this);
+ Future submit() async {
+ await _post('submit');
+ }
- /// Send [keysToSend] (a [String] or [List<String>]) to this element.
- Future<WebElement> sendKeys(dynamic keysToSend) {
- if (keysToSend is String) {
- keysToSend = [ keysToSend ];
- }
- return _post('value', { 'value' : keysToSend as List<String>})
- .then((_) => this);
+ /// Send [keysToSend] to this element.
+ Future sendKeys(String keysToSend) async {
+ await _post('value', { 'value' : [ keysToSend ]});
}
/// Clear the content of a text element.
- Future<WebElement> clear() => _post('clear').then((_) => this);
+ Future clear() async {
+ await _post('clear');
+ }
/// Is this radio button/checkbox selected?
Future<bool> get selected => _get('selected');
@@ -38,12 +42,16 @@
Future<bool> get displayed => _get('displayed');
/// The location within the document of this element.
- Future<Point> get location => _get('location')
- .then((json) => new Point.fromJson(json));
+ Future<Point> get location async {
+ var point = await _get('location');
+ return new Point.fromJson(point);
+ }
/// The size of this element.
- Future<Size> get size => _get('size')
- .then((json) => new Size.fromJson(json));
+ Future<Size> get size async {
+ var size = await _get('size');
+ return new Size.fromJson(size);
+ }
/// The tag name for this element.
Future<String> get name => _get('name');
@@ -56,15 +64,20 @@
*
* Throws [WebDriverError] no such element if matching element is not found.
*/
- Future<WebElement> findElement(By by) => _post('element', by)
- .then((element) =>
- new WebElement._(element, _originalPrefix, _commandProcessor));
+ Future<WebElement> findElement(By by) async {
+ var element = await _post('element', by);
+ return new WebElement._(driver, element['ELEMENT'], this, by);
+ }
/// Find multiple elements nested within this element.
- Future<List<WebElement>> findElements(By by) => _post('elements', by)
- .then((response) => response.map((element) =>
- new WebElement._(element, _originalPrefix, _commandProcessor))
- .toList());
+ Stream<WebElement> findElements(By by) async* {
+ var elements = await _post('elements', by);
+ int i = 0;
+ for (var element in elements) {
+ yield new WebElement._(driver, element['ELEMENT'], this, by, i);
+ i++;
+ }
+ }
/**
* Access to the HTML attributes of this tag.
@@ -72,7 +85,7 @@
* TODO(DrMarcII): consider special handling of boolean attributes.
*/
Attributes get attributes =>
- new Attributes._('attribute', _prefix, _commandProcessor);
+ new Attributes._(driver, '$_prefix/attribute');
/**
* Access to the cssProperties of this element.
@@ -81,14 +94,13 @@
* properties.
*/
Attributes get cssProperties =>
- new Attributes._('css', _prefix, _commandProcessor);
+ new Attributes._(driver, '$_prefix/css');
/**
* Does this element represent the same element as another element?
* Not the same as ==
*/
- Future<bool> equals(WebElement other) => _get('equals/${other._elementId}');
+ Future<bool> equals(WebElement other) => _get('equals/${other.id}');
- Map<String, String> toJson() =>
- new Map<String, String>()..[_ELEMENT] = _elementId;
+ Map<String, String> toJson() => {'ELEMENT': id};
}
diff --git a/lib/src/window.dart b/lib/src/window.dart
index c8a55d8..d790a1e 100644
--- a/lib/src/window.dart
+++ b/lib/src/window.dart
@@ -2,24 +2,36 @@
class Window extends _WebDriverBase {
- Window._(windowHandle, prefix, commandProcessor)
- : super('$prefix/window/$windowHandle', commandProcessor);
+ final String handle;
+
+ Window._(driver, handle)
+ : this.handle = handle,
+ super(driver, 'window/$handle');
/// The size of this window.
- Future<Size> get size => _get('size')
- .then((json) => new Size.fromJson(json));
+ Future<Size> get size async {
+ var size = await _get('size');
+ return new Size.fromJson(size);
+ }
/// The location of this window.
- Future<Point> get location => _get('position')
- .then((json) => new Point.fromJson(json));
+ Future<Point> get location async {
+ var point = await _get('position');
+ return new Point.fromJson(point);
+ }
/// Maximize this window.
- Future<Window> maximize() => _post('maximize').then((_) => this);
+ Future maximize() async {
+ await _post('maximize');
+ }
/// Set this window size.
- Future<Window> setSize(Size size) => _post('size', size).then((_) => this);
+ Future setSize(Size size) async {
+ await _post('size', size);
+ }
/// Set this window location.
- Future<Window> setLocation(Point point) =>
- _post('position', point).then((_) => this);
+ Future setLocation(Point point) async {
+ await _post('position', point);
+ }
}
diff --git a/lib/webdriver.dart b/lib/webdriver.dart
index 8d984e2..aeb3aef 100644
--- a/lib/webdriver.dart
+++ b/lib/webdriver.dart
@@ -5,8 +5,9 @@
library webdriver;
import 'dart:async';
+import 'dart:collection';
import 'dart:convert';
-import 'dart:io' hide JSON;
+import 'dart:io';
import 'package:crypto/crypto.dart';
@@ -14,13 +15,13 @@
part 'src/capabilities.dart';
part 'src/command_processor.dart';
part 'src/common.dart';
+part 'src/exception.dart';
part 'src/keyboard.dart';
part 'src/keys.dart';
part 'src/mouse.dart';
part 'src/navigation.dart';
part 'src/options.dart';
part 'src/target_locator.dart';
-part 'src/touch.dart';
part 'src/web_driver.dart';
part 'src/web_element.dart';
part 'src/window.dart';
diff --git a/test/pageloader_test.dart b/test/pageloader_test.dart
deleted file mode 100644
index 96bc8cc..0000000
--- a/test/pageloader_test.dart
+++ /dev/null
@@ -1,165 +0,0 @@
-library webdriver_test;
-
-import 'package:webdriver/pageloader.dart';
-import 'package:webdriver/webdriver.dart';
-import 'package:unittest/unittest.dart';
-import 'package:unittest/compact_vm_config.dart';
-
-import 'test_util.dart';
-
-/**
- * These tests are not expected to be run as part of normal automated testing,
- * as they are slow and they have external dependencies.
- */
-void main() {
- useCompactVMConfiguration();
-
- WebDriver driver;
- PageLoader loader;
-
- setUp(() => WebDriver.createDriver(desiredCapabilities: Capabilities.chrome)
- .then((_driver) {
- driver = _driver;
- loader = new PageLoader(driver);
- return driver.get(testPagePath);
- }));
-
- tearDown(() => driver.quit());
-
- test('simple', () {
- return loader.getInstance(PageForSimpleTest)
- .then((PageForSimpleTest page) {
- expect(page.table.rows, hasLength(2));
- expect(page.table.rows[0].cells, hasLength(2));
- expect(page.table.rows[1].cells, hasLength(2));
- expect(page.table.rows[0].cells[0].text, completion('r1c1'));
- expect(page.table.rows[0].cells[1].text, completion('r1c2'));
- expect(page.table.rows[1].cells[0].text, completion('r2c1'));
- expect(page.table.rows[1].cells[1].text, completion('r2c2'));
- });
- });
-
- test('displayed filtering', () {
- return loader.getInstance(PageForDisplayedFilteringTest)
- .then((PageForDisplayedFilteringTest page) {
- expect(page.shouldHaveOneElement, hasLength(1));
- expect(page.shouldBeEmpty, isEmpty);
- expect(page.shouldAlsoBeEmpty, isEmpty);
- });
- });
-
- test('setters', () {
- return loader.getInstance(PageForSettersTest)
- .then((PageForSettersTest page) {
- expect(page._shouldHaveOneElement, hasLength(1));
- });
- });
-
- test('skip finals', () {
- return loader.getInstance(PageForSkipFinalTest)
- .then((PageForSkipFinalTest page) {
- expect(page.shouldHaveOneElement, hasLength(1));
- expect(page.shouldBeNull, isNull);
- });
- });
-
- test('skip fields without finders', () {
- return loader.getInstance(PageForSkipFieldsWithoutFinderTest)
- .then((PageForSkipFieldsWithoutFinderTest page) {
- expect(page.shouldHaveOneElement, hasLength(1));
- expect(page.shouldBeNull, isNull);
- });
- });
-
- test('no matching element', () {
- expect(loader.getInstance(PageForNoMatchingElementTest), throws);
- });
-
- test('multiple matching element', () {
- expect(loader.getInstance(PageForMultipleMatchingElementTest), throws);
- });
-
- test('multiple finders', () {
- expect(() => loader.getInstance(PageForMultipleFinderTest), throws);
- });
-
- test('invalid constructor', () {
- expect(() => loader.getInstance(PageForInvalidConstructorTest), throws);
- });
-}
-
-class PageForSimpleTest {
- @By.tagName('table')
- Table table;
-}
-
-class Table {
- @By.tagName('tr')
- @ListOf(Row)
- List<Row> rows;
-}
-
-class Row {
- @By.tagName('td')
- List<WebElement> cells;
-}
-
-class PageForDisplayedFilteringTest {
- @By.id('div') @WithState.present()
- List<WebElement> shouldHaveOneElement;
-
- @By.id('div')
- List<WebElement> shouldBeEmpty;
-
- @By.id('div') @WithState.visible()
- @ListOf()
- dynamic shouldAlsoBeEmpty;
-}
-
-class PageForSettersTest {
- List<WebElement> _shouldHaveOneElement;
-
- @By.id('div') @WithState.present()
- set shouldHaveOneElement(List<WebElement> elements) {
- _shouldHaveOneElement = elements;
- }
-}
-
-class PageForSkipFinalTest {
- @By.id('div') @WithState.present()
- List<WebElement> shouldHaveOneElement;
-
- @By.id('div') @WithState.present()
- final List<WebElement> shouldBeNull = null;
-}
-
-class PageForSkipFieldsWithoutFinderTest {
- @By.id('div') @WithState.present()
- List<WebElement> shouldHaveOneElement;
-
- @WithState.present()
- List<WebElement> shouldBeNull;
-}
-
-class PageForNoMatchingElementTest {
- @By.id('non-existent id')
- WebElement doesntExist;
-}
-
-class PageForMultipleMatchingElementTest {
- @By.tagName('td')
- WebElement doesntExist;
-}
-
-class PageForMultipleFinderTest {
- @By.id('non-existent id') @By.name('a-name')
- WebElement multipleFinder;
-}
-
-class PageForInvalidConstructorTest {
-
- PageForInvalidConstructorTest(String someArg);
-
- @By.id('div') @WithState.present()
- List<WebElement> shouldHaveOneElement;
-}