Adds support W3C spec exception handling. (#171)
* Refactor exception to allow JSON and W3C spec versions. Adds JSON version.
* Adds exception type of W3C spec.
* Extends w3c web element test to account for exception handling.
* Dartfmt
* Address pull request comments.
diff --git a/lib/src/sync/exception.dart b/lib/src/sync/exception.dart
index 14b3688..a881820 100644
--- a/lib/src/sync/exception.dart
+++ b/lib/src/sync/exception.dart
@@ -14,208 +14,10 @@
library webdriver.exception;
+/// Exceptions returned from WebDriver implementation.
+///
+/// Exception handling differs heavily based on protocol.
abstract class WebDriverException implements Exception {
- /// 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) {
- final status = jsonResp['status'];
- final 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: // NoSuchAlert
- return new NoSuchAlertException(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);
-
- @override
- String toString() => '$runtimeType ($statusCode): $message';
-
- @override
- bool operator ==(other) =>
- other != null &&
- other.runtimeType == this.runtimeType &&
- other.statusCode == this.statusCode &&
- other.message == this.message;
-
- @override
- int get hashCode => statusCode + message.hashCode;
-}
-
-class InvalidRequestException extends WebDriverException {
- const InvalidRequestException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class UnknownException extends WebDriverException {
- const UnknownException(statusCode, message) : super._(statusCode, message);
-}
-
-class NoSuchDriverException extends WebDriverException {
- const NoSuchDriverException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class NoSuchElementException extends WebDriverException {
- const NoSuchElementException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class NoSuchFrameException extends WebDriverException {
- const NoSuchFrameException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class UnknownCommandException extends WebDriverException {
- const UnknownCommandException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class StaleElementReferenceException extends WebDriverException {
- const StaleElementReferenceException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class ElementNotVisibleException extends WebDriverException {
- const ElementNotVisibleException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class InvalidElementStateException extends WebDriverException {
- const InvalidElementStateException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class ElementIsNotSelectableException extends WebDriverException {
- const ElementIsNotSelectableException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class JavaScriptException extends WebDriverException {
- const JavaScriptException(statusCode, message) : super._(statusCode, message);
-}
-
-class XPathLookupException extends WebDriverException {
- const XPathLookupException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class TimeoutException extends WebDriverException {
- const TimeoutException(statusCode, message) : super._(statusCode, message);
-}
-
-class NoSuchWindowException extends WebDriverException {
- const NoSuchWindowException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class InvalidCookieDomainException extends WebDriverException {
- const InvalidCookieDomainException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class UnableToSetCookieException extends WebDriverException {
- const UnableToSetCookieException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class UnexpectedAlertOpenException extends WebDriverException {
- const UnexpectedAlertOpenException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class NoSuchAlertException extends WebDriverException {
- const NoSuchAlertException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class InvalidElementCoordinatesException extends WebDriverException {
- const InvalidElementCoordinatesException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class IMENotAvailableException extends WebDriverException {
- const IMENotAvailableException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class IMEEngineActivationFailedException extends WebDriverException {
- const IMEEngineActivationFailedException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class InvalidSelectorException extends WebDriverException {
- const InvalidSelectorException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class SessionNotCreatedException extends WebDriverException {
- const SessionNotCreatedException(statusCode, message)
- : super._(statusCode, message);
-}
-
-class MoveTargetOutOfBoundsException extends WebDriverException {
- const MoveTargetOutOfBoundsException(statusCode, message)
- : super._(statusCode, message);
+ String get message;
}
diff --git a/lib/src/sync/json_wire_spec/exception.dart b/lib/src/sync/json_wire_spec/exception.dart
new file mode 100644
index 0000000..0c0b035
--- /dev/null
+++ b/lib/src/sync/json_wire_spec/exception.dart
@@ -0,0 +1,226 @@
+// Copyright 2017 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.
+
+library webdriver.exception;
+
+import '../exception.dart';
+
+abstract class JsonWireWebDriverException implements WebDriverException {
+ /// The status value returned in the JSON response (preferred) or the
+ /// HTTP status code.
+ final int statusCode;
+
+ final String _message;
+
+ /// A message describing the error.
+ @override
+ String get message => _message;
+
+ factory JsonWireWebDriverException(
+ {int httpStatusCode, String httpReasonPhrase, dynamic jsonResp}) {
+ if (jsonResp is Map) {
+ final status = jsonResp['status'];
+ final 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: // NoSuchAlert
+ return new NoSuchAlertException(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 JsonWireWebDriverException._(this.statusCode, this._message);
+
+ @override
+ String toString() => '$runtimeType ($statusCode): $message';
+
+ @override
+ bool operator ==(other) =>
+ other != null &&
+ other.runtimeType == this.runtimeType &&
+ other.statusCode == this.statusCode &&
+ other.message == this.message;
+
+ @override
+ int get hashCode => statusCode + message.hashCode;
+}
+
+class InvalidRequestException extends JsonWireWebDriverException {
+ const InvalidRequestException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnknownException extends JsonWireWebDriverException {
+ const UnknownException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchDriverException extends JsonWireWebDriverException {
+ const NoSuchDriverException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class NoSuchElementException extends JsonWireWebDriverException {
+ const NoSuchElementException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class NoSuchFrameException extends JsonWireWebDriverException {
+ const NoSuchFrameException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnknownCommandException extends JsonWireWebDriverException {
+ const UnknownCommandException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class StaleElementReferenceException extends JsonWireWebDriverException {
+ const StaleElementReferenceException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class ElementNotVisibleException extends JsonWireWebDriverException {
+ const ElementNotVisibleException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidElementStateException extends JsonWireWebDriverException {
+ const InvalidElementStateException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class ElementIsNotSelectableException extends JsonWireWebDriverException {
+ const ElementIsNotSelectableException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class JavaScriptException extends JsonWireWebDriverException {
+ const JavaScriptException(statusCode, message) : super._(statusCode, message);
+}
+
+class XPathLookupException extends JsonWireWebDriverException {
+ const XPathLookupException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class TimeoutException extends JsonWireWebDriverException {
+ const TimeoutException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchWindowException extends JsonWireWebDriverException {
+ const NoSuchWindowException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidCookieDomainException extends JsonWireWebDriverException {
+ const InvalidCookieDomainException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnableToSetCookieException extends JsonWireWebDriverException {
+ const UnableToSetCookieException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class UnexpectedAlertOpenException extends JsonWireWebDriverException {
+ const UnexpectedAlertOpenException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class NoSuchAlertException extends JsonWireWebDriverException {
+ const NoSuchAlertException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidElementCoordinatesException extends JsonWireWebDriverException {
+ const InvalidElementCoordinatesException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class IMENotAvailableException extends JsonWireWebDriverException {
+ const IMENotAvailableException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class IMEEngineActivationFailedException extends JsonWireWebDriverException {
+ const IMEEngineActivationFailedException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class InvalidSelectorException extends JsonWireWebDriverException {
+ const InvalidSelectorException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class SessionNotCreatedException extends JsonWireWebDriverException {
+ const SessionNotCreatedException(statusCode, message)
+ : super._(statusCode, message);
+}
+
+class MoveTargetOutOfBoundsException extends JsonWireWebDriverException {
+ const MoveTargetOutOfBoundsException(statusCode, message)
+ : super._(statusCode, message);
+}
diff --git a/lib/src/sync/json_wire_spec/response_processor.dart b/lib/src/sync/json_wire_spec/response_processor.dart
index 212df91..8a2d9ec 100644
--- a/lib/src/sync/json_wire_spec/response_processor.dart
+++ b/lib/src/sync/json_wire_spec/response_processor.dart
@@ -16,7 +16,7 @@
import 'package:sync_http/sync_http.dart';
-import '../exception.dart' show WebDriverException;
+import 'exception.dart' show JsonWireWebDriverException;
/// Handles responses from the JSON wire protocol.
dynamic processJsonWireResponse(SyncHttpClientResponse response, bool value) {
@@ -30,7 +30,7 @@
(responseBody is Map &&
responseBody['status'] != null &&
responseBody['status'] != 0)) {
- throw new WebDriverException(
+ throw new JsonWireWebDriverException(
httpStatusCode: response.statusCode,
httpReasonPhrase: response.reasonPhrase,
jsonResp: responseBody);
diff --git a/lib/src/sync/w3c_spec/exception.dart b/lib/src/sync/w3c_spec/exception.dart
new file mode 100644
index 0000000..8fee62c
--- /dev/null
+++ b/lib/src/sync/w3c_spec/exception.dart
@@ -0,0 +1,70 @@
+// Copyright 2017 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.
+
+library webdriver.exception;
+
+import '../exception.dart';
+
+class W3cWebDriverException implements WebDriverException {
+ /// The HTTP status code.
+ final int httpStatusCode;
+
+ final String _message;
+
+ /// Error type.
+ final String error;
+
+ /// Stacktrace
+ final String stackTrace;
+
+ /// A message describing the error.
+ @override
+ String get message => _message;
+
+ factory W3cWebDriverException({int httpStatusCode, dynamic jsonResp}) {
+ if (jsonResp is Map && jsonResp.keys.contains('value')) {
+ final value = jsonResp['value'];
+ final error = value['error'];
+ final message = value['message'];
+ final stacktrace = value['stacktrace'];
+
+ return new W3cWebDriverException._(
+ httpStatusCode, error, message, stacktrace);
+ }
+ return new InvalidResponseW3cWebDriverException(httpStatusCode);
+ }
+
+ const W3cWebDriverException._(
+ this.httpStatusCode, this.error, this._message, this.stackTrace);
+
+ @override
+ String toString() => '$runtimeType ($httpStatusCode): $message';
+
+ @override
+ bool operator ==(other) =>
+ other != null &&
+ other.runtimeType == this.runtimeType &&
+ other.statusCode == this.httpStatusCode &&
+ other.message == this.message &&
+ other.error == this.error;
+
+ @override
+ int get hashCode => httpStatusCode + message.hashCode;
+}
+
+/// Thrown in case of invalid responses.
+class InvalidResponseW3cWebDriverException extends W3cWebDriverException {
+ const InvalidResponseW3cWebDriverException(int httpStatusCode)
+ : super._(httpStatusCode, null, null, null);
+}
diff --git a/lib/src/sync/w3c_spec/response_processor.dart b/lib/src/sync/w3c_spec/response_processor.dart
index d8e3d8d..a4620f8 100644
--- a/lib/src/sync/w3c_spec/response_processor.dart
+++ b/lib/src/sync/w3c_spec/response_processor.dart
@@ -16,7 +16,7 @@
import 'package:sync_http/sync_http.dart';
-import '../exception.dart' show WebDriverException;
+import 'exception.dart' show W3cWebDriverException;
/// Handles responses from the W3C protocol.
dynamic processW3cResponse(SyncHttpClientResponse response, bool value) {
@@ -26,10 +26,8 @@
} catch (e) {}
if (response.statusCode < 200 || response.statusCode > 299) {
- throw new WebDriverException(
- httpStatusCode: response.statusCode,
- httpReasonPhrase: response.reasonPhrase,
- jsonResp: responseBody);
+ throw new W3cWebDriverException(
+ httpStatusCode: response.statusCode, jsonResp: responseBody);
}
if (value && responseBody is Map) {
return responseBody['value'];
diff --git a/test/sync/w3c_web_element.dart b/test/sync/w3c_web_element.dart
index 5157063..a89dfc0 100644
--- a/test/sync/w3c_web_element.dart
+++ b/test/sync/w3c_web_element.dart
@@ -19,6 +19,7 @@
import 'package:test/test.dart';
import 'package:webdriver/sync_core.dart';
+import 'package:webdriver/src/sync/w3c_spec/exception.dart';
import 'sync_io_config.dart' as config;
@@ -133,7 +134,13 @@
try {
button.findElement(const By.tagName('tr'));
throw 'Expected Exception';
- } on Exception {}
+ } catch (e) {
+ expect(e, new isInstanceOf<W3cWebDriverException>());
+ expect((e as W3cWebDriverException).httpStatusCode, 404);
+ expect((e as W3cWebDriverException).error, 'no such element');
+ expect((e as W3cWebDriverException).message,
+ contains('Unable to locate element'));
+ }
});
test('findElements -- 1 found', () {
diff --git a/test/sync/web_element.dart b/test/sync/web_element.dart
index 5b7899f..b3b729d 100644
--- a/test/sync/web_element.dart
+++ b/test/sync/web_element.dart
@@ -16,6 +16,7 @@
library webdriver.web_element_test;
import 'package:test/test.dart';
+import 'package:webdriver/src/sync/json_wire_spec/exception.dart';
import 'package:webdriver/sync_core.dart';
import 'sync_io_config.dart' as config;
@@ -146,7 +147,9 @@
try {
button.findElement(const By.tagName('tr'));
throw 'Expected NoSuchElementException';
- } on NoSuchElementException {}
+ } catch (e) {
+ expect(e, new isInstanceOf<NoSuchElementException>());
+ }
});
test('findElements -- 1 found', () {