From 99a1fa97684d99fbf0e97fa81364f5165cbc8139 Mon Sep 17 00:00:00 2001 From: Andreas Tolfsen Date: Thu, 2 Apr 2015 20:07:20 +0100 Subject: [PATCH] Bug 1150522: Add WebDriver string statuses to Marionette client Adds string based statuses as defined by the W3C WebDriver protocol to the Marionette Python client. Importantly, it does not remove the ability to look up errors by their Selenium protocol number for backwards compatibility reasons. r=dburns --HG-- extra : rebase_source : 792e85d01ed6513370f448762c1c5bf8f13842a4 --- .../marionette/client/marionette/__init__.py | 2 +- .../client/marionette/marionette_test.py | 2 +- .../marionette/tests/unit/test_errors.py | 35 +++- .../marionette/tests/unit/test_marionette.py | 11 +- .../driver/marionette_driver/errors.py | 193 ++++++++++++------ .../driver/marionette_driver/marionette.py | 56 +---- testing/marionette/error.js | 1 + 7 files changed, 175 insertions(+), 125 deletions(-) diff --git a/testing/marionette/client/marionette/__init__.py b/testing/marionette/client/marionette/__init__.py index 78b88f62dcce..1894eb274cfe 100644 --- a/testing/marionette/client/marionette/__init__.py +++ b/testing/marionette/client/marionette/__init__.py @@ -23,4 +23,4 @@ from runner import ( TestManifest, TestResult, TestResultCollection -) +) \ No newline at end of file diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/client/marionette/marionette_test.py index a56375dc19b3..a1c56370d4df 100644 --- a/testing/marionette/client/marionette/marionette_test.py +++ b/testing/marionette/client/marionette/marionette_test.py @@ -16,7 +16,7 @@ import warnings from marionette_driver.errors import ( - ErrorCodes, MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException, + MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException, JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException, StaleElementException, ScriptTimeoutException, ElementNotVisibleException, NoSuchFrameException, InvalidElementStateException, NoAlertPresentException, diff --git a/testing/marionette/client/marionette/tests/unit/test_errors.py b/testing/marionette/client/marionette/tests/unit/test_errors.py index f004933f74ee..fd3700db5334 100644 --- a/testing/marionette/client/marionette/tests/unit/test_errors.py +++ b/testing/marionette/client/marionette/tests/unit/test_errors.py @@ -6,7 +6,6 @@ import sys from marionette import marionette_test from marionette_driver import errors -from marionette_driver.errors import ErrorCodes def fake_cause(): try: @@ -15,29 +14,26 @@ def fake_cause(): return sys.exc_info() message = "foo" -status = ErrorCodes.TIMEOUT cause = fake_cause() stacktrace = "first\nsecond" -class TestMarionetteException(marionette_test.MarionetteTestCase): +class TestExceptionType(marionette_test.MarionetteTestCase): def test_defaults(self): exc = errors.MarionetteException() self.assertIsNone(exc.msg) - self.assertEquals(exc.status, ErrorCodes.MARIONETTE_ERROR) self.assertIsNone(exc.cause) self.assertIsNone(exc.stacktrace) def test_construction(self): exc = errors.MarionetteException( - message=message, status=status, cause=cause, stacktrace=stacktrace) + message=message, cause=cause, stacktrace=stacktrace) self.assertEquals(exc.msg, message) - self.assertEquals(exc.status, status) self.assertEquals(exc.cause, cause) self.assertEquals(exc.stacktrace, stacktrace) def test_str(self): exc = errors.MarionetteException( - message=message, status=status, cause=cause, stacktrace=stacktrace) + message=message, cause=cause, stacktrace=stacktrace) r = str(exc) self.assertIn(message, r) self.assertIn(", caused by %r" % cause[0], r) @@ -55,3 +51,28 @@ class TestMarionetteException(marionette_test.MarionetteTestCase): self.assertEqual(exc.cause, cause) r = str(exc) self.assertIn(", caused by %r" % cause[0], r) + + +class TestLookup(marionette_test.MarionetteTestCase): + def test_by_known_number(self): + self.assertEqual(errors.NoSuchElementException, errors.lookup(7)) + + def test_by_unknown_number(self): + self.assertEqual(errors.MarionetteException, errors.lookup(123456)) + + def test_by_known_string(self): + self.assertEqual(errors.NoSuchElementException, + errors.lookup("no such element")) + + def test_by_unknown_string(self): + self.assertEqual(errors.MarionetteException, errors.lookup("barbera")) + + +class TestAllExceptions(marionette_test.MarionetteTestCase): + def test_properties(self): + for exc in errors.excs: + self.assertTrue(hasattr(exc, "code"), + "expected exception to have attribute `code'") + self.assertTrue(hasattr(exc, "status"), + "expected exception to have attribute `status'") + self.assertIsInstance(exc.code, tuple) diff --git a/testing/marionette/client/marionette/tests/unit/test_marionette.py b/testing/marionette/client/marionette/tests/unit/test_marionette.py index 477fa9693414..26d6ce71871b 100644 --- a/testing/marionette/client/marionette/tests/unit/test_marionette.py +++ b/testing/marionette/client/marionette/tests/unit/test_marionette.py @@ -15,8 +15,17 @@ class TestHandleError(marionette_test.MarionetteTestCase): def test_known_error_code(self): with self.assertRaises(errors.NoSuchElementException): self.marionette._handle_error( - {"error": {"status": errors.ErrorCodes.NO_SUCH_ELEMENT}}) + {"error": {"status": errors.NoSuchElementException.code[0]}}) + + def test_known_error_status(self): + with self.assertRaises(errors.NoSuchElementException): + self.marionette._handle_error( + {"error": {"status": errors.NoSuchElementException.status}}) def test_unknown_error_code(self): with self.assertRaises(errors.MarionetteException): self.marionette._handle_error({"error": {"status": 123456}}) + + def test_unknown_error_status(self): + with self.assertRaises(errors.MarionetteException): + self.marionette._handle_error({"error": {"status": "barbera"}}) diff --git a/testing/marionette/driver/marionette_driver/errors.py b/testing/marionette/driver/marionette_driver/errors.py index b8b1b1d04734..89e3ab469ca2 100644 --- a/testing/marionette/driver/marionette_driver/errors.py +++ b/testing/marionette/driver/marionette_driver/errors.py @@ -4,52 +4,19 @@ import traceback -class ErrorCodes(object): - SUCCESS = 0 - NO_SUCH_ELEMENT = 7 - NO_SUCH_FRAME = 8 - UNKNOWN_COMMAND = 9 - STALE_ELEMENT_REFERENCE = 10 - ELEMENT_NOT_VISIBLE = 11 - INVALID_ELEMENT_STATE = 12 - UNKNOWN_ERROR = 13 - ELEMENT_NOT_ACCESSIBLE = 56 - ELEMENT_IS_NOT_SELECTABLE = 15 - JAVASCRIPT_ERROR = 17 - XPATH_LOOKUP_ERROR = 19 - TIMEOUT = 21 - NO_SUCH_WINDOW = 23 - INVALID_COOKIE_DOMAIN = 24 - UNABLE_TO_SET_COOKIE = 25 - UNEXPECTED_ALERT_OPEN = 26 - NO_ALERT_OPEN = 27 - SCRIPT_TIMEOUT = 28 - INVALID_ELEMENT_COORDINATES = 29 - INVALID_SELECTOR = 32 - MOVE_TARGET_OUT_OF_BOUNDS = 34 - INVALID_XPATH_SELECTOR = 51 - INVALID_XPATH_SELECTOR_RETURN_TYPER = 52 - INVALID_RESPONSE = 53 - FRAME_SEND_NOT_INITIALIZED_ERROR = 54 - FRAME_SEND_FAILURE_ERROR = 55 - SESSION_NOT_CREATED = 71 - UNSUPPORTED_OPERATION = 405 - MARIONETTE_ERROR = 500 class MarionetteException(Exception): + """Raised when a generic non-recoverable exception has occured.""" - def __init__(self, message=None, - status=ErrorCodes.MARIONETTE_ERROR, cause=None, - stacktrace=None): + code = (500,) + status = "webdriver error" + + def __init__(self, message=None, cause=None, stacktrace=None): """Construct new MarionetteException instance. :param message: An optional exception message. - :param status: A WebDriver status code given as an integer. - By default the generic Marionette error code 500 will be - used. - :param cause: An optional tuple of three values giving information about the root exception cause. Expected tuple values are (type, value, traceback). @@ -61,7 +28,6 @@ class MarionetteException(Exception): """ self.msg = message - self.status = status self.cause = cause self.stacktrace = stacktrace @@ -81,72 +47,175 @@ class MarionetteException(Exception): return "".join(traceback.format_exception(self.__class__, msg, tb)) + class InstallGeckoError(MarionetteException): pass + class TimeoutException(MarionetteException): - pass + code = (21,) + status = "timeout" + class InvalidResponseException(MarionetteException): - pass + code = (53,) + status = "invalid response" + class JavascriptException(MarionetteException): - pass + code = (17,) + status = "javascript error" + class NoSuchElementException(MarionetteException): - pass + code = (7,) + status = "no such element" + class XPathLookupException(MarionetteException): - pass + code = (19,) + status = "invalid xpath selector" + class NoSuchWindowException(MarionetteException): - pass + code = (23,) + status = "no such window" + class StaleElementException(MarionetteException): - pass + code = (10,) + status = "stale element reference" + class ScriptTimeoutException(MarionetteException): - pass + code = (28,) + status = "script timeout" + class ElementNotVisibleException(MarionetteException): - def __init__(self, message="Element is not currently visible and may not be manipulated", - status=ErrorCodes.ELEMENT_NOT_VISIBLE, + code = (11,) + status = "element not visible" + + def __init__( + self, message="Element is not currently visible and may not be manipulated", stacktrace=None, cause=None): super(ElementNotVisibleException, self).__init__( - message, status=status, cause=cause, stacktrace=stacktrace) + message, cause=cause, stacktrace=stacktrace) + class ElementNotAccessibleException(MarionetteException): - pass + code = (56,) + status = "element not accessible" + class NoSuchFrameException(MarionetteException): - pass + code = (8,) + status = "no such frame" + class InvalidElementStateException(MarionetteException): - pass + code = (12,) + status = "invalid element state" + class NoAlertPresentException(MarionetteException): - pass + code = (27,) + status = "no such alert" + class InvalidCookieDomainException(MarionetteException): - pass + code = (24,) + status = "invalid cookie domain" + class UnableToSetCookieException(MarionetteException): - pass + code = (25,) + status = "unable to set cookie" + + +class InvalidElementCoordinates(MarionetteException): + code = (29,) + status = "invalid element coordinates" + class InvalidSelectorException(MarionetteException): - pass + code = (32, 51, 52) + status = "invalid selector" + class MoveTargetOutOfBoundsException(MarionetteException): - pass + code = (34,) + status = "move target out of bounds" + class FrameSendNotInitializedError(MarionetteException): - pass + code = (54,) + status = "frame send not initialized" + class FrameSendFailureError(MarionetteException): - pass + code = (55,) + status = "frame send failure" + class UnsupportedOperationException(MarionetteException): - pass + code = (405,) + status = "unsupported operation" + class SessionNotCreatedException(MarionetteException): - pass + code = (33, 71) + status = "session not created" + + +class UnexpectedAlertOpen(MarionetteException): + code = (26,) + status = "unexpected alert open" + +excs = [ + MarionetteException, + TimeoutException, + InvalidResponseException, + JavascriptException, + NoSuchElementException, + XPathLookupException, + NoSuchWindowException, + StaleElementException, + ScriptTimeoutException, + ElementNotVisibleException, + ElementNotAccessibleException, + NoSuchFrameException, + InvalidElementStateException, + NoAlertPresentException, + InvalidCookieDomainException, + UnableToSetCookieException, + InvalidElementCoordinates, + InvalidSelectorException, + MoveTargetOutOfBoundsException, + FrameSendNotInitializedError, + FrameSendFailureError, + UnsupportedOperationException, + SessionNotCreatedException, + UnexpectedAlertOpen, +] + + +def lookup(identifier): + """Finds error exception class by associated Selenium JSON wire + protocol number code, or W3C WebDriver protocol string.""" + + by_code = lambda exc: identifier in exc.code + by_status = lambda exc: exc.status == identifier + + rv = None + if isinstance(identifier, int): + rv = filter(by_code, excs) + elif isinstance(identifier, str): + rv = filter(by_status, excs) + + if not rv: + return MarionetteException + return rv[0] + + +__all__ = excs + ["lookup"] diff --git a/testing/marionette/driver/marionette_driver/marionette.py b/testing/marionette/driver/marionette_driver/marionette.py index 436b89e12656..a7c59026cc37 100644 --- a/testing/marionette/driver/marionette_driver/marionette.py +++ b/testing/marionette/driver/marionette_driver/marionette.py @@ -693,8 +693,7 @@ class Marionette(object): self.session = None self.window = None self.client.close() - raise errors.TimeoutException( - "Connection timed out", status=errors.ErrorCodes.TIMEOUT) + raise errors.TimeoutException("Connection timed out") # Process any emulator commands that are sent from a script # while it's executing. @@ -745,60 +744,11 @@ class Marionette(object): "Malformed packet, expected key 'error' to be a dict: %s" % response) error = response["error"] - status = error.get("status", 500) + status = error.get("status") message = error.get("message") stacktrace = error.get("stacktrace") - # status numbers come from - # http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes - if status == errors.ErrorCodes.NO_SUCH_ELEMENT: - raise errors.NoSuchElementException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.NO_SUCH_FRAME: - raise errors.NoSuchFrameException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.STALE_ELEMENT_REFERENCE: - raise errors.StaleElementException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.ELEMENT_NOT_VISIBLE: - raise errors.ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.ELEMENT_NOT_ACCESSIBLE: - raise errors.ElementNotAccessibleException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.INVALID_ELEMENT_STATE: - raise errors.InvalidElementStateException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.UNKNOWN_ERROR: - raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.ELEMENT_IS_NOT_SELECTABLE: - raise errors.ElementNotSelectableException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.JAVASCRIPT_ERROR: - raise errors.JavascriptException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.XPATH_LOOKUP_ERROR: - raise errors.XPathLookupException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.TIMEOUT: - raise errors.TimeoutException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.NO_SUCH_WINDOW: - raise errors.NoSuchWindowException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.INVALID_COOKIE_DOMAIN: - raise errors.InvalidCookieDomainException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.UNABLE_TO_SET_COOKIE: - raise errors.UnableToSetCookieException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.NO_ALERT_OPEN: - raise errors.NoAlertPresentException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.SCRIPT_TIMEOUT: - raise errors.ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.INVALID_SELECTOR \ - or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR \ - or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER: - raise errors.InvalidSelectorException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS: - raise errors.MoveTargetOutOfBoundsException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.FRAME_SEND_NOT_INITIALIZED_ERROR: - raise errors.FrameSendNotInitializedError(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.FRAME_SEND_FAILURE_ERROR: - raise errors.FrameSendFailureError(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.UNSUPPORTED_OPERATION: - raise errors.UnsupportedOperationException(message=message, status=status, stacktrace=stacktrace) - elif status == errors.ErrorCodes.SESSION_NOT_CREATED: - raise errors.SessionNotCreatedException(message=message, status=status, stacktrace=stacktrace) - else: - raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace) + raise errors.lookup(status)(message, stacktrace=stacktrace) def _reset_timeouts(self): if self.timeout is not None: diff --git a/testing/marionette/error.js b/testing/marionette/error.js index f1888d457d2f..db5edcaaf236 100644 --- a/testing/marionette/error.js +++ b/testing/marionette/error.js @@ -124,6 +124,7 @@ this.WebDriverError = function(msg) { Error.call(this, msg); this.name = "WebDriverError"; this.message = msg; + this.status = "webdriver error"; this.code = 500; // overridden }; WebDriverError.prototype = Object.create(Error.prototype);