From 361c0396ff7fb61a2bd4c8277391ec5aaba792be Mon Sep 17 00:00:00 2001 From: Andreas Tolfsen Date: Fri, 24 Jan 2014 12:22:12 +0000 Subject: [PATCH] Bug 948075 - Add expected conditions to Marionette. r=mdas r=dhunt --- .../marionette/client/marionette/expected.py | 265 ++++++++++++++++++ .../marionette/tests/unit/test_expected.py | 198 +++++++++++++ .../marionette/tests/unit/unit-tests.ini | 3 +- 3 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 testing/marionette/client/marionette/expected.py create mode 100644 testing/marionette/client/marionette/tests/unit/test_expected.py diff --git a/testing/marionette/client/marionette/expected.py b/testing/marionette/client/marionette/expected.py new file mode 100644 index 000000000000..2c7cf21b47ae --- /dev/null +++ b/testing/marionette/client/marionette/expected.py @@ -0,0 +1,265 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import errors +import types + +"""This file provides a set of expected conditions for common use +cases when writing Marionette tests. + +The conditions rely on explicit waits that retries conditions a number +of times until they are either successfully met, or they time out. + +""" + +class element_present(object): + """Checks that a web element is present in the DOM of the current + context. This does not necessarily mean that the element is + visible. + + You can select which element to be checked for presence by + supplying a locator: + + el = Wait(marionette).until(expected.element_present(By.ID, "foo")) + + Or by using a function/lambda returning an element: + + el = Wait(marionette).until(expected.element_present(lambda m: m.find_element(By.ID, "foo"))) + + :param args: locator or function returning web element + :returns: the web element once it is located, or False + + """ + + def __init__(self, *args): + if len(args) == 1 and isinstance(args[0], types.FunctionType): + self.locator = args[0] + else: + self.locator = lambda m: m.find_element(*args) + + def __call__(self, marionette): + return _find(marionette, self.locator) + +class element_not_present(element_present): + """Checks that a web element is not present in the DOM of the current + context. + + You can select which element to be checked for lack of presence by + supplying a locator: + + r = Wait(marionette).until(expected.element_not_present(By.ID, "foo")) + + Or by using a function/lambda returning an element: + + r = Wait(marionette).until(expected.element_present(lambda m: m.find_element(By.ID, "foo"))) + + :param args: locator or function returning web element + :returns: True if element is not present, or False if it is present + + """ + + def __init__(self, *args): + super(element_not_present, self).__init__(*args) + + def __call__(self, marionette): + return not super(element_not_present, self).__call__(marionette) + +class element_stale(object): + """Check that the given element is no longer attached to DOM of the + current context. + + This can be useful for waiting until an element is no longer + present. + + Sample usage: + + el = marionette.find_element(By.ID, "foo") + # ... + Wait(marionette).until(expected.element_stale(el)) + + :param element: the element to wait for + :returns: False if the element is still attached to the DOM, True + otherwise + + """ + + def __init__(self, element): + self.el = element + + def __call__(self, marionette): + try: + # Calling any method forces a staleness check + self.el.is_enabled() + return False + except errors.StaleElementException: + return True + +class elements_present(object): + """Checks that web elements are present in the DOM of the current + context. This does not necessarily mean that the elements are + visible. + + You can select which elements to be checked for presence by + supplying a locator: + + els = Wait(marionette).until(expected.elements_present(By.TAG_NAME, "a")) + + Or by using a function/lambda returning a list of elements: + + els = Wait(marionette).until(expected.elements_present(lambda m: m.find_elements(By.TAG_NAME, "a"))) + + :param args: locator or function returning a list of web elements + :returns: list of web elements once they are located, or False + + """ + + def __init__(self, *args): + if len(args) == 1 and isinstance(args[0], types.FunctionType): + self.locator = args[0] + else: + self.locator = lambda m: m.find_elements(*args) + + def __call__(self, marionette): + return _find(marionette, self.locator) + +class elements_not_present(elements_present): + """Checks that web elements are not present in the DOM of the + current context. + + You can select which elements to be checked for not being present + by supplying a locator: + + r = Wait(marionette).until(expected.elements_not_present(By.TAG_NAME, "a")) + + Or by using a function/lambda returning a list of elements: + + r = Wait(marionette).until(expected.elements_not_present(lambda m: m.find_elements(By.TAG_NAME, "a"))) + + :param args: locator or function returning a list of web elements + :returns: True if elements are missing, False if one or more are + present + + """ + + def __init__(self, *args): + super(elements_not_present, self).__init__(*args) + + def __call__(self, marionette): + return not super(elements_not_present, self).__call__(marionette) + +class element_displayed(object): + """An expectation for checking that an element is visible. + + Visibility means that the element is not only displayed, but also + has a height and width that is greater than 0 pixels. + + Stale elements, meaning elements that have been detached from the + DOM of the current context are treated as not being displayed, + meaning this expectation is not analogous to the behaviour of + calling `is_displayed()` on an `HTMLElement`. + + :param element: the element to perform the visibility check on + :returns: True if element is displayed, False if hidden + + """ + + def __init__(self, element): + self.el = element + + def __call__(self, marionette): + try: + return self.el.is_displayed() + except errors.StaleElementException: + return False + +class element_not_displayed(element_displayed): + """An expectation for checking that an element is not visible. + + Visibility means that the element is not only displayed, but also + has a height and width that is greater than 0 pixels. + + Stale elements, meaning elements that have been detached fom the + DOM of the current context are treated as not being displayed, + meaning this expectation is not analogous to the behaviour of + calling `is_displayed()` on an `HTMLElement`. + + :param element: the element to perform the visibility check on + :returns: True if element is hidden, False if visible + + """ + + def __init__(self, element): + super(element_not_displayed, self).__init__(element) + + def __call__(self, marionette): + return not super(element_not_displayed, self).__call__(marionette) + +class element_selected(object): + """An expectation for checking that the given element is selected. + + :param element: the element to be selected + :returns: True if element is selected, False otherwise + + """ + + def __init__(self, element): + self.el = element + + def __call__(self, marionette): + return self.el.is_selected() + +class element_not_selected(element_selected): + """An expectation for checking that the given element is not + selected. + + :param element: the element to not be selected + :returns True if element is not selected, False if selected + + """ + + def __init__(self, element): + super(element_not_selected, self).__init__(element) + + def __call__(self, marionette): + return not super(element_not_selected, self).__call__(marionette) + +class element_enabled(object): + """An expectation for checking that the given element is enabled. + + :param element: the element to check if enabled + :returns: True if element is enabled, False otherwise + + """ + + def __init__(self, element): + self.el = element + + def __call__(self, marionette): + return self.el.is_enabled() + +class element_not_enabled(element_enabled): + """An expectation for checking that the given element is disabled. + + :param element: the element to check if disabled + :returns: True if element is disabled, False if enabled + + """ + + def __init__(self, element): + super(element_not_enabled, self).__init__(element) + + def __call__(self, marionette): + return not super(element_not_enabled, self).__call__(marionette) + +def _find(marionette, func): + el = None + + try: + el = func(marionette) + except errors.NoSuchElementException: + pass + + if el is None: + return False + return el diff --git a/testing/marionette/client/marionette/tests/unit/test_expected.py b/testing/marionette/client/marionette/tests/unit/test_expected.py new file mode 100644 index 000000000000..e4d7f2a72e26 --- /dev/null +++ b/testing/marionette/client/marionette/tests/unit/test_expected.py @@ -0,0 +1,198 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import urllib + +import expected +import marionette_test + +from by import By + +def inline(doc): + return "data:text/html;charset=utf-8,%s" % urllib.quote(doc) + +static_element = inline("""

foo

""") +static_elements = static_element + static_element + +remove_element_by_tag_name = \ + """var el = document.getElementsByTagName('%s')[0]; + document.getElementsByTagName("body")[0].remove(el);""" + +hidden_element = inline("

hidden

") + +selected_element = inline("") +unselected_element = inline("") + +enabled_element = inline("") +disabled_element = inline("") + +def no_such_element(marionette): + return marionette.find_element(By.ID, "nosuchelement") + +def no_such_elements(marionette): + return marionette.find_elements(By.ID, "nosuchelement") + +def p(marionette): + return marionette.find_element(By.TAG_NAME, "p") + +def ps(marionette): + return marionette.find_elements(By.TAG_NAME, "p") + +class TestExpected(marionette_test.MarionetteTestCase): + def test_element_present_func(self): + self.marionette.navigate(static_element) + el = expected.element_present(p)(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_locator(self): + self.marionette.navigate(static_element) + el = expected.element_present(By.TAG_NAME, "p")(self.marionette) + self.assertIsNotNone(el) + + def test_element_present_not_present(self): + r = expected.element_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_not_present_func(self): + r = expected.element_not_present(no_such_element)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_element_not_present_is_present(self): + self.marionette.navigate(static_element) + r = expected.element_not_present(p)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.assertIsNotNone(el) + self.marionette.execute_script(remove_element_by_tag_name % "p") + r = expected.element_stale(el)(self.marionette) + self.assertTrue(r) + + def test_element_stale_is_not_stale(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + r = expected.element_stale(el)(self.marionette) + self.assertFalse(r) + + def test_elements_present_func(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(ps)(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_present_locator(self): + self.marionette.navigate(static_elements) + els = expected.elements_present(By.TAG_NAME, "p")(self.marionette) + self.assertEqual(len(els), 2) + + def test_elements_not_present_func(self): + r = expected.element_not_present(no_such_elements)(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_locator(self): + r = expected.element_not_present(By.ID, "nosuchelement")(self.marionette) + self.assertIsInstance(r, bool) + self.assertTrue(r) + + def test_elements_not_present_is_present(self): + self.marionette.navigate(static_elements) + r = expected.elements_not_present(ps)(self.marionette) + self.assertIsInstance(r, bool) + self.assertFalse(r) + + def test_element_displayed(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_displayed(el)(self.marionette) + self.assertTrue(visible) + + def test_element_displayed_when_hidden(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_displayed(el)(self.marionette) + self.assertFalse(hidden) + + def test_element_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_displayed(el)(self.marionette) + self.assertFalse(missing) + + def test_element_not_displayed(self): + self.marionette.navigate(hidden_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + visible = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(visible) + + def test_element_not_displayed_when_visible(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + hidden = expected.element_not_displayed(el)(self.marionette) + self.assertFalse(hidden) + + def test_element_not_displayed_when_stale_element(self): + self.marionette.navigate(static_element) + el = self.marionette.find_element(By.TAG_NAME, "p") + self.marionette.navigate("about:blank") + missing = expected.element_not_displayed(el)(self.marionette) + self.assertTrue(missing) + + def test_element_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_selected(el)(self.marionette) + self.assertTrue(selected) + + def test_element_selected_when_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_selected(el)(self.marionette) + self.assertFalse(unselected) + + def test_element_not_selected(self): + self.marionette.navigate(unselected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + unselected = expected.element_not_selected(el)(self.marionette) + self.assertTrue(unselected) + + def test_element_not_selected_when_selected(self): + self.marionette.navigate(selected_element) + el = self.marionette.find_element(By.TAG_NAME, "option") + selected = expected.element_not_selected(el)(self.marionette) + self.assertFalse(selected) + + def test_element_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_enabled(el)(self.marionette) + self.assertTrue(enabled) + + def test_element_enabled_when_disabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_enabled(el)(self.marionette) + self.assertFalse(disabled) + + def test_element_not_enabled(self): + self.marionette.navigate(disabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + disabled = expected.element_not_enabled(el)(self.marionette) + self.assertTrue(disabled) + + def test_element_not_enabled_when_enabled(self): + self.marionette.navigate(enabled_element) + el = self.marionette.find_element(By.TAG_NAME, "input") + enabled = expected.element_not_enabled(el)(self.marionette) + self.assertFalse(enabled) diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/client/marionette/tests/unit/unit-tests.ini index bb53ddb46a93..045447a97dc0 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -93,6 +93,7 @@ b2g = false b2g = false [test_implicit_waits.py] [test_wait.py] +[test_expected.py] [test_date_time_value.py] [test_getactiveframe_oop.py] disabled = "Bug 925688" @@ -100,4 +101,4 @@ disabled = "Bug 925688" [test_chrome_async_finish.js] [test_screen_orientation.py] browser = false -[test_errors.py] \ No newline at end of file +[test_errors.py]