From 52fd1321654dc41891f13efe5ff167b53e63c602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 12 Oct 2017 22:28:13 +0300 Subject: [PATCH 01/12] Cleanup. - Remove unnecessary coupling between Element and FormElement keywords. - Enhance related error message. --- src/SeleniumLibrary/keywords/element.py | 14 +++++--------- src/SeleniumLibrary/keywords/formelement.py | 9 --------- .../element_should_be_enabled_and_disabled.robot | 6 ++++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index fb29e7958..e1b19e70a 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -18,16 +18,11 @@ from selenium.webdriver.common.keys import Keys from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.keywords.formelement import FormElementKeywords from SeleniumLibrary.utils import escape_xpath_value, is_falsy, is_truthy class ElementKeywords(LibraryComponent): - def __init__(self, ctx): - LibraryComponent.__init__(self, ctx) - self.form_element = FormElementKeywords(ctx) - @keyword def get_webelement(self, locator): """Returns the first WebElement matching the given locator. @@ -828,12 +823,13 @@ def _get_text(self, locator): def _is_enabled(self, locator): element = self.find_element(locator) - if not self.form_element._is_form_element(element): - raise AssertionError("ERROR: Element %s is not an input." % locator) + if element.tag_name.lower() not in {'input', 'select', 'textarea', + 'button', 'option'}: + raise ValueError("Element '%s' is '%s', not an input element." + % (locator, element.tag_name)) if not element.is_enabled(): return False - read_only = element.get_attribute('readonly') - if read_only == 'readonly' or read_only == 'true': + if element.get_attribute('readonly') in {'readonly', 'true'}: return False return True diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 8abc40e9c..a4a6ece96 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -22,9 +22,6 @@ class FormElementKeywords(LibraryComponent): - def __init__(self, ctx): - LibraryComponent.__init__(self, ctx) - @keyword def submit_form(self, locator=None): """Submits a form identified by `locator`. @@ -399,9 +396,3 @@ def _input_text_into_text_field(self, locator, text): element = self.find_element(locator) element.clear() element.send_keys(text) - - def _is_form_element(self, element): - if element is None: - return False - tag = element.tag_name.lower() - return tag == 'input' or tag == 'select' or tag == 'textarea' or tag == 'button' or tag == 'option' diff --git a/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot b/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot index 78e72dfa0..ca3de54ff 100644 --- a/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot +++ b/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot @@ -46,9 +46,11 @@ Disabled with different syntaxes Not Input nor Editable Element [Documentation] Not Input nor Editable Element - Run Keyword And Expect Error ERROR: Element table1 is not an input. + Run Keyword And Expect Error + ... ValueError: Element 'table1' is 'table', not an input element. ... Element Should Be Enabled table1 - Run Keyword And Expect Error ERROR: Element table1 is not an input. + Run Keyword And Expect Error + ... ValueError: Element 'table1' is 'table', not an input element. ... Element Should Be Disabled table1 *** Keywords *** From 030e845ec551b84a0fa15cea5c80ccce67d4fe3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Fri, 13 Oct 2017 10:01:29 +0300 Subject: [PATCH 02/12] Quite big cleanup commit. - Enhance documentation of ElementKeywords (#925). - Add '.' at the end of error and log messages. - Introduce `ContextsAware.find_elements` helper. - Change default value of optional `message` from '' to None. - Use `is_noney` instead of `is_falsy` when default value is None. - General (and trivial) cleanup to code and tests. --- src/SeleniumLibrary/__init__.py | 8 +- src/SeleniumLibrary/base/context.py | 3 + .../keywords/browsermanagement.py | 4 +- src/SeleniumLibrary/keywords/element.py | 685 +++++++++--------- src/SeleniumLibrary/keywords/formelement.py | 72 +- src/SeleniumLibrary/keywords/selectelement.py | 15 +- src/SeleniumLibrary/locators/elementfinder.py | 11 +- src/SeleniumLibrary/utils/__init__.py | 2 +- .../keywords/checkbox_and_radio_buttons.robot | 10 +- .../click_element_at_coordinates.robot | 2 +- .../keywords/content_assertions.robot | 216 ++++-- test/acceptance/keywords/element_focus.robot | 32 +- test/acceptance/keywords/elements.robot | 20 +- test/acceptance/keywords/lists.robot | 22 +- test/acceptance/keywords/mouse.robot | 16 +- test/acceptance/keywords/run_on_failure.robot | 31 +- test/acceptance/keywords/textfields.robot | 21 +- test/acceptance/keywords/variables.py | 2 - .../acceptance/locators/locator_parsing.robot | 2 +- .../test_keyword_arguments_element.py | 12 +- .../test_keyword_arguments_formelement.py | 3 +- 21 files changed, 619 insertions(+), 570 deletions(-) delete mode 100644 test/acceptance/keywords/variables.py diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index bbb2b4757..a4b823d94 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -64,10 +64,10 @@ class SeleniumLibrary(DynamicCore): = Locating elements = All keywords in SeleniumLibrary that need to interact with an element - on a web page take an argument named ``locator`` that specifies how - to find the element. Most often the locator is given as a string using - the locator syntax described below, but `using WebElements` is possible - too. + on a web page take an argument typically named ``locator`` that specifies + how to find the element. Most often the locator is given as a string + using the locator syntax described below, but `using WebElements` is + possible too. == Locator syntax == diff --git a/src/SeleniumLibrary/base/context.py b/src/SeleniumLibrary/base/context.py index 52e16e160..d637577eb 100644 --- a/src/SeleniumLibrary/base/context.py +++ b/src/SeleniumLibrary/base/context.py @@ -42,6 +42,9 @@ def find_element(self, locator, tag=None, first_only=True, required=True, return self.element_finder.find(locator, tag, first_only, required, parent) + def find_elements(self, locator, tag=None, parent=None): + return self.find_element(locator, tag, False, False, parent) + @property def table_element_finder(self): return self.ctx.table_element_finder diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 327f573ba..9445c897e 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -431,8 +431,8 @@ def location_should_be(self, url): """Verifies that current URL is exactly ``url``.""" actual = self.get_location() if actual != url: - raise AssertionError("Location should have been '%s' but was '%s'" - % (url, actual)) + raise AssertionError("Location should have been '%s' but was " + "'%s'." % (url, actual)) self.info("Current location is '%s'." % url) @keyword diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index e1b19e70a..d46e4e4c1 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -18,45 +18,49 @@ from selenium.webdriver.common.keys import Keys from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.utils import escape_xpath_value, is_falsy, is_truthy +from SeleniumLibrary.utils import (escape_xpath_value, is_falsy, is_noney, + is_truthy, plural_or_not as s) class ElementKeywords(LibraryComponent): @keyword def get_webelement(self, locator): - """Returns the first WebElement matching the given locator. + """Returns the first WebElement matching the given ``locator``. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ return self.find_element(locator) @keyword def get_webelements(self, locator): - """Returns list of WebElement objects matching locator. + """Returns list of WebElement objects matching the ``locator``. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. - New in SeleniumLibrary 3.0.0: If element is not found, keyword - does not anymore fail. In previous releases keyword did fail if - element is not found. + Starting from SeleniumLibrary 3.0, the keyword returns an empty + list if there are no matching elements. In previous releases the + keyword failed in this case. """ return self.find_element(locator, first_only=False, required=False) @keyword def current_frame_should_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains `text`. + """Verifies that current frame contains ``text``. - See `Page Should Contain ` for explanation about `loglevel` argument. + See `Page Should Contain` for explanation about the ``loglevel`` + argument. Prior to SeleniumLibrary 3.0 this keyword was named `Current Frame Contains`. """ if not self.is_text_present(text): self.ctx.log_source(loglevel) - raise AssertionError("Page should have contained text '%s' " - "but did not" % text) - self.info("Current page contains text '%s'." % text) + raise AssertionError("Frame should have contained text '%s' " + "but did not." % text) + self.info("Current frame contains text '%s'." % text) @keyword def current_frame_contains(self, text, loglevel='INFO'): @@ -65,188 +69,200 @@ def current_frame_contains(self, text, loglevel='INFO'): @keyword def current_frame_should_not_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains `text`. + """Verifies that current frame contains ``text``. - See `Page Should Contain ` for explanation about `loglevel` argument. + See `Page Should Contain` for explanation about the ``loglevel`` + argument. """ if self.is_text_present(text): self.ctx.log_source(loglevel) - raise AssertionError("Page should not have contained text '%s' " - "but it did" % text) - self.info("Current page should not contain text '%s'." % text) + raise AssertionError("Frame should not have contained text '%s' " + "but it did." % text) + self.info("Current frame did not contain text '%s'." % text) @keyword - def element_should_contain(self, locator, expected, message=''): - """Verifies element identified by `locator` contains text `expected`. + def element_should_contain(self, locator, expected, message=None): + """Verifies that element ``locator`` contains text ``expected``. - If you wish to assert an exact (not a substring) match on the text - of the element, use `Element Text Should Be`. + See the `Locating elements` section for details about the locator + syntax. - `message` can be used to override the default error message. + The ``message`` argument can be used to override the default error + message. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + Use `Element Text Should Be` if you want to match the exact text, + not a substring. """ - self.info("Verifying element '%s' contains " - "text '%s'." % (locator, expected)) + self.info("Verifying element '%s' contains text '%s'." + % (locator, expected)) actual = self._get_text(locator) if expected not in actual: - if is_falsy(message): + if is_noney(message): message = "Element '%s' should have contained text '%s' but "\ "its text was '%s'." % (locator, expected, actual) raise AssertionError(message) @keyword - def element_should_not_contain(self, locator, expected, message=''): - """Verifies element identified by `locator` does not contain text `expected`. + def element_should_not_contain(self, locator, expected, message=None): + """Verifies that element ``locator`` does not contains text ``expected``. - `message` can be used to override the default error message. + See the `Locating elements` section for details about the locator + syntax. - Key attributes for arbitrary elements are `id` and `name`. See - `Element Should Contain` for more details. + The ``message`` argument can be used to override the default error + message. """ self.info("Verifying element '%s' does not contain text '%s'." % (locator, expected)) actual = self._get_text(locator) if expected in actual: - if is_falsy(message): + if is_noney(message): message = "Element '%s' should not contain text '%s' but " \ "it did." % (locator, expected) raise AssertionError(message) @keyword def frame_should_contain(self, locator, text, loglevel='INFO'): - """Verifies frame identified by `locator` contains `text`. + """Verifies that frame identified by ``locator`` contains ``text``. - See `Page Should Contain ` for explanation about `loglevel` argument. + See the `Locating elements` section for details about the locator + syntax. - Key attributes for frames are `id` and `name.` See `introduction` for - details about locating elements. + See `Page Should Contain` for explanation about the ``loglevel`` + argument. """ if not self._frame_contains(locator, text): self.ctx.log_source(loglevel) - raise AssertionError("Page should have contained text '%s' " - "but did not" % text) - self.info("Current page contains text '%s'." % text) + raise AssertionError("Frame '%s' should have contained text '%s' " + "but did not." % (locator, text)) + self.info("Frame '%s' contains text '%s'." % (locator, text)) @keyword def page_should_contain(self, text, loglevel='INFO'): - """Verifies that current page contains `text`. + """Verifies that current page contains ``text``. If this keyword fails, it automatically logs the page source - using the log level specified with the optional `loglevel` argument. - Valid log levels are DEBUG, INFO (default), WARN, and NONE. If the - log level is NONE or below the current active log level the source - will not be logged. + using the log level specified with the optional ``loglevel`` + argument. Valid log levels are ``DEBUG``, ``INFO`` (default), + ``WARN``, and ``NONE``. If the log level is ``NONE`` or below + the current active log level the source will not be logged. """ if not self._page_contains(text): self.ctx.log_source(loglevel) raise AssertionError("Page should have contained text '%s' " - "but did not" % text) + "but did not." % text) self.info("Current page contains text '%s'." % text) @keyword - def page_should_contain_element(self, locator, message='', loglevel='INFO'): - """Verifies element identified by `locator` is found on the current page. + def page_should_contain_element(self, locator, message=None, loglevel='INFO'): + """Verifies that element ``locator`` is found on the current page. - `message` can be used to override default error message. + See the `Locating elements` section for details about the locator + syntax. - See `Page Should Contain` for explanation about `loglevel` argument. + The ``message`` argument can be used to override the default error + message. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See `Page Should Contain` for explanation about the ``loglevel`` + argument. """ self.assert_page_contains(locator, message=message, loglevel=loglevel) @keyword - def locator_should_match_x_times(self, locator, expected_locator_count, message='', loglevel='INFO'): - """Verifies that the page contains the given number of elements located by the given `locator`. + def locator_should_match_x_times(self, locator, x, message=None, loglevel='INFO'): + """Verifies that ``locator`` matches ``x`` number of elements. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. + See `Page Should Contain Element` for explanation about ``message`` + and ``loglevel`` arguments. """ - actual_locator_count = len(self.find_element( - locator, first_only=False, required=False) - ) - if int(actual_locator_count) != int(expected_locator_count): + count = len(self.find_elements(locator)) + x = int(x) + if count != x: if is_falsy(message): - message = "Locator %s should have matched %s times but matched %s times"\ - %(locator, expected_locator_count, actual_locator_count) + message = ("Locator '%s' should have matched %s time%s but " + "matched %s time%s." + % (locator, x, s(x), count, s(count))) self.ctx.log_source(loglevel) raise AssertionError(message) self.info("Current page contains %s elements matching '%s'." - % (actual_locator_count, locator)) + % (count, locator)) @keyword def page_should_not_contain(self, text, loglevel='INFO'): - """Verifies the current page does not contain `text`. + """Verifies the current page does not contain ``text``. - See `Page Should Contain ` for explanation about `loglevel` argument. + See `Page Should Contain` for explanation about the ``loglevel`` + argument. """ if self._page_contains(text): self.ctx.log_source(loglevel) - raise AssertionError("Page should not have contained text '%s'" % text) + raise AssertionError("Page should not have contained text '%s'." + % text) self.info("Current page does not contain text '%s'." % text) @keyword - def page_should_not_contain_element(self, locator, message='', loglevel='INFO'): - """Verifies element identified by `locator` is not found on the current page. - - `message` can be used to override the default error message. + def page_should_not_contain_element(self, locator, message=None, loglevel='INFO'): + """Verifies that element ``locator`` is found on the current page. - See `Page Should Contain ` for explanation about `loglevel` argument. + See the `Locating elements` section for details about the locator + syntax. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See `Page Should Contain` for explanation about ``message`` and + ``loglevel`` arguments. """ self.assert_page_not_contains(locator, message=message, loglevel=loglevel) @keyword def assign_id_to_element(self, locator, id): - """Assigns a temporary identifier to element specified by `locator`. + """Assigns temporary ``id`` to element specified by ``locator``. - This is mainly useful if the locator is complicated/slow XPath expression. - Identifier expires when the page is reloaded. + This is mainly useful if the locator is complicated and/or slow XPath + expression and it is needed multiple times. Identifier expires when + the page is reloaded. + + See the `Locating elements` section for details about the locator + syntax. Example: - | Assign ID to Element | xpath=//div[@id="first_div"] | my id | - | Page Should Contain Element | my id | + | `Assign ID to Element` | //ul[@class='example' and ./li[contains(., 'Stuff')]] | my id | + | `Page Should Contain Element` | my id | """ - self.info("Assigning temporary id '%s' to element '%s'" % (id, locator)) + self.info("Assigning temporary id '%s' to element '%s'." % (id, locator)) element = self.find_element(locator) self.browser.execute_script("arguments[0].id = '%s';" % id, element) @keyword def element_should_be_disabled(self, locator): - """Verifies that element identified with `locator` is disabled. + """Verifies that element identified with ``locator`` is disabled. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ if self._is_enabled(locator): - raise AssertionError("Element '%s' is enabled." % (locator)) + raise AssertionError("Element '%s' is enabled." % locator) @keyword def element_should_be_enabled(self, locator): - """Verifies that element identified with `locator` is enabled. + """Verifies that element identified with ``locator`` is enabled. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ if not self._is_enabled(locator): - raise AssertionError("Element '%s' is disabled." % (locator)) + raise AssertionError("Element '%s' is disabled." % locator) @keyword def element_should_be_focused(self, locator): - """Verifies that element identified with `locator` is focused. + """Verifies that element identified with ``locator`` is focused. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. - New in SeleniumLibrary 3.0.0. + New in SeleniumLibrary 3.0. """ element = self.find_element(locator) if self.browser.capabilities['browserName'] != "firefox": @@ -254,187 +270,179 @@ def element_should_be_focused(self, locator): else: focused = self.browser.execute_script('return document.activeElement;') if element != focused: - raise AssertionError("Element '%s' is not with focus." % (locator)) + raise AssertionError("Element '%s' does not have focus." % locator) @keyword - def element_should_be_visible(self, locator, message=''): - """Verifies that the element identified by `locator` is visible. + def element_should_be_visible(self, locator, message=None): + """Verifies that the element identified by ``locator`` is visible. - Herein, visible means that the element is logically visible, not optically - visible in the current browser viewport. For example, an element that carries - display:none is not logically visible, so using this keyword on that element - would fail. + Herein, visible means that the element is logically visible, not + optically visible in the current browser viewport. For example, + an element that carries ``display:none`` is not logically visible, + so using this keyword on that element would fail. - `message` can be used to override the default error message. + See the `Locating elements` section for details about the locator + syntax. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + The ``message`` argument can be used to override the default error + message. """ self.info("Verifying element '%s' is visible." % locator) visible = self.is_visible(locator) if not visible: - if is_falsy(message): + if is_noney(message): message = ("The element '%s' should be visible, but it " "is not." % locator) raise AssertionError(message) @keyword - def element_should_not_be_visible(self, locator, message=''): - """Verifies that the element identified by `locator` is NOT visible. + def element_should_not_be_visible(self, locator, message=None): + """Verifies that the element identified by ``locator`` is NOT visible. This is the opposite of `Element Should Be Visible`. - - `message` can be used to override the default error message. - - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. """ self.info("Verifying element '%s' is not visible." % locator) visible = self.is_visible(locator) if visible: - if is_falsy(message): + if is_noney(message): message = ("The element '%s' should not be visible, " "but it is." % locator) raise AssertionError(message) @keyword - def element_text_should_be(self, locator, expected, message=''): - """Verifies element identified by `locator` exactly contains text `expected`. + def element_text_should_be(self, locator, expected, message=None): + """Verifies that element ``locator`` contains exact text ``expected``. - In contrast to `Element Should Contain`, this keyword does not try - a substring match but an exact match on the element identified by `locator`. + See the `Locating elements` section for details about the locator + syntax. - `message` can be used to override the default error message. + The ``message`` argument can be used to override the default error + message. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + Use `Element Should Contain` if a substring match is desired. """ - self.info("Verifying element '%s' contains exactly text '%s'." + self.info("Verifying element '%s' contains exact text '%s'." % (locator, expected)) element = self.find_element(locator) - actual = element.text - if expected != actual: - if is_falsy(message): + if element.text != expected: + if is_noney(message): message = ("The text of element '%s' should have been '%s' " - "but in fact it was '%s'." - % (locator, expected, actual)) + "but it was '%s'." + % (locator, expected, element.text)) raise AssertionError(message) @keyword - def get_element_attribute(self, locator, attribute_name=None): - """Returns value of the element attribute. + def get_element_attribute(self, locator, attribute=None): + """Returns value of ``attribute`` from element ``locator``. - There are two cases how to use this keyword. - - First, if only `locator` is provided, `locator` should consists of - element locator followed by an @ sign and attribute name. - This behavior is left for backward compatibility. + See the `Locating elements` section for details about the locator + syntax. Example: - | ${id}= | Get Element Attribute | link=Link with id@id | - - Second, if `locator` and `attribute_name` are provided both, `locator` - should be standard locator and `attribute_name` is name of the - requested element attribute. + | ${id}= | `Get Element Attribute` | css:h1 | id | - Examples: - | ${id}= | Get Element Attribute | link=Link with id | id | - | ${element_by_dom}= | Get Webelement | dom=document.getElementsByTagName('a')[3] | - | ${id}= | Get Element Attribute | ${element_by_dom} | id | + Passing attribute name as part of the ``locator`` is deprecated + since SeleniumLibrary 3.0. The explicit ``attribute`` argument + should be used instead. """ - if is_falsy(attribute_name): - locator, attribute_name = self._parse_attribute_locator(locator) - element = self.find_element(locator, required=False) - if not element: - raise ValueError("Element '%s' not found." % (locator)) - return element.get_attribute(attribute_name) + if is_noney(attribute): + self.warn("Using 'Get Element Attribute' without explicit " + "attribute is deprecated.") + locator, attribute = locator.rsplit('@', 1) + return self.find_element(locator).get_attribute(attribute) @keyword def get_horizontal_position(self, locator): - """Returns horizontal position of element identified by `locator`. + """Returns horizontal position of element identified by ``locator``. + + See the `Locating elements` section for details about the locator + syntax. The position is returned in pixels off the left side of the page, - as an integer. Fails if a matching element is not found. + as an integer. See also `Get Vertical Position`. """ - element = self.find_element(locator, required=False) - if not element: - raise AssertionError("Could not determine position for '%s'" - % locator) - return element.location['x'] + return self.find_element(locator).location['x'] @keyword def get_element_size(self, locator): - """Returns width and height of element identified by `locator`. + """Returns width and height of element identified by ``locator``. + + See the `Locating elements` section for details about the locator + syntax. - The element width and height is returned. - Fails if a matching element is not found. + Both width and height are returned as integers. + + Example: + | ${width} | ${height} = | `Get Element Size` | css:div#container | """ element = self.find_element(locator) return element.size['width'], element.size['height'] @keyword def get_value(self, locator): - """Returns the value attribute of element identified by `locator`. + """Returns the value attribute of element identified by ``locator``. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ return self.ctx.element_finder.get_value(locator) @keyword def get_text(self, locator): - """Returns the text value of element identified by `locator`. + """Returns the text value of element identified by ``locator``. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ return self._get_text(locator) @keyword def clear_element_text(self, locator): - """Clears the text value of text entry element identified by `locator`. + """Clears the value of text entry element identified by ``locator``. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ - element = self.find_element(locator) - element.clear() + self.find_element(locator).clear() @keyword def get_vertical_position(self, locator): - """Returns vertical position of element identified by `locator`. + """Returns vertical position of element identified by ``locator``. + + See the `Locating elements` section for details about the locator + syntax. The position is returned in pixels off the top of the page, - as an integer. Fails if a matching element is not found. + as an integer. See also `Get Horizontal Position`. """ - element = self.find_element(locator, required=False) - if element is None: - raise AssertionError("Could not determine position for '%s'" - % locator) - return element.location['y'] + return self.find_element(locator).location['y'] @keyword def click_element(self, locator): - """Click element identified by `locator`. + """Click element identified by ``locator``. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ self.info("Clicking element '%s'." % locator) self.find_element(locator).click() @keyword def click_element_at_coordinates(self, locator, xoffset, yoffset): - """Click element identified by `locator` at x/y coordinates of the element. + """Click element ``locator`` at ``xoffset/yoffset``. + Cursor is moved and the center of the element and x/y coordinates are - calculted from that point. + calculated from that point. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ - self.info("Click clicking element '%s' in coordinates " - "'%s', '%s'." % (locator, xoffset, yoffset)) + self.info("Clicking element '%s' at coordinates x=%s, y=%s." + % (locator, xoffset, yoffset)) element = self.find_element(locator) action = ActionChains(self.browser) action.move_to_element(element) @@ -444,10 +452,10 @@ def click_element_at_coordinates(self, locator, xoffset, yoffset): @keyword def double_click_element(self, locator): - """Double click element identified by `locator`. + """Double click element identified by ``locator``. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ self.info("Double clicking element '%s'." % locator) element = self.find_element(locator) @@ -456,7 +464,10 @@ def double_click_element(self, locator): @keyword def set_focus_to_element(self, locator): - """Sets focus to element identified by `locator`. + """Sets focus to element identified by ``locator``. + + See the `Locating elements` section for details about the locator + syntax. Prior to SeleniumLibrary 3.0 this keyword was named `Focus`. """ @@ -469,68 +480,65 @@ def focus(self, locator): self.set_focus_to_element(locator) @keyword - def drag_and_drop(self, source, target): - """Drags element identified with `source` which is a locator. - - Element can be moved on top of another element with `target` - argument. + def drag_and_drop(self, locator, target): + """Drags ``source`` element into ``target`` element. - `target` is a locator of the element where the dragged object is - dropped. + The ``locator`` argument is the locator of the dragged element + and the ``target`` is the locator of the target. See the + `Locating elements` section for details about the locator syntax. - Examples: - | Drag And Drop | elem1 | elem2 | # Move elem1 over elem2. | + Example: + | `Drag And Drop` | css:div#element | css:div.target | """ - src_elem = self.find_element(source) - trg_elem = self.find_element(target) + element = self.find_element(locator) + target = self.find_element(target) action = ActionChains(self.browser) - action.drag_and_drop(src_elem, trg_elem).perform() + action.drag_and_drop(element, target).perform() @keyword - def drag_and_drop_by_offset(self, source, xoffset, yoffset): - """Drags element identified with `source` which is a locator. + def drag_and_drop_by_offset(self, locator, xoffset, yoffset): + """Drags element identified with ``locator`` by ``xoffset/yoffset``. - Element will be moved by xoffset and yoffset, each of which is a - negative or positive number specify the offset. + See the `Locating elements` section for details about the locator + syntax. - Examples: - | Drag And Drop By Offset | myElem | 50 | -35 | # Move myElem 50px right and 35px down. | + Element will be moved by ``xoffset`` and ``yoffset``, each of which + is a negative or positive number specifying the offset. + + Example: + | `Drag And Drop By Offset` | myElem | 50 | -35 | # Move myElem 50px right and 35px down | """ - src_elem = self.find_element(source) + element = self.find_element(locator) action = ActionChains(self.browser) - action.drag_and_drop_by_offset(src_elem, xoffset, yoffset) + action.drag_and_drop_by_offset(element, int(xoffset), int(yoffset)) action.perform() @keyword def mouse_down(self, locator): - """Simulates pressing the left mouse button on the element specified by `locator`. + """Simulates pressing the left mouse button on the element ``locator``. - The element is pressed without releasing the mouse button. + See the `Locating elements` section for details about the locator + syntax. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + The element is pressed without releasing the mouse button. See also the more specific keywords `Mouse Down On Image` and `Mouse Down On Link`. """ - self.info("Simulating Mouse Down on element '%s'" % locator) - element = self.find_element(locator, required=False) - if element is None: - raise AssertionError("ERROR: Element %s not found." % (locator)) + self.info("Simulating Mouse Down on element '%s'." % locator) + element = self.find_element(locator) action = ActionChains(self.browser) action.click_and_hold(element).perform() @keyword def mouse_out(self, locator): - """Simulates moving mouse away from the element specified by `locator`. + """Simulates moving mouse away from the element ``locator``. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ - self.info("Simulating Mouse Out on element '%s'" % locator) - element = self.find_element(locator, required=False) - if element is None: - raise AssertionError("ERROR: Element %s not found." % (locator)) + self.info("Simulating Mouse Out on element '%s'." % locator) + element = self.find_element(locator) size = element.size offsetx = (size['width'] / 2) + 1 offsety = (size['height'] / 2) + 1 @@ -540,46 +548,43 @@ def mouse_out(self, locator): @keyword def mouse_over(self, locator): - """Simulates hovering mouse over the element specified by `locator`. + """Simulates hovering mouse over the element ``locator``. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ - self.info("Simulating Mouse Over on element '%s'" % locator) - element = self.find_element(locator, required=False) - if element is None: - raise AssertionError("ERROR: Element %s not found." % (locator)) + self.info("Simulating Mouse Over on element '%s'." % locator) + element = self.find_element(locator) action = ActionChains(self.browser) action.move_to_element(element).perform() @keyword def mouse_up(self, locator): - """Simulates releasing the left mouse button on the element specified by `locator`. + """Simulates releasing the left mouse button on the element ``locator``. - Key attributes for arbitrary elements are `id` and `name`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. """ - self.info("Simulating Mouse Up on element '%s'" % locator) - element = self.find_element(locator, required=False) - if element is None: - raise AssertionError("ERROR: Element %s not found." % (locator)) + self.info("Simulating Mouse Up on element '%s'." % locator) + element = self.find_element(locator) ActionChains(self.browser).release(element).perform() @keyword def open_context_menu(self, locator): - """Opens context menu on element identified by `locator`.""" + """Opens context menu on element identified by ``locator``.""" element = self.find_element(locator) action = ActionChains(self.browser) action.context_click(element).perform() @keyword def simulate_event(self, locator, event): - """Simulates `event` on element identified by `locator`. + """Simulates ``event`` on element identified by ``locator``. - This keyword is useful if element has OnEvent handler that needs to be - explicitly invoked. + This keyword is useful if element has ``OnEvent`` handler that + needs to be explicitly invoked. - See `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. Prior to SeleniumLibrary 3.0 this keyword was named `Simulate`. """ @@ -601,16 +606,20 @@ def simulate(self, locator, event): """Deprecated. Use `Simulate Event` instead.""" self.simulate_event(locator, event) - @keyword def press_key(self, locator, key): - """Simulates user pressing key on element identified by `locator`. - `key` is either a single character, a string, or a numerical ASCII code of the key - lead by '\\\\'. + r"""Simulates user pressing key on element identified by ``locator``. + + See the `Locating elements` section for details about the locator + syntax. + + ``key`` is either a single character, a string, or a numerical ASCII + code of the key lead by '\\'. + Examples: - | Press Key | text_field | q | - | Press Key | text_field | abcde | - | Press Key | login_button | \\\\13 | # ASCII code for enter key | + | `Press Key` | text_field | q | + | `Press Key` | text_field | abcde | + | `Press Key` | login_button | \\13 | # ASCII code for enter key | """ if key.startswith('\\') and len(key) > 1: key = self._map_ascii_key_code_to_key(int(key[1:])) @@ -619,14 +628,14 @@ def press_key(self, locator, key): @keyword def click_link(self, locator): - """Clicks a link identified by locator. + """Clicks a link identified by ``locator``. - Key attributes for links are `id`, `name`, `href` and link text. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. Key attributes for links are ``id``, ``name``, ``href`` and + link text. """ self.info("Clicking link '%s'." % locator) - link = self.find_element(locator, tag='a') - link.click() + self.find_element(locator, tag='a').click() @keyword def get_all_links(self): @@ -634,54 +643,53 @@ def get_all_links(self): If a link has no id, an empty string will be in the list instead. """ - links = [] - elements = self.find_element("tag=a", tag='a', first_only=False, - required=False) - for anchor in elements: - links.append(anchor.get_attribute('id')) - return links + links = self.find_elements("tag=a", tag='a') + return [link.get_attribute('id') for link in links] @keyword def mouse_down_on_link(self, locator): - """Simulates a mouse down event on a link. + """Simulates a mouse down event on a link identified by ``locator``. - Key attributes for links are `id`, `name`, `href` and link text. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. Key attributes for links are ``id``, ``name``, ``href`` and + link text. """ - element = self.find_element(locator, tag='link') + element = self.find_element(locator, tag='a') action = ActionChains(self.browser) action.click_and_hold(element).perform() @keyword - def page_should_contain_link(self, locator, message='', loglevel='INFO'): - """Verifies link identified by `locator` is found from current page. + def page_should_contain_link(self, locator, message=None, loglevel='INFO'): + """Verifies link identified by ``locator`` is found from current page. - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. + See the `Locating elements` section for details about the locator + syntax. Key attributes for links are ``id``, ``name``, ``href`` and + link text. - Key attributes for links are `id`, `name`, `href` and link text. See - `introduction` for details about locating elements. + See `Page Should Contain Element` for explanation about ``message`` + and ``loglevel`` arguments. """ self.assert_page_contains(locator, 'link', message, loglevel) @keyword - def page_should_not_contain_link(self, locator, message='', loglevel='INFO'): - """Verifies image identified by `locator` is not found from current page. + def page_should_not_contain_link(self, locator, message=None, loglevel='INFO'): + """Verifies image identified by ``locator`` is not found from current page. - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. + See the `Locating elements` section for details about the locator + syntax. Key attributes for links are ``id``, ``name``, ``href`` and + link text. - Key attributes for images are `id`, `src` and `alt`. See - `introduction` for details about locating elements. + See `Page Should Contain Element` for explanation about ``message`` + and ``loglevel`` arguments. """ self.assert_page_not_contains(locator, 'link', message, loglevel) @keyword def click_image(self, locator): - """Clicks an image found by `locator`. + """Clicks an image identified by ``locator``. - Key attributes for images are `id`, `src` and `alt`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. Key attributes for images are ``id``, ``src`` and ``alt``. """ self.info("Clicking image '%s'." % locator) element = self.find_element(locator, tag='image', required=False) @@ -692,108 +700,72 @@ def click_image(self, locator): @keyword def mouse_down_on_image(self, locator): - """Simulates a mouse down event on an image. + """Simulates a mouse down event on an image identified by ``locator``. - Key attributes for images are `id`, `src` and `alt`. See - `introduction` for details about locating elements. + See the `Locating elements` section for details about the locator + syntax. Key attributes for images are ``id``, ``src`` and ``alt``. """ element = self.find_element(locator, tag='image') action = ActionChains(self.browser) action.click_and_hold(element).perform() @keyword - def page_should_contain_image(self, locator, message='', loglevel='INFO'): - """Verifies image identified by `locator` is found from current page. - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. + def page_should_contain_image(self, locator, message=None, loglevel='INFO'): + """Verifies image identified by ``locator`` is found from current page. + + See the `Locating elements` section for details about the locator + syntax. Key attributes for images are ``id``, ``src`` and ``alt``. - Key attributes for images are `id`, `src` and `alt`. See - `introduction` for details about locating elements. + See `Page Should Contain Element` for explanation about ``message`` + and ``loglevel`` arguments. """ self.assert_page_contains(locator, 'image', message, loglevel) @keyword - def page_should_not_contain_image(self, locator, message='', loglevel='INFO'): - """Verifies image identified by `locator` is found from current page. + def page_should_not_contain_image(self, locator, message=None, loglevel='INFO'): + """Verifies image identified by ``locator`` is found from current page. - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. + See the `Locating elements` section for details about the locator + syntax. Key attributes for images are ``id``, ``src`` and ``alt``. - Key attributes for images are `id`, `src` and `alt`. See - `introduction` for details about locating elements. + See `Page Should Contain Element` for explanation about ``message`` + and ``loglevel`` arguments. """ self.assert_page_not_contains(locator, 'image', message, loglevel) @keyword def get_matching_xpath_count(self, xpath, return_str=True): - """Returns number of elements matching `xpath` + """Returns number of elements matching ``xpath``. - The default return type is `str` but it can changed to `int` by setting - the ``return_str`` argument to Python False. + The default return type is string, but it can changed to an integer + by setting the ``return_str`` argument to a false value. - One should not use the xpath= prefix for 'xpath'. XPath is assumed. + The ``xpath`` should not contain ``xpath:`` prefix. - Correct: - | count = | Get Matching Xpath Count | //div[@id='sales-pop'] - Incorrect: - | count = | Get Matching Xpath Count | xpath=//div[@id='sales-pop'] - - If you wish to assert the number of matching elements, use - `Xpath Should Match X Times`. + Example: + | count = | `Get Matching Xpath Count` | //div[@id='sales-pop'] | """ - count = len(self.find_element("xpath=" + xpath, first_only=False, + count = len(self.find_element("xpath:" + xpath, first_only=False, required=False)) return str(count) if is_truthy(return_str) else count @keyword - def xpath_should_match_x_times(self, xpath, expected_xpath_count, message='', loglevel='INFO'): - """Verifies that the page contains the given number of elements located by the given `xpath`. - - One should not use the xpath= prefix for 'xpath'. XPath is assumed. - - Correct: - | Xpath Should Match X Times | //div[@id='sales-pop'] | 1 - Incorrect: - | Xpath Should Match X Times | xpath=//div[@id='sales-pop'] | 1 - - See `Page Should Contain Element` for explanation about `message` and - `loglevel` arguments. - """ - actual_xpath_count = len(self.find_element( - "xpath=" + xpath, first_only=False, required=False)) - if int(actual_xpath_count) != int(expected_xpath_count): - if is_falsy(message): - message = ("Xpath %s should have matched %s times but " - "matched %s times" - % (xpath, expected_xpath_count, actual_xpath_count)) - self.ctx.log_source(loglevel) - raise AssertionError(message) - self.info("Current page contains %s elements matching '%s'." - % (actual_xpath_count, xpath)) + def xpath_should_match_x_times(self, xpath, x, message=None, loglevel='INFO'): + """Deprecated. Use `Locator Should Match X Times` instead.""" + self.locator_should_match_x_times('xpath:'+xpath, x, message, loglevel) @keyword def add_location_strategy(self, strategy_name, strategy_keyword, persist=False): - """Adds a custom location strategy based on a keyword. - - Location strategies are automatically removed after leaving the current - scope by default. Setting `persist` to Python True will cause the - location strategy to stay registered throughout the life of the test. + """Adds a custom location strategy. - Trying to add a custom location strategy with the same name as one that - already exists will cause the keyword to fail. + See `Custom locators` for information how to create and use + custom strategies. `Remove Location Strategy` can be used to + remove a registered strategy. - Custom locator keyword example: - | Custom Locator Strategy | - | | [Arguments] | ${browser} | ${criteria} | ${tag} | ${constraints} | - | | ${retVal}= | Execute Javascript | return window.document.getElementById('${criteria}'); | - | | [Return] | ${retVal} | - - Usage example: - | Add Location Strategy | custom | Custom Locator Strategy | - | Page Should Contain Element | custom=my_id | - - See `Remove Location Strategy` for details about removing a custom - location strategy. + Location strategies are automatically removed after leaving the + current scope by default. Setting ``persist`` to a true value (see + `Boolean arguments`) will cause the location strategy to stay + registered throughout the life of the test. """ self.element_finder.register(strategy_name, strategy_keyword, persist) @@ -801,9 +773,8 @@ def add_location_strategy(self, strategy_name, strategy_keyword, persist=False): def remove_location_strategy(self, strategy_name): """Removes a previously added custom location strategy. - Will fail if a default strategy is specified. - - See `Add Location Strategy` for details about adding a custom location strategy. + See `Custom locators` for information how to create and use + custom strategies. """ self.element_finder.unregister(strategy_name) @@ -834,7 +805,7 @@ def _is_enabled(self, locator): return True def is_text_present(self, text): - locator = "xpath=//*[contains(., %s)]" % escape_xpath_value(text) + locator = "xpath://*[contains(., %s)]" % escape_xpath_value(text) return self.find_element(locator, required=False) def is_visible(self, locator): @@ -876,24 +847,14 @@ def _map_named_key_code_to_special_key(self, key_name): self.debug(message) raise ValueError(message) - def _parse_attribute_locator(self, attribute_locator): - parts = attribute_locator.rpartition('@') - if len(parts[0]) == 0: - raise ValueError("Attribute locator '%s' does not contain an element locator." % (attribute_locator)) - if len(parts[2]) == 0: - raise ValueError("Attribute locator '%s' does not contain an attribute name." % (attribute_locator)) - return parts[0], parts[2] - def _page_contains(self, text): self.browser.switch_to.default_content() if self.is_text_present(text): return True - subframes = self.find_element("xpath=//frame|//iframe", - first_only=False, - required=False) - self.debug('Current frame has %d subframes' % len(subframes)) + subframes = self.find_elements("xpath://frame|//iframe") + self.debug('Current frame has %d subframes.' % len(subframes)) for frame in subframes: self.browser.switch_to.frame(frame) found_text = self.is_text_present(text) diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index a4a6ece96..922bd85be 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -17,7 +17,7 @@ import os from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.utils import is_falsy +from SeleniumLibrary.utils import is_noney class FormElementKeywords(LibraryComponent): @@ -31,8 +31,8 @@ def submit_form(self, locator=None): details about locating elements. """ self.info("Submitting form '%s'." % locator) - if is_falsy(locator): - locator = 'xpath=//form' + if is_noney(locator): + locator = 'tag:form' element = self.find_element(locator, tag='form') element.submit() @@ -47,7 +47,7 @@ def checkbox_should_be_selected(self, locator): element = self._get_checkbox(locator) if not element.is_selected(): raise AssertionError("Checkbox '%s' should have been selected " - "but was not" % locator) + "but was not." % locator) @keyword def checkbox_should_not_be_selected(self, locator): @@ -59,11 +59,11 @@ def checkbox_should_not_be_selected(self, locator): self.info("Verifying checkbox '%s' is not selected." % locator) element = self._get_checkbox(locator) if element.is_selected(): - raise AssertionError("Checkbox '%s' should not have been selected" - % locator) + raise AssertionError("Checkbox '%s' should not have been " + "selected." % locator) @keyword - def page_should_contain_checkbox(self, locator, message='', loglevel='INFO'): + def page_should_contain_checkbox(self, locator, message=None, loglevel='INFO'): """Verifies checkbox identified by `locator` is found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -75,7 +75,7 @@ def page_should_contain_checkbox(self, locator, message='', loglevel='INFO'): self.assert_page_contains(locator, 'checkbox', message, loglevel) @keyword - def page_should_not_contain_checkbox(self, locator, message='', loglevel='INFO'): + def page_should_not_contain_checkbox(self, locator, message=None, loglevel='INFO'): """Verifies checkbox identified by `locator` is not found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -113,7 +113,7 @@ def unselect_checkbox(self, locator): element.click() @keyword - def page_should_contain_radio_button(self, locator, message='', loglevel='INFO'): + def page_should_contain_radio_button(self, locator, message=None, loglevel='INFO'): """Verifies radio button identified by `locator` is found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -125,7 +125,7 @@ def page_should_contain_radio_button(self, locator, message='', loglevel='INFO') self.assert_page_contains(locator, 'radio button', message, loglevel) @keyword - def page_should_not_contain_radio_button(self, locator, message='', loglevel='INFO'): + def page_should_not_contain_radio_button(self, locator, message=None, loglevel='INFO'): """Verifies radio button identified by `locator` is not found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -150,7 +150,7 @@ def radio_button_should_be_set_to(self, group_name, value): actual_value = self._get_value_from_radio_buttons(elements) if actual_value is None or actual_value != value: raise AssertionError("Selection of radio button '%s' should have " - "been '%s' but was '%s'" + "been '%s' but was '%s'." % (group_name, value, actual_value)) @keyword @@ -164,8 +164,8 @@ def radio_button_should_not_be_selected(self, group_name): elements = self._get_radio_buttons(group_name) actual_value = self._get_value_from_radio_buttons(elements) if actual_value is not None: - raise AssertionError("Radio button group '%s' should not have had " - "selection, but '%s' was selected" + raise AssertionError("Radio button group '%s' should not have " + "had selection, but '%s' was selected." % (group_name, actual_value)) @keyword @@ -201,8 +201,8 @@ def choose_file(self, locator, file_path): | Choose File | my_upload_field | /home/user/files/trades.csv | """ if not os.path.isfile(file_path): - raise AssertionError("File '%s' does not exist on the local file system" - % file_path) + raise ValueError("File '%s' does not exist on the local file " + "system." % file_path) self.find_element(locator).send_keys(file_path) @keyword @@ -213,7 +213,7 @@ def input_password(self, locator, text): does not log the given password. See `introduction` for details about locating elements. """ - self.info("Typing password into text field '%s'" % locator) + self.info("Typing password into text field '%s'." % locator) self._input_text_into_text_field(locator, text) @keyword @@ -222,11 +222,11 @@ def input_text(self, locator, text): See `introduction` for details about locating elements. """ - self.info("Typing text '%s' into text field '%s'" % (text, locator)) + self.info("Typing text '%s' into text field '%s'." % (text, locator)) self._input_text_into_text_field(locator, text) @keyword - def page_should_contain_textfield(self, locator, message='', loglevel='INFO'): + def page_should_contain_textfield(self, locator, message=None, loglevel='INFO'): """Verifies text field identified by `locator` is found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -238,7 +238,7 @@ def page_should_contain_textfield(self, locator, message='', loglevel='INFO'): self.assert_page_contains(locator, 'text field', message, loglevel) @keyword - def page_should_not_contain_textfield(self, locator, message='', loglevel='INFO'): + def page_should_not_contain_textfield(self, locator, message=None, loglevel='INFO'): """Verifies text field identified by `locator` is not found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -250,7 +250,7 @@ def page_should_not_contain_textfield(self, locator, message='', loglevel='INFO' self.assert_page_not_contains(locator, 'text field', message, loglevel) @keyword - def textfield_should_contain(self, locator, expected, message=''): + def textfield_should_contain(self, locator, expected, message=None): """Verifies text field identified by `locator` contains text `expected`. `message` can be used to override default error message. @@ -260,14 +260,14 @@ def textfield_should_contain(self, locator, expected, message=''): """ actual = self.element_finder.get_value(locator, 'text field') if expected not in actual: - if is_falsy(message): + if is_noney(message): message = "Text field '%s' should have contained text '%s' "\ - "but it contained '%s'" % (locator, expected, actual) + "but it contained '%s'." % (locator, expected, actual) raise AssertionError(message) self.info("Text field '%s' contains text '%s'." % (locator, expected)) @keyword - def textfield_value_should_be(self, locator, expected, message=''): + def textfield_value_should_be(self, locator, expected, message=None): """Verifies the value in text field identified by `locator` is exactly `expected`. `message` can be used to override default error message. @@ -281,14 +281,14 @@ def textfield_value_should_be(self, locator, expected, message=''): required=False) actual = element.get_attribute('value') if element else None if actual != expected: - if is_falsy(message): + if is_noney(message): message = "Value of text field '%s' should have been '%s' "\ - "but was '%s'" % (locator, expected, actual) + "but was '%s'." % (locator, expected, actual) raise AssertionError(message) self.info("Content of text field '%s' is '%s'." % (locator, expected)) @keyword - def textarea_should_contain(self, locator, expected, message=''): + def textarea_should_contain(self, locator, expected, message=None): """Verifies text area identified by `locator` contains text `expected`. `message` can be used to override default error message. @@ -299,16 +299,17 @@ def textarea_should_contain(self, locator, expected, message=''): actual = self.element_finder.get_value(locator, 'text area') if actual is not None: if expected not in actual: - if is_falsy(message): + if is_noney(message): message = "Text field '%s' should have contained text '%s' "\ - "but it contained '%s'" % (locator, expected, actual) + "but it contained '%s'." % (locator, expected, actual) raise AssertionError(message) else: - raise ValueError("Element locator '" + locator + "' did not match any elements.") + raise ValueError("Element locator '%s' did not match any " + "elements." % locator) self.info("Text area '%s' contains text '%s'." % (locator, expected)) @keyword - def textarea_value_should_be(self, locator, expected, message=''): + def textarea_value_should_be(self, locator, expected, message=None): """Verifies the value in text area identified by `locator` is exactly `expected`. `message` can be used to override default error message. @@ -319,12 +320,13 @@ def textarea_value_should_be(self, locator, expected, message=''): actual = self.element_finder.get_value(locator, 'text area') if actual is not None: if expected != actual: - if is_falsy(message): + if is_noney(message): message = "Text field '%s' should have contained text '%s' "\ - "but it contained '%s'" % (locator, expected, actual) + "but it contained '%s'." % (locator, expected, actual) raise AssertionError(message) else: - raise ValueError("Element locator '" + locator + "' did not match any elements.") + raise ValueError("Element locator '%s' did not match any " + "elements." % locator) self.info("Content of text area '%s' is '%s'." % (locator, expected)) @keyword @@ -341,7 +343,7 @@ def click_button(self, locator): element.click() @keyword - def page_should_contain_button(self, locator, message='', loglevel='INFO'): + def page_should_contain_button(self, locator, message=None, loglevel='INFO'): """Verifies button identified by `locator` is found from current page. This keyword searches for buttons created with either `input` or `button` tag. @@ -358,7 +360,7 @@ def page_should_contain_button(self, locator, message='', loglevel='INFO'): self.assert_page_contains(locator, 'button', message, loglevel) @keyword - def page_should_not_contain_button(self, locator, message='', loglevel='INFO'): + def page_should_not_contain_button(self, locator, message=None, loglevel='INFO'): """Verifies button identified by `locator` is not found from current page. This keyword searches for buttons created with either `input` or `button` tag. diff --git a/src/SeleniumLibrary/keywords/selectelement.py b/src/SeleniumLibrary/keywords/selectelement.py index 18c8df726..994a9e7bf 100644 --- a/src/SeleniumLibrary/keywords/selectelement.py +++ b/src/SeleniumLibrary/keywords/selectelement.py @@ -23,9 +23,6 @@ class SelectElementKeywords(LibraryComponent): - def __init__(self, ctx): - LibraryComponent.__init__(self, ctx) - @keyword def get_list_items(self, locator, value=False): """Returns the labels or values in the select list identified by `locator`. @@ -68,8 +65,9 @@ def get_selected_list_labels(self, locator): locating elements. """ select, options = self._get_select_list_options_selected(locator) - if len(options) == 0: - raise ValueError("Select list with locator '%s' does not have any selected values") + if not options: + raise ValueError("List '%s' does not have any selected values." + % locator) return self._get_labels_for_options(options) @keyword @@ -96,7 +94,8 @@ def get_selected_list_values(self, locator): locating elements. """ select, options = self._get_select_list_options_selected(locator) - if len(options) == 0: + # TODO: Should return an empty list, not fail + if not options: raise ValueError("Select list with locator '%s' does not have any selected values") return self._get_values_for_options(options) @@ -145,7 +144,7 @@ def list_should_have_no_selections(self, locator): "(selection was [ %s ])" % (locator, items_str)) @keyword - def page_should_contain_list(self, locator, message='', loglevel='INFO'): + def page_should_contain_list(self, locator, message=None, loglevel='INFO'): """Verifies select list identified by `locator` is found from current page. See `Page Should Contain Element` for explanation about `message` and @@ -157,7 +156,7 @@ def page_should_contain_list(self, locator, message='', loglevel='INFO'): self.assert_page_contains(locator, 'list', message, loglevel) @keyword - def page_should_not_contain_list(self, locator, message='', loglevel='INFO'): + def page_should_not_contain_list(self, locator, message=None, loglevel='INFO'): """Verifies select list identified by `locator` is not found from current page. See `Page Should Contain Element` for explanation about `message` and diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py index 801ad158a..dd9edb87b 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -19,7 +19,8 @@ from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import ContextAware -from SeleniumLibrary.utils import escape_xpath_value, events, is_falsy +from SeleniumLibrary.utils import (escape_xpath_value, events, is_falsy, + is_noney) from .customlocator import CustomLocator @@ -81,8 +82,8 @@ def find(self, locator, tag=None, first_only=True, required=True, def assert_page_contains(self, locator, tag=None, message=None, loglevel='INFO'): if not self.find(locator, tag, required=False): - if is_falsy(message): - message = ("Page should have contained %s '%s' but did not" + if is_noney(message): + message = ("Page should have contained %s '%s' but did not." % (tag or 'element', locator)) self.ctx.log_source(loglevel) # TODO: Could this moved to base raise AssertionError(message) @@ -92,8 +93,8 @@ def assert_page_contains(self, locator, tag=None, message=None, def assert_page_not_contains(self, locator, tag=None, message=None, loglevel='INFO'): if self.find(locator, tag, required=False): - if is_falsy(message): - message = ("Page should not have contained %s '%s'" + if is_noney(message): + message = ("Page should not have contained %s '%s'." % (tag or 'element', locator)) self.ctx.log_source(loglevel) # TODO: Could this moved to base raise AssertionError(message) diff --git a/src/SeleniumLibrary/utils/__init__.py b/src/SeleniumLibrary/utils/__init__.py index 377f9d42d..d27eddecd 100644 --- a/src/SeleniumLibrary/utils/__init__.py +++ b/src/SeleniumLibrary/utils/__init__.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from robot.utils import secs_to_timestr, timestr_to_secs +from robot.utils import plural_or_not, secs_to_timestr, timestr_to_secs from .browsercache import BrowserCache from .deprecated import Deprecated diff --git a/test/acceptance/keywords/checkbox_and_radio_buttons.robot b/test/acceptance/keywords/checkbox_and_radio_buttons.robot index 770e694b2..7ce2218bd 100644 --- a/test/acceptance/keywords/checkbox_and_radio_buttons.robot +++ b/test/acceptance/keywords/checkbox_and_radio_buttons.robot @@ -7,13 +7,15 @@ Resource ../resource.robot Checkbox Should Be Selected [Documentation] LOG 2 Verifying checkbox 'can_send_email' is selected. Checkbox Should Be Selected can_send_email - Run Keyword And Expect Error Checkbox 'can_send_sms' should have been selected but was not + Run Keyword And Expect Error + ... Checkbox 'can_send_sms' should have been selected but was not. ... Checkbox Should Be Selected can_send_sms Checkbox Should Not Be Selected [Documentation] LOG 2 Verifying checkbox 'can_send_sms' is not selected. Checkbox Should Not Be Selected can_send_sms - Run Keyword And Expect Error Checkbox 'can_send_email' should not have been selected + Run Keyword And Expect Error + ... Checkbox 'can_send_email' should not have been selected. ... Checkbox Should Not Be Selected can_send_email Select Checkbox @@ -34,7 +36,7 @@ Radio Button Should Be Set To [Documentation] LOG 2 Verifying radio button 'sex' has selection 'female'. Radio Button Should Be Set To sex female Run Keyword And Expect Error - ... Selection of radio button 'sex' should have been 'male' but was 'female' + ... Selection of radio button 'sex' should have been 'male' but was 'female'. ... Radio Button Should Be Set To sex male Select Radio Button @@ -48,7 +50,7 @@ Radio Button Should Not Be Selected [Documentation] LOG 2 Verifying radio button 'referrer' has no selection. Radio Button Should Not Be Selected referrer Run Keyword And Expect Error - ... Radio button group 'sex' should not have had selection, but 'female' was selected + ... Radio button group 'sex' should not have had selection, but 'female' was selected. ... Radio Button Should Not Be Selected sex Clicking Radio Button Should Trigger Onclick Event diff --git a/test/acceptance/keywords/click_element_at_coordinates.robot b/test/acceptance/keywords/click_element_at_coordinates.robot index 44231d8f9..86c89b452 100644 --- a/test/acceptance/keywords/click_element_at_coordinates.robot +++ b/test/acceptance/keywords/click_element_at_coordinates.robot @@ -6,7 +6,7 @@ Resource ../resource.robot *** Test Cases *** Click Element At Coordinates - [Documentation] LOG 2 Click clicking element 'Clickable' in coordinates '10', '20'. + [Documentation] LOG 2 Clicking element 'Clickable' at coordinates x=10, y=20. [Tags] Known Issue Internet Explorer Known Issue Safari Click Element At Coordinates Clickable ${10} ${20} Element Text Should Be outputX 110 diff --git a/test/acceptance/keywords/content_assertions.robot b/test/acceptance/keywords/content_assertions.robot index 9103c95f7..822940692 100644 --- a/test/acceptance/keywords/content_assertions.robot +++ b/test/acceptance/keywords/content_assertions.robot @@ -8,12 +8,16 @@ Resource ../resource.robot Location Should Be [Documentation] LOG 2:3 Current location is '${FRONT PAGE}'. Location Should Be ${FRONT PAGE} - Run Keyword And Expect Error Location should have been 'non existing' but was '${FRONT PAGE}' Location Should Be non existing + Run Keyword And Expect Error + ... Location should have been 'non existing' but was '${FRONT PAGE}'. + ... Location Should Be non existing Location Should Contain [Documentation] LOG 2:3 Current location contains 'html'. Location Should Contain html - Run Keyword And Expect Error Location should have contained 'not a location' but it was '${FRONT PAGE}'. Location Should Contain not a location + Run Keyword And Expect Error + ... Location should have contained 'not a location' but it was '${FRONT PAGE}'. + ... Location Should Contain not a location Title Should Be [Documentation] LOG 2:3 Page title is '(root)/index.html'. @@ -27,16 +31,22 @@ Page Should Contain ... LOG 4.1:10 REGEXP: (?i) Page Should Contain needle Page Should Contain This is the haystack - Run Keyword And Expect Error Page should have contained text 'non existing text' but did not Page Should Contain non existing text + Run Keyword And Expect Error + ... Page should have contained text 'non existing text' but did not. + ... Page Should Contain non existing text Page Should Contain With Custom Log Level [Documentation] LOG 2.1:10 DEBUG REGEXP: (?i) - Run Keyword And Expect Error Page should have contained text 'non existing text' but did not Page Should Contain non existing text DEBUG + Run Keyword And Expect Error + ... Page should have contained text 'non existing text' but did not. + ... Page Should Contain non existing text DEBUG Page Should Contain With Disabling Source Logging [Documentation] LOG 3:2 NONE Set Log Level INFO - Run Keyword And Expect Error Page should have contained text 'non existing text' but did not Page Should Contain non existing text loglevel=NONE + Run Keyword And Expect Error + ... Page should have contained text 'non existing text' but did not. + ... Page Should Contain non existing text loglevel=NONE [Teardown] Set Log Level DEBUG Page Should Contain With Frames @@ -48,160 +58,201 @@ Page Should Not Contain [Documentation] LOG 2:8 Current page does not contain text 'non existing text'. ... LOG 3.1:7 REGEXP: (?i) Page Should Not Contain non existing text - Run Keyword And Expect Error Page should not have contained text 'needle' Page Should Not Contain needle + Run Keyword And Expect Error + ... Page should not have contained text 'needle'. + ... Page Should Not Contain needle Page Should Not Contain With Custom Log Level [Documentation] LOG 2.1:7 DEBUG REGEXP: (?i) - Run Keyword And Expect Error Page should not have contained text 'needle' Page Should Not Contain needle DEBUG + Run Keyword And Expect Error + ... Page should not have contained text 'needle'. + ... Page Should Not Contain needle DEBUG Page Should Not Contain With Disabling Source Logging [Documentation] LOG 3:2 NONE Set Log Level INFO - Run Keyword And Expect Error Page should not have contained text 'needle' Page Should Not Contain needle loglevel=NONE + Run Keyword And Expect Error + ... Page should not have contained text 'needle'. + ... Page Should Not Contain needle loglevel=NONE [Teardown] Set Log Level DEBUG Page Should Contain Element - [Documentation] Page Should Contain Element Page Should Contain Element some_id - Run Keyword And Expect Error Page should have contained element 'non-existent' but did not Page Should Contain Element non-existent + Run Keyword And Expect Error + ... Page should have contained element 'non-existent' but did not. + ... Page Should Contain Element non-existent Page Should Contain Element With Custom Message - [Documentation] Page Should Contain Element With Custom Message - Run Keyword And Expect Error Custom error message Page Should Contain Element invalid Custom error message + Run Keyword And Expect Error + ... Custom error message + ... Page Should Contain Element invalid Custom error message Page Should Contain Element With Disabling Source Logging [Documentation] LOG 3:2 NONE Set Log Level INFO - Run Keyword And Expect Error Page should have contained element 'non-existent' but did not Page Should Contain Element non-existent loglevel=NONE + Run Keyword And Expect Error + ... Page should have contained element 'non-existent' but did not. + ... Page Should Contain Element non-existent loglevel=NONE [Teardown] Set Log Level DEBUG Page Should Not Contain Element - [Documentation] Page Should Not Contain Element Page Should Not Contain Element non-existent - Run Keyword And Expect Error Page should not have contained element 'some_id' Page Should Not Contain Element some_id + Run Keyword And Expect Error + ... Page should not have contained element 'some_id'. + ... Page Should Not Contain Element some_id Page Should Not Contain Element With Disabling Source Logging [Documentation] LOG 3:2 NONE Set Log Level INFO - Run Keyword And Expect Error Page should not have contained element 'some_id' Page Should Not Contain Element some_id loglevel=NONE + Run Keyword And Expect Error + ... Page should not have contained element 'some_id'. + ... Page Should Not Contain Element some_id loglevel=NONE [Teardown] Set Log Level DEBUG Element Should Contain - [Documentation] Element Should Contain Element Should Contain some_id This text is inside an identified element - Run Keyword And Expect Error Element 'some_id' should have contained text 'non existing text' but its text was 'This text is inside an identified element'. Element Should Contain some_id non existing text - Run Keyword And Expect Error ValueError: Element locator 'missing_id' did not match any elements. Element Should Contain missing_id This should report missing element. + Run Keyword And Expect Error + ... Element 'some_id' should have contained text 'non existing text' but its text was 'This text is inside an identified element'. + ... Element Should Contain some_id non existing text + Run Keyword And Expect Error + ... ValueError: Element locator 'missing_id' did not match any elements. + ... Element Should Contain missing_id This should report missing element. Element Should Not Contain - [Documentation] Element Should Not Contain Element Should Not Contain some_id This text is not inside an identified element Element Should Not Contain some_id elementypo - Run Keyword And Expect Error Element 'some_id' should not contain text 'This text is inside an identified element' but it did. Element Should Not Contain some_id This text is inside an identified element - Run Keyword And Expect Error ValueError: Element locator 'missing_id' did not match any elements. Element Should Not Contain missing_id This should report missing element. + Run Keyword And Expect Error + ... Element 'some_id' should not contain text 'This text is inside an identified element' but it did. + ... Element Should Not Contain some_id This text is inside an identified element + Run Keyword And Expect Error + ... ValueError: Element locator 'missing_id' did not match any elements. + ... Element Should Not Contain missing_id This should report missing element. Element Text Should Be - [Documentation] Element Text Should Be Element Text Should Be some_id This text is inside an identified element - Run Keyword And Expect Error The text of element 'some_id' should have been 'inside' but in fact it was 'This text is inside an identified element'. Element Text Should Be some_id inside + Run Keyword And Expect Error + ... The text of element 'some_id' should have been 'inside' but it was 'This text is inside an identified element'. + ... Element Text Should Be some_id inside Get Text - [Documentation] Get Text ${str} = Get Text some_id Should Match ${str} This text is inside an identified element - Run Keyword And Expect Error ValueError: Element locator 'missing_id' did not match any elements. Get Text missing_id + Run Keyword And Expect Error + ... ValueError: Element locator 'missing_id' did not match any elements. + ... Get Text missing_id Element Should Be Visible - [Documentation] Element Should Be Visible [Setup] Go To Page "visibility.html" Element Should Be Visible i_am_visible - Run Keyword And Expect Error The element 'i_am_hidden' should be visible, but it is not. Element Should Be Visible i_am_hidden + Run Keyword And Expect Error + ... The element 'i_am_hidden' should be visible, but it is not. + ... Element Should Be Visible i_am_hidden Element Should Not Be Visible - [Documentation] Element Should Not Be Visible [Setup] Go To Page "visibility.html" Element Should Not Be Visible i_am_hidden - Run Keyword And Expect Error The element 'i_am_visible' should not be visible, but it is. Element Should Not Be Visible i_am_visible + Run Keyword And Expect Error + ... The element 'i_am_visible' should not be visible, but it is. + ... Element Should Not Be Visible i_am_visible Page Should Contain Checkbox [Documentation] LOG 2:5 Current page contains checkbox 'can_send_email'. [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Contain Checkbox can_send_email Page Should Contain Checkbox xpath=//input[@type='checkbox' and @name='can_send_sms'] - Run Keyword And Expect Error Page should have contained checkbox 'non-existing' but did not Page Should Contain Checkbox non-existing + Run Keyword And Expect Error + ... Page should have contained checkbox 'non-existing' but did not. + ... Page Should Contain Checkbox non-existing Page Should Not Contain Checkbox [Documentation] LOG 2:5 Current page does not contain checkbox 'non-existing'. [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Not Contain Checkbox non-existing - Run Keyword And Expect Error Page should not have contained checkbox 'can_send_email' Page Should Not Contain Checkbox can_send_email + Run Keyword And Expect Error + ... Page should not have contained checkbox 'can_send_email'. + ... Page Should Not Contain Checkbox can_send_email Page Should Contain Radio Button - [Documentation] Page Should Contain Radio Button [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Contain Radio Button sex Page Should Contain Radio Button xpath=//input[@type="radio" and @value="male"] - Run Keyword And Expect Error Page should have contained radio button 'non-existing' but did not Page Should Contain Radio Button non-existing + Run Keyword And Expect Error + ... Page should have contained radio button 'non-existing' but did not. + ... Page Should Contain Radio Button non-existing Page Should Not Contain Radio Button - [Documentation] Page Should Not Contain Radio Button [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Not Contain Radio Button non-existing - Run Keyword And Expect Error Page should not have contained radio button 'sex' Page Should Not Contain Radio Button sex + Run Keyword And Expect Error + ... Page should not have contained radio button 'sex'. + ... Page Should Not Contain Radio Button sex Page Should Contain Image - [Documentation] Page Should Contain Image [Setup] Go To Page "links.html" Page Should contain Image image.jpg - Run Keyword And Expect Error Page should have contained image 'non-existent' but did not Page Should contain Image non-existent + Run Keyword And Expect Error + ... Page should have contained image 'non-existent' but did not. + ... Page Should contain Image non-existent Page Should Not Contain Image - [Documentation] Page Should Not Contain Image [Setup] Go To Page "links.html" Page Should not contain Image non-existent - Run Keyword And Expect Error Page should not have contained image 'image.jpg' Page Should not contain Image image.jpg + Run Keyword And Expect Error + ... Page should not have contained image 'image.jpg'. + ... Page Should not contain Image image.jpg Page Should Contain Link - [Documentation] Page Should Contain Link [Setup] Go To Page "links.html" Page Should contain link Relative Page Should contain link sub/index.html - Run Keyword And Expect Error Page should have contained link 'non-existent' but did not Page Should contain link non-existent + Run Keyword And Expect Error + ... Page should have contained link 'non-existent' but did not. + ... Page Should contain link non-existent Page Should Not Contain Link - [Documentation] Page Should Not Contain Link [Setup] Go To Page "links.html" Page Should not contain link non-existent - Run Keyword And Expect Error Page should not have contained link 'Relative' Page Should not contain link Relative + Run Keyword And Expect Error + ... Page should not have contained link 'Relative'. + ... Page Should not contain link Relative Page Should Contain List - [Documentation] Page Should Contain List [Setup] Go To Page "forms/prefilled_email_form.html" Page should Contain List possible_channels - Run Keyword And Expect Error Page should have contained list 'non-existing' but did not Page Should Contain List non-existing + Run Keyword And Expect Error + ... Page should have contained list 'non-existing' but did not. + ... Page Should Contain List non-existing Page Should Not Contain List - [Documentation] Page Should Not Contain List [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Not Contain List non-existing - Run Keyword And Expect Error Page should not have contained list 'possible_channels' Page Should Not Contain List possible_channels + Run Keyword And Expect Error + ... Page should not have contained list 'possible_channels'. + ... Page Should Not Contain List possible_channels Page Should Contain TextField - [Documentation] Page Should Contain TextField [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Contain Text Field name Page Should Contain Text Field website Page Should Contain Text Field xpath=//input[@type='text' and @name='email'] Page Should Contain Text Field xpath=//input[@type='url' and @name='website'] - Run Keyword And Expect Error Page should have contained text field 'non-existing' but did not Page Should Contain Text Field non-existing - Run Keyword And Expect Error Page should have contained text field 'can_send_email' but did not Page Should Contain Text Field can_send_email + Run Keyword And Expect Error + ... Page should have contained text field 'non-existing' but did not. + ... Page Should Contain Text Field non-existing + Run Keyword And Expect Error + ... Page should have contained text field 'can_send_email' but did not. + ... Page Should Contain Text Field can_send_email Page Should Not Contain Text Field - [Documentation] Page Should Not Contain Text Field [Setup] Go To Page "forms/prefilled_email_form.html" Page Should Not Contain Text Field non-existing Page Should Not Contain Text Field can_send_email - Run Keyword And Expect Error Page should not have contained text field 'name' Page Should Not Contain Text Field name - Run Keyword And Expect Error Page should not have contained text field 'website' Page Should Not Contain Text Field website + Run Keyword And Expect Error + ... Page should not have contained text field 'name'. + ... Page Should Not Contain Text Field name + Run Keyword And Expect Error + ... Page should not have contained text field 'website'. + ... Page Should Not Contain Text Field website TextField Should Contain [Documentation] LOG 2:7 Text field 'name' contains text ''. @@ -212,8 +263,12 @@ TextField Should Contain Input Text website https://example.org TextField Should contain name my name TextField Should contain website https://example.org - Run Keyword And Expect Error Text field 'name' should have contained text 'non-existing' but it contained 'my name' TextField Should contain name non-existing - Run Keyword And Expect Error Text field 'website' should have contained text 'https://w3.org' but it contained 'https://example.org' TextField Should contain website https://w3.org + Run Keyword And Expect Error + ... Text field 'name' should have contained text 'non-existing' but it contained 'my name'. + ... TextField Should contain name non-existing + Run Keyword And Expect Error + ... Text field 'website' should have contained text 'https://w3.org' but it contained 'https://example.org'. + ... TextField Should contain website https://w3.org TextField Value Should Be [Documentation] LOG 2:7 Content of text field 'name' is ''. @@ -221,28 +276,31 @@ TextField Value Should Be textfield Value Should Be name ${EMPTY} Input Text name my name textfield Value Should Be name my name - Run Keyword And Expect Error Value of text field 'name' should have been 'non-existing' but was 'my name' textfield Value Should Be name non-existing + Run Keyword And Expect Error + ... Value of text field 'name' should have been 'non-existing' but was 'my name'. + ... textfield Value Should Be name non-existing Clear Element Text name Textfield Value Should Be name ${EMPTY} TextArea Should Contain - [Documentation] TextArea Should Contain [Setup] Go To Page "forms/email_form.html" TextArea Should Contain comment ${EMPTY} Input Text comment This is a comment. - Run Keyword And Expect Error Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.' TextArea Should Contain comment Hello World! + Run Keyword And Expect Error + ... Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.'. + ... TextArea Should Contain comment Hello World! TextArea Value Should Be - [Documentation] TextArea Value Should Be [Setup] Go To Page "forms/email_form.html" TextArea Value Should Be comment ${EMPTY} Input Text comment This is a comment. - Run Keyword And Expect Error Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.' TextArea Value Should Be comment Hello World! + Run Keyword And Expect Error + ... Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.'. + ... TextArea Value Should Be comment Hello World! Clear Element Text comment TextArea Value Should Be comment ${EMPTY} Page Should Contain Button - [Documentation] Page Should Contain Button [Setup] Go To Page "forms/buttons.html" Page Should Contain Button button Page Should Contain Button Sisään @@ -252,36 +310,46 @@ Page Should Contain Button Page Should Contain Button xpath=//input[@type="submit"] Page Should Contain Button Act! Page Should Contain Button xpath=//input[@type="button"] - Run Keyword And Expect Error Page should have contained button 'non-existing' but did not Page Should Contain Button non-existing + Run Keyword And Expect Error + ... Page should have contained button 'non-existing' but did not. + ... Page Should Contain Button non-existing Page Should Not Contain Button In Button Tag - [Documentation] Page Should Not Contain Button In Button Tag [Setup] Go To Page "forms/buttons.html" Page Should Not Contain Button invalid - Run Keyword And Expect Error Page should not have contained button 'button' Page Should Not Contain Button button + Run Keyword And Expect Error + ... Page should not have contained button 'button'. + ... Page Should Not Contain Button button Page Should Not Contain Button In Input Tag - [Documentation] Page Should Not Contain Button In Input Tag [Setup] Go To Page "forms/buttons.html" Page Should Not Contain Button invalid - Run Keyword And Expect Error Page should not have contained input 'Act!' Page Should Not Contain Button Act! + Run Keyword And Expect Error + ... Page should not have contained input 'Act!'. + ... Page Should Not Contain Button Act! Get All Links - [Documentation] Get All Links [Setup] Go To Page "links.html" ${links}= Get All Links Length Should Be ${links} 19 List Should Contain Value ${links} bold_id Xpath Should Match X Times - [Documentation] Xpath Should Match X Times [Setup] Go To Page "forms/login.html" Xpath Should Match X Times //input[@type="text"] 1 Xpath Should Match X Times //input[@type="text"] ${1} - Run Keyword And Expect Error Xpath //input[@type="text"] should have matched 2 times but matched 1 times Xpath Should Match X Times //input[@type="text"] 2 + Run Keyword And Expect Error + ... Locator 'xpath://input[@type="text"]' should have matched 2 times but matched 1 time. + ... Xpath Should Match X Times //input[@type="text"] 2 Locator Should Match X Times - [Documentation] Locator Should Match X Times - [Setup] Go To Page "links.html" - Locator Should Match X Times link=Link 2 - Locator Should Match X Times link=Missing Link 0 + [Setup] Go To Page "forms/login.html" + Locator Should Match X Times //input[@type="text"] 1 + Locator Should Match X Times css:input ${3} + Locator Should Match X Times link:Missing Link 0 + Run Keyword And Expect Error + ... Locator '//input[@type="text"]' should have matched 2 times but matched 1 time. + ... Locator Should Match X Times //input[@type="text"] 2 + Run Keyword And Expect Error + ... Locator 'css:input' should have matched 1 time but matched 3 times. + ... Locator Should Match X Times css:input 1 diff --git a/test/acceptance/keywords/element_focus.robot b/test/acceptance/keywords/element_focus.robot index 70ca2e8b2..df74603bc 100644 --- a/test/acceptance/keywords/element_focus.robot +++ b/test/acceptance/keywords/element_focus.robot @@ -14,50 +14,66 @@ Should Not Be Focused [Documentation] Verify that element is not Focused [Setup] Go To Page "mouse/index.html" Click Element el_for_focus - Run Keyword And Expect Error Element 'el_for_blur' is not with focus. Element Should Be Focused el_for_blur + Run Keyword And Expect Error + ... Element 'el_for_blur' does not have focus. + ... Element Should Be Focused el_for_blur Element Should Be Focused el_for_focus Unexistent Element Not Focused [Documentation] Missing element returns locator error [Setup] Go To Page "mouse/index.html" Click Element el_for_focus - Run Keyword And Expect Error ValueError: Element locator 'Unexistent_element' did not match any elements. Element Should Be Focused Unexistent_element + Run Keyword And Expect Error + ... ValueError: Element locator 'Unexistent_element' did not match any elements. + ... Element Should Be Focused Unexistent_element Element Should Be Focused el_for_focus Span Element Not Focused [Documentation] Focus on not Focusable Span [Setup] Go To Page "/" Click Element some_id - Run Keyword And Expect Error Element 'some_id' is not with focus. Element Should Be Focused some_id + Run Keyword And Expect Error + ... Element 'some_id' does not have focus. + ... Element Should Be Focused some_id Table Element Not Focused [Documentation] Focus on not Focusable Table [Setup] Go To Page "tables/tables.html" Click Element simpleTable - Run Keyword And Expect Error Element 'simpleTable' is not with focus. Element Should Be Focused simpleTable + Run Keyword And Expect Error + ... Element 'simpleTable' does not have focus. + ... Element Should Be Focused simpleTable Radio Button Should Be Focused [Documentation] Radio Button with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Click Element xpath=//input[@name='sex' and @value='male'] Element Should Be Focused xpath=//input[@name='sex' and @value='male'] - Run Keyword And Expect Error Element 'xpath=//input[@name=\'sex\' and @value=\'female\']' is not with focus. Element Should Be Focused xpath=//input[@name='sex' and @value='female'] + Run Keyword And Expect Error + ... Element 'xpath=//input[@name=\'sex\' and @value=\'female\']' does not have focus. + ... Element Should Be Focused xpath=//input[@name='sex' and @value='female'] Checkbox Should Be Focused [Documentation] Checkbox with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Click Element xpath=//input[@name='can_send_sms'] Element Should Be Focused xpath=//input[@name='can_send_sms'] - Run Keyword And Expect Error Element 'xpath=//input[@name=\'can_send_email\']' is not with focus. Element Should Be Focused xpath=//input[@name='can_send_email'] + Run Keyword And Expect Error + ... Element 'xpath=//input[@name=\'can_send_email\']' does not have focus. + ... Element Should Be Focused xpath=//input[@name='can_send_email'] Select Button Should Be Focused [Documentation] Select Button with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Mouse Down xpath=//select[@name='preferred_channel'] Element Should Be Focused xpath=//select[@name='preferred_channel'] - Run Keyword And Expect Error Element 'xpath=//select[@name=\'preferred_channel\']/option[@value=\'phone\']' is not with focus. Element Should Be Focused xpath=//select[@name='preferred_channel']/option[@value='phone'] + Run Keyword And Expect Error + ... Element 'xpath=//select[@name=\'preferred_channel\']/option[@value=\'phone\']' does not have focus. + ... Element Should Be Focused xpath=//select[@name='preferred_channel']/option[@value='phone'] Click Element xpath=//option[@value='email'] - Run Keyword And Expect Error Element 'xpath=//option[@value=\'email\']' is not with focus. Element Should Be Focused xpath=//option[@value='email'] + Run Keyword And Expect Error + ... Element 'xpath=//option[@value=\'email\']' does not have focus. + ... Element Should Be Focused xpath=//option[@value='email'] Submit Button Should Be Focused [Setup] Go To Page "forms/prefilled_email_form.html" diff --git a/test/acceptance/keywords/elements.robot b/test/acceptance/keywords/elements.robot index d36927418..9eac7e5cb 100644 --- a/test/acceptance/keywords/elements.robot +++ b/test/acceptance/keywords/elements.robot @@ -69,23 +69,27 @@ Get Matching XPath Count Get Horizontal Position ${pos}= Get Horizontal Position link=Link - Should Be True ${pos} > ${0} - Run Keyword And Expect Error Could not determine position for 'non-existent' + Should Be True ${pos} > 0 + Run Keyword And Expect Error + ... ValueError: Element locator 'non-existent' did not match any elements. ... Get Horizontal Position non-existent Get Vertical Position ${pos}= Get Vertical Position link=Link - Should Be True ${pos} > ${0} - Run Keyword And Expect Error Could not determine position for 'non-existent' + Should Be True ${pos} > 0 + Run Keyword And Expect Error + ... ValueError: Element locator 'non-existent' did not match any elements. ... Get Horizontal Position non-existent Get Element Size ${width} ${height}= Get Element Size link=Link - Should be True ${height} > ${0} - Should be True ${width} > ${0} - Run Keyword And Expect Error ValueError: Element locator 'non-existent' did not match any elements. Get Element Size non-existent + Should be True ${height} > 0 + Should be True ${width} > 0 + Run Keyword And Expect Error + ... ValueError: Element locator 'non-existent' did not match any elements. + ... Get Element Size non-existent Get Empty Element Size [Tags] Known Issue Internet Explorer ${width} ${height}= Get Element Size id=emptyDiv - Should be True ${height} == 0 + Should be Equal ${height} ${0} diff --git a/test/acceptance/keywords/lists.robot b/test/acceptance/keywords/lists.robot index 694fe33f1..be2f9fe8a 100644 --- a/test/acceptance/keywords/lists.robot +++ b/test/acceptance/keywords/lists.robot @@ -6,42 +6,35 @@ Force Tags Known Issue Internet Explorer *** Test Cases *** Get List Items From Single-Select List - [Documentation] Get List Items From Single-Select List ${items}= Get List Items preferred_channel ${expected}= Create List Email Telephone Direct mail Should Be Equal ${items} ${expected} Get List Items From Multi-Select List - [Documentation] Get List Items From Multi-Select List ${items}= Get List Items interests ${expected}= Create List Males Females Others Should Be Equal ${items} ${expected} Get List Values From Single-Select List - [Documentation] Get List Values From Single-Select List ${values}= Get List Items preferred_channel value=${True} ${expected}= Create List email phone directmail Should Be Equal ${values} ${expected} Get List Values From Multi-Select List - [Documentation] Get List Values From Multi-Select List ${values}= Get List Items interests value=True ${expected}= Create List males females others Should Be Equal ${values} ${expected} Get Selected List Value - [Documentation] Get Selected List Value ${selected}= Get Selected List Value preferred_channel Should Be Equal ${selected} phone Get Selected List Values - [Documentation] Get Selected List Values ${selected}= Get Selected List Values preferred_channel ${expected}= Create List phone Should Be Equal ${selected} ${expected} Get Selected List Label - [Documentation] Get Selected List Label ${selected}= Get Selected List Label preferred_channel Should Be Equal ${selected} Telephone Select From List interests Males @@ -66,23 +59,23 @@ List Selection Should Be ... List Selection Should Be possible_channels email Telephone Direct mail List Selection Should Be When Extraneous Options Are Selected - [Documentation] List Selection Should Be When Extraneous Options Are Selected Run Keyword And Expect Error ... List 'possible_channels' should have had selection [ email ] but it was [ Email | Telephone ] ... List Selection Should Be possible_channels email List Selection Should Be When List Does Not Exist - [Documentation] List Selection Should Be When List Does Not Exist - Run Keyword And Expect Error Page should have contained list 'nonexisting' but did not + Run Keyword And Expect Error + ... Page should have contained list 'nonexisting' but did not. ... List Selection Should Be nonexisting whatever UnSelect Single Value From List [Documentation] LOG 2.1 Unselecting option(s) 'Email' from list 'possible_channels'. Unselect and Verify Selection possible_channels Email phone - Comment unselecting already unselected option has no effect + # Unselecting already unselected option has no effect Unselect and Verify Selection possible_channels Email phone Unselect And Verify Selection possible_channels Telephone - Run Keyword And Expect Error Keyword 'Unselect from list' works only for multiselect lists. + Run Keyword And Expect Error + ... Keyword 'Unselect from list' works only for multiselect lists. ... Unselect From List preferred_channel UnSelect All From List @@ -104,8 +97,6 @@ Select From Single Selection List List Selection Should Be preferred_channel Direct mail Select Non-Existing Item From Single Selection List - [Documentation] Select Non-Existing Item From Single Selection List - [Tags] OnlyThisOne Run Keyword And Expect Error ... ValueError: Option 'Smoke Signals' not in list 'preferred_channel'. ... Select From List preferred_channel Tin Can Phone Smoke Signals @@ -118,8 +109,6 @@ Select Non-Existing Item From Single Selection List ... Select From List By Label preferred_channel Tin Can Phone Select Non-Existing Item From Multi-Selection List - [Documentation] Select Non-Existing Item From Multi-Selection List - [Tags] OnlyThisOne Run Keyword And Expect Error ... ValueError: Options 'Tin Can Phone, Smoke Signals' not in list 'possible_channels'. ... Select From List possible_channels Tin Can Phone Smoke Signals @@ -132,7 +121,6 @@ Select Non-Existing Item From Multi-Selection List Unselect Non-Existing Item From List [Documentation] LOG 3 Unselecting option(s) 'Tin Can Phone, Smoke Signals, Email' from list 'possible_channels'. - [Tags] OnlyThisOne Unselect From List possible_channels Tin Can Phone Smoke Signals Unselect From List possible_channels Tin Can Phone Smoke Signals Email diff --git a/test/acceptance/keywords/mouse.robot b/test/acceptance/keywords/mouse.robot index 48d85d65f..5e5b4fbf4 100644 --- a/test/acceptance/keywords/mouse.robot +++ b/test/acceptance/keywords/mouse.robot @@ -9,25 +9,33 @@ Mouse Over [Tags] Known Issue Safari Mouse Over el_for_mouseover Textfield Value Should Be el_for_mouseover mouseover el_for_mouseover - Run Keyword And Expect Error ERROR: Element not_there not found. Mouse Over not_there + Run Keyword And Expect Error + ... ValueError: Element locator 'not_there' did not match any elements. + ... Mouse Over not_there Mouse Out [Tags] Known Issue Safari Mouse Out el_for_mouseout Textfield Value Should Be el_for_mouseout mouseout el_for_mouseout - Run Keyword And Expect Error ERROR: Element not_there not found. Mouse Out not_there + Run Keyword And Expect Error + ... ValueError: Element locator 'not_there' did not match any elements. + ... Mouse Out not_there Mouse Down [Tags] Known Issue Safari Mouse Down el_for_mousedown Textfield Value Should Be el_for_mousedown mousedown el_for_mousedown - Run Keyword And Expect Error ERROR: Element not_there not found. Mouse Down not_there + Run Keyword And Expect Error + ... ValueError: Element locator 'not_there' did not match any elements. + ... Mouse Down not_there Mouse Up [Tags] Known Issue Safari Known Issue Firefox Mouse Up el_for_mouseup Textfield Value Should Be el_for_mouseup mouseup el_for_mouseup - Run Keyword And Expect Error ERROR: Element not_there not found. Mouse Up not_there + Run Keyword And Expect Error + ... ValueError: Element locator 'not_there' did not match any elements. + ... Mouse Up not_there Simulate Event Simulate event el_for_blur blur diff --git a/test/acceptance/keywords/run_on_failure.robot b/test/acceptance/keywords/run_on_failure.robot index 656e93a89..17f30e944 100644 --- a/test/acceptance/keywords/run_on_failure.robot +++ b/test/acceptance/keywords/run_on_failure.robot @@ -1,5 +1,4 @@ *** Settings *** -Documentation Tests running on failure Suite Setup Run Keywords Go To Front Page Set Info Loglevel Prefer Custom Keywords Test Teardown Register Keyword to Run On Failure Nothing Suite Teardown Set Debug Loglevel @@ -8,24 +7,24 @@ Force Tags Known Issue Internet Explorer *** Variables *** ${PAGE TITLE} (root)/index.html -${FAILURE MESSAGE} Page should not have contained text 'needle' -${old order} ${EMPTY} +${FAILURE MESSAGE} Page should not have contained text 'needle'. *** Test Cases *** Run On Failure Keyword Only Called Once - [Documentation] Run On Failure Keyword Only Called Once Set Test Variable ${ON FAIL COUNT} ${0} Register Keyword To Run On Failure On Fail Run Keyword And Ignore Error Custom Selenium Keyword Should Be Equal ${ON FAIL COUNT} ${1} On Failure Keyword called ${ON FAIL COUNT} times. Log Title On Failure - [Documentation] LOG 1 Log Title will be run on failure. + [Documentation] + ... LOG 1 Log Title will be run on failure. ... LOG 2:2 NONE LOG 3.1.1:1 ${PAGE TITLE} LOG 3.1:3 NONE Register Keyword to Run on Failure Log Title Page Should Contain needle - Run Keyword And Expect Error ${FAILURE MESSAGE} Page Should Not Contain - ... needle loglevel=None + Run Keyword And Expect Error + ... ${FAILURE MESSAGE} + ... Page Should Not Contain needle loglevel=None Disable Run on Failure [Documentation] @@ -34,11 +33,13 @@ Disable Run on Failure ... LOG 3 No keyword will be run on failure. ... LOG 4.1:2 NONE Register Keyword to Run On Failure Nothing - Run Keyword And Expect Error ${FAILURE MESSAGE} Page Should Not Contain - ... needle loglevel=None + Run Keyword And Expect Error + ... ${FAILURE MESSAGE} + ... Page Should Not Contain needle loglevel=None Register Keyword to Run On Failure ${NONE} - Run Keyword And Expect Error ${FAILURE MESSAGE} Page Should Not Contain - ... needle loglevel=None + Run Keyword And Expect Error + ... ${FAILURE MESSAGE} + ... Page Should Not Contain needle loglevel=None Run on Failure Returns Previous Value [Documentation] Also tests that previous value always works as input. @@ -52,9 +53,11 @@ Run on Failure Returns Previous Value Should Be Equal ${old} Log Title Run On Failure also fails + [Documentation] LOG 2.1 WARN Keyword 'Failure During Run On failure' could not be run on failure: Expected error. Register Keyword to Run on Failure Failure During Run On failure - Run Keyword And Expect Error ${FAILURE MESSAGE} Page Should Not Contain - ... needle loglevel=None + Run Keyword And Expect Error + ... ${FAILURE MESSAGE} + ... Page Should Not Contain needle loglevel=None *** Keywords *** On Fail @@ -69,4 +72,4 @@ Open Browser To Front Page Open Browser ${FRONT PAGE} Failure During Run On failure - Page Should Not Contain needle + Fail Expected error. diff --git a/test/acceptance/keywords/textfields.robot b/test/acceptance/keywords/textfields.robot index fcbbe7abd..78a16759a 100644 --- a/test/acceptance/keywords/textfields.robot +++ b/test/acceptance/keywords/textfields.robot @@ -1,33 +1,34 @@ *** Setting *** Documentation Test textfields Test Setup Go To Page "forms/prefilled_email_form.html" -Variables variables.py Resource ../resource.robot Force Tags Known Issue Internet Explorer *** Test Cases *** Get Value From Text Field - [Documentation] Get Value From Text Field ${text} = Get Value name Should Be Equal ${text} Prefilled Name Clear Element Text name ${text} = Get Value name Should Be Equal ${text} ${EMPTY} -Input Unicode In Text Field - [Documentation] Input Unicode In Text Field - Input Text name ${unic_text} - ${text} = Get Value name - Should Be Equal ${text} ${unic_text} - -Input Password - [Documentation] LOG 3 Typing password into text field 'password_field' +Input Text and Input Password + [Documentation] + ... LOG 2 Typing text 'username' into text field 'username_field'. + ... LOG 3 Typing password into text field 'password_field'. [Setup] Go To Page "forms/login.html" Input Text username_field username Input Password password_field password Submit Form Verify Location Is "forms/submit.html" +Input Non-ASCII Text + [Documentation] + ... LOG 2 Typing text 'Yrjö Ärje' into text field 'name'. + Input Text name Yrjö Ärje + ${text} = Get Value name + Should Be Equal ${text} Yrjö Ärje + Press Key [Setup] Go To Page "forms/login.html" Cannot Be Executed in IE diff --git a/test/acceptance/keywords/variables.py b/test/acceptance/keywords/variables.py deleted file mode 100644 index 8516739e2..000000000 --- a/test/acceptance/keywords/variables.py +++ /dev/null @@ -1,2 +0,0 @@ -#encoding: utf-8 -unic_text = u'ÄÖÅåöä €€ scandic toimii kivasti' diff --git a/test/acceptance/locators/locator_parsing.robot b/test/acceptance/locators/locator_parsing.robot index 0d7acab85..3fd7dbc1f 100644 --- a/test/acceptance/locators/locator_parsing.robot +++ b/test/acceptance/locators/locator_parsing.robot @@ -33,5 +33,5 @@ Locator with separator and with matchign prefix cannot be used as-is Page Should Contain Element id:id:problematic Page Should Contain Element id=id:problematic Run Keyword And Expect Error - ... Page should have contained element 'id:problematic' but did not + ... Page should have contained element 'id:problematic' but did not. ... Page Should Contain Element id:problematic diff --git a/test/unit/keywords/test_keyword_arguments_element.py b/test/unit/keywords/test_keyword_arguments_element.py index 33b053bfb..71eefbbdd 100644 --- a/test/unit/keywords/test_keyword_arguments_element.py +++ b/test/unit/keywords/test_keyword_arguments_element.py @@ -46,8 +46,7 @@ def test_element_should_not_contain(self): def test_locator_should_match_x_times(self): locator = '//div' - when(self.element).find_element(locator, required=False, - first_only=False).thenReturn([]) + when(self.element).find_elements(locator).thenReturn([]) with self.assertRaisesRegexp(AssertionError, 'should have matched'): self.element.locator_should_match_x_times(locator, 1) @@ -87,8 +86,7 @@ def test_get_element_attribute(self): locator = '//div' attrib = 'id' element = mock() - when(self.element).find_element(locator, - required=False).thenReturn(element) + when(self.element).find_element(locator).thenReturn(element) when(element).get_attribute(attrib).thenReturn('value') value = self.element.get_element_attribute(locator, attrib) self.assertEqual(value, 'value') @@ -100,7 +98,7 @@ def test_get_element_attribute(self): def test_get_matching_xpath_count(self): locator = '//div' when(self.element).find_element( - 'xpath={}'.format(locator), first_only=False, + 'xpath:{}'.format(locator), first_only=False, required=False).thenReturn([]) count = self.element.get_matching_xpath_count(locator) self.assertEqual(count, '0') @@ -112,9 +110,7 @@ def test_get_matching_xpath_count(self): def test_xpath_should_match_x_times(self): locator = '//div' - when(self.element).find_element( - 'xpath={}'.format(locator), first_only=False, - required=False).thenReturn([]) + when(self.element).find_elements('xpath:{}'.format(locator)).thenReturn([]) with self.assertRaisesRegexp(AssertionError, 'should have matched'): self.element.xpath_should_match_x_times(locator, 1) diff --git a/test/unit/keywords/test_keyword_arguments_formelement.py b/test/unit/keywords/test_keyword_arguments_formelement.py index ecd67f620..3dbcf16c0 100644 --- a/test/unit/keywords/test_keyword_arguments_formelement.py +++ b/test/unit/keywords/test_keyword_arguments_formelement.py @@ -21,8 +21,7 @@ def tearDown(self): def test_submit_form_false(self): element = mock() - when(self.form).find_element('xpath=//form', - tag='form').thenReturn(element) + when(self.form).find_element('tag:form', tag='form').thenReturn(element) for false in FALSES: self.form.submit_form() self.form.submit_form() From 4724be0b21ae9560aaa6d7862c43e538c12d0e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Fri, 13 Oct 2017 18:11:07 +0300 Subject: [PATCH 03/12] Enhance docs of different locator strategies. Also added more XPath examples. This ought to cover enhancements proposed by #940. --- src/SeleniumLibrary/__init__.py | 43 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index a4b823d94..e34a63f3d 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -100,7 +100,7 @@ class SeleniumLibrary(DynamicCore): | `Click Element` | name:foo | # Find element with name ``foo``. | | `Click Element` | default:name:foo | # Use default strategy with value ``name:foo``. | | `Click Element` | //foo | # Find element using XPath ``//foo``. | - | `Click Element` | default://foo | # Use default strategy with value ``//foo``. | + | `Click Element` | default: //foo | # Use default strategy with value ``//foo``. | === Explicit locator strategy === @@ -108,9 +108,9 @@ class SeleniumLibrary(DynamicCore): syntax ``strategy:value`` or ``strategy=value``. The former syntax is preferred, because the latter is identical to Robot Framework's [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#named-argument-syntax| - named argument syntax] and that can cause problems. Notice that the - ``strategy:value`` syntax is olny supported by SeleniumLibrary 3.0 and - newer, though. + named argument syntax] and that can cause problems. Spaces around + the separator are ignored, so ``id:foo``, ``id: foo`` and ``id : foo`` + are all equivalent. Locator strategies that are supported by default are listed in the table below. In addition to them, it is possible to register `custom locators`. @@ -135,20 +135,33 @@ class SeleniumLibrary(DynamicCore): prefix is only necessary if the locator value itself accidentally matches some of the explicit strategies. - Spaces around the separator are ignored, so ``id:foo``, ``id: foo`` - and ``id : foo`` are all equivalent. + Different locator strategies have different pros and cons. Using ids, + either explicitly like ``id:foo`` or by using the `default locator + strategy` simply like ``foo``, is recommended when possible, because + the syntax is simple and locating elements by an id is fast for browsers. + If an element does not have an id or the id is not stable, other + solutions need to be used. If an element has a unique tag name or class, + using ``tag``, ``class`` or ``css`` strategy like ``tag:h1``, + ``class:example`` or ``css:h1.example`` is often an easy solution. In + more complex cases using XPath expressions is typically the best + approach. They are very powerful but a downside is that they can also + get complex. Examples: - | `Click Element` | id:container | - | `Click Element` | css:div#container h1 | - | `Click Element` | xpath: //div[@id="container"]//h1 | + | `Click Element` | id:foo | # Element with id 'foo'. | + | `Click Element` | css:div#foo h1 | # h1 element under div with id 'foo'. | + | `Click Element` | xpath: //div[@id="foo"]//h1 | # Same as the above using XPath, not CSS. | + | `Click Element` | xpath: //*[contains(text(), "example")] | # Element containing text 'example'. | - Notice that using the ``sizzle`` strategy or its alias ``jquery`` - requires that the system under test contains the jQuery library. + *NOTE:* - Notice also that prior to SeleniumLibrary 3.0, table related keywords - only supported ``xpath``, ``css`` and ``sizzle/jquery`` strategies. + - The ``strategy:value`` syntax is only supported by SeleniumLibrary 3.0 + and newer. + - Using the ``sizzle`` strategy or its alias ``jquery`` requires that + the system under test contains the jQuery library. + - Prior to SeleniumLibrary 3.0, table related keywords only supported + ``xpath``, ``css`` and ``sizzle/jquery`` strategies. === Implicit XPath strategy === @@ -158,8 +171,8 @@ class SeleniumLibrary(DynamicCore): Examples: - | `Click Element` | //div[@id="container"] | - | `Click Element` | (//div)[2] | + | `Click Element` | //div[@id="foo"]//h1 | + | `Click Element` | (//div)[2] | The support for the ``(//`` prefix is new in SeleniumLibrary 3.0. From f3834708aa3dde1c60ac126ca14c1aaf22e20c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 17 Oct 2017 01:01:54 +0300 Subject: [PATCH 04/12] Another big cleanup commit. - Doc string to `find_element`. Mainly to give type hits to IDE but higher level APIs should get more docs in general. - Introduced ElementNotFound error that `ElementFinder.find` uses. - Move helpers to ContextAware and LibraryComponent to avoid unnecessary coupling between different library componets. - Remove unnecessary helpers when `find_element(locator).method()` works as well. - Rewriter internal logic with `Wait` keywords. This includes consistently handling non-existing elements. - Make unregistering strategy that hasn't been registered an error. - Consider `` text element consistently. - General cleanup here and there. --- src/SeleniumLibrary/base/context.py | 35 +++- src/SeleniumLibrary/base/librarycomponent.py | 26 ++- src/SeleniumLibrary/errors.py | 19 ++ .../keywords/browsermanagement.py | 1 - src/SeleniumLibrary/keywords/element.py | 60 +++---- src/SeleniumLibrary/keywords/formelement.py | 43 ++--- src/SeleniumLibrary/keywords/waiting.py | 164 +++++++----------- src/SeleniumLibrary/locators/elementfinder.py | 62 ++----- .../keywords/content_assertions.robot | 10 +- test/acceptance/keywords/element_focus.robot | 9 +- test/acceptance/keywords/elements.robot | 8 +- test/acceptance/keywords/mouse.robot | 8 +- test/acceptance/keywords/waiting.robot | 60 ++++--- test/acceptance/locators/custom.robot | 12 +- .../test_keyword_arguments_element.py | 47 ----- .../test_keyword_arguments_formelement.py | 28 ++- .../test_keyword_arguments_waiting.py | 2 +- test/unit/locators/test_elementfinder.py | 26 +-- 18 files changed, 275 insertions(+), 345 deletions(-) create mode 100644 src/SeleniumLibrary/errors.py diff --git a/src/SeleniumLibrary/base/context.py b/src/SeleniumLibrary/base/context.py index d637577eb..103a3fbb6 100644 --- a/src/SeleniumLibrary/base/context.py +++ b/src/SeleniumLibrary/base/context.py @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from SeleniumLibrary.utils import escape_xpath_value + class ContextAware(object): @@ -37,14 +39,41 @@ def browsers(self): def element_finder(self): return self.ctx.element_finder + @property + def table_element_finder(self): + return self.ctx.table_element_finder + def find_element(self, locator, tag=None, first_only=True, required=True, parent=None): + """Find element matching `locator`. + + :param locator: Locator to use when searching the element. + See library documentation for the supported locator syntax. + :param tag: Limit searching only to these elements. + :param first_only: Return only first matching element if true, + a list of elements otherwise. + :param required: Raise `ElementNotFound` if element not found when + true, return `None` otherwise. + :param parent: Possible parent `WebElememt` element to search + elements from. By default search starts from `WebDriver`. + :return: Found `WebElement` or `None` if element not found and + `required` is false. + :rtype: selenium.webdriver.remote.webelement.WebElement + :raises SeleniumLibrary.errors.ElementNotFound: If element not found + and `required` is true. + """ return self.element_finder.find(locator, tag, first_only, required, parent) def find_elements(self, locator, tag=None, parent=None): + """Find all elements matching `locator`. + + Always returns a list of `WebElement` objects. If no matching element + is found, the list is empty. Otherwise semantics are exactly same + as with :meth:`find_element`. + """ return self.find_element(locator, tag, False, False, parent) - @property - def table_element_finder(self): - return self.ctx.table_element_finder + def is_text_present(self, text): + locator = "xpath://*[contains(., %s)]" % escape_xpath_value(text) + return self.find_element(locator, required=False) is not None diff --git a/src/SeleniumLibrary/base/librarycomponent.py b/src/SeleniumLibrary/base/librarycomponent.py index 1273fe366..232db6952 100644 --- a/src/SeleniumLibrary/base/librarycomponent.py +++ b/src/SeleniumLibrary/base/librarycomponent.py @@ -30,9 +30,6 @@ class LibraryComponent(ContextAware): - def __init__(self, ctx): - ContextAware.__init__(self, ctx) - def info(self, msg, html=False): logger.info(msg, html) @@ -46,15 +43,30 @@ def log(self, msg, level='INFO', html=False): def warn(self, msg, html=False): logger.warn(msg, html) + def log_source(self, loglevel='INFO'): + self.ctx.log_source(loglevel) + def assert_page_contains(self, locator, tag=None, message=None, loglevel='INFO'): - self.element_finder.assert_page_contains(locator, tag, message, - loglevel) + if not self.find_element(locator, tag, required=False): + self.log_source(loglevel) + if is_noney(message): + message = ("Page should have contained %s '%s' but did not." + % (tag or 'element', locator)) + raise AssertionError(message) + logger.info("Current page contains %s '%s'." + % (tag or 'element', locator)) def assert_page_not_contains(self, locator, tag=None, message=None, loglevel='INFO'): - self.element_finder.assert_page_not_contains(locator, tag, message, - loglevel) + if self.find_element(locator, tag, required=False): + self.log_source(loglevel) + if is_noney(message): + message = ("Page should not have contained %s '%s'." + % (tag or 'element', locator)) + raise AssertionError(message) + logger.info("Current page does not contain %s '%s'." + % (tag or 'element', locator)) def get_timeout(self, timeout=None): if is_noney(timeout): diff --git a/src/SeleniumLibrary/errors.py b/src/SeleniumLibrary/errors.py new file mode 100644 index 000000000..2e66b3b1c --- /dev/null +++ b/src/SeleniumLibrary/errors.py @@ -0,0 +1,19 @@ +# Copyright 2008-2011 Nokia Networks +# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors +# Copyright 2016- Robot Framework Foundation +# +# 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. + + +class ElementNotFound(Exception): + ROBOT_SUPPRESS_NAME = True diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 9445c897e..19443cabe 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -18,7 +18,6 @@ import time import types -from robot.errors import DataError from robot.utils import NormalizedDict from selenium import webdriver from selenium.common.exceptions import NoSuchWindowException diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index d46e4e4c1..a44169ddc 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -18,8 +18,8 @@ from selenium.webdriver.common.keys import Keys from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.utils import (escape_xpath_value, is_falsy, is_noney, - is_truthy, plural_or_not as s) +from SeleniumLibrary.utils import (is_falsy, is_noney, is_truthy, + plural_or_not as s) class ElementKeywords(LibraryComponent): @@ -44,7 +44,7 @@ def get_webelements(self, locator): list if there are no matching elements. In previous releases the keyword failed in this case. """ - return self.find_element(locator, first_only=False, required=False) + return self.find_elements(locator) @keyword def current_frame_should_contain(self, text, loglevel='INFO'): @@ -93,14 +93,13 @@ def element_should_contain(self, locator, expected, message=None): Use `Element Text Should Be` if you want to match the exact text, not a substring. """ - self.info("Verifying element '%s' contains text '%s'." - % (locator, expected)) - actual = self._get_text(locator) + actual = self.find_element(locator).text if expected not in actual: if is_noney(message): message = "Element '%s' should have contained text '%s' but "\ "its text was '%s'." % (locator, expected, actual) raise AssertionError(message) + self.info("Element '%s' contains text '%s'." % (locator, expected)) @keyword def element_should_not_contain(self, locator, expected, message=None): @@ -112,14 +111,14 @@ def element_should_not_contain(self, locator, expected, message=None): The ``message`` argument can be used to override the default error message. """ - self.info("Verifying element '%s' does not contain text '%s'." - % (locator, expected)) - actual = self._get_text(locator) + actual = self.find_element(locator).text if expected in actual: if is_noney(message): message = "Element '%s' should not contain text '%s' but " \ "it did." % (locator, expected) raise AssertionError(message) + self.info("Element '%s' does not contain text '%s'." + % (locator, expected)) @keyword def frame_should_contain(self, locator, text, loglevel='INFO'): @@ -287,23 +286,26 @@ def element_should_be_visible(self, locator, message=None): The ``message`` argument can be used to override the default error message. """ - self.info("Verifying element '%s' is visible." % locator) - visible = self.is_visible(locator) - if not visible: + if not self.find_element(locator).is_displayed(): if is_noney(message): message = ("The element '%s' should be visible, but it " "is not." % locator) raise AssertionError(message) + self.info("Element '%s' is displayed." % locator) @keyword def element_should_not_be_visible(self, locator, message=None): """Verifies that the element identified by ``locator`` is NOT visible. - This is the opposite of `Element Should Be Visible`. + Passes if element does not exists. See `Element Should Be Visible` + for more information about visibility and supported arguments. """ - self.info("Verifying element '%s' is not visible." % locator) - visible = self.is_visible(locator) - if visible: + element = self.find_element(locator, required=False) + if element is None: + self.info("Element '%s' did not exist." % locator) + elif not element.is_displayed(): + self.info("Element '%s' exists but is not displayed." % locator) + else: if is_noney(message): message = ("The element '%s' should not be visible, " "but it is." % locator) @@ -323,12 +325,12 @@ def element_text_should_be(self, locator, expected, message=None): """ self.info("Verifying element '%s' contains exact text '%s'." % (locator, expected)) - element = self.find_element(locator) - if element.text != expected: + text = self.find_element(locator).text + if text != expected: if is_noney(message): message = ("The text of element '%s' should have been '%s' " "but it was '%s'." - % (locator, expected, element.text)) + % (locator, expected, text)) raise AssertionError(message) @keyword @@ -387,7 +389,7 @@ def get_value(self, locator): See the `Locating elements` section for details about the locator syntax. """ - return self.ctx.element_finder.get_value(locator) + return self.get_element_attribute(locator, 'value') @keyword def get_text(self, locator): @@ -396,7 +398,7 @@ def get_text(self, locator): See the `Locating elements` section for details about the locator syntax. """ - return self._get_text(locator) + return self.find_element(locator).text @keyword def clear_element_text(self, locator): @@ -786,12 +788,6 @@ def _frame_contains(self, locator, text): self.browser.switch_to.default_content() return found - def _get_text(self, locator): - element = self.find_element(locator) - if element is not None: - return element.text - return None - def _is_enabled(self, locator): element = self.find_element(locator) if element.tag_name.lower() not in {'input', 'select', 'textarea', @@ -804,16 +800,6 @@ def _is_enabled(self, locator): return False return True - def is_text_present(self, text): - locator = "xpath://*[contains(., %s)]" % escape_xpath_value(text) - return self.find_element(locator, required=False) - - def is_visible(self, locator): - element = self.find_element(locator, required=False) - if element is not None: - return element.is_displayed() - return None - def _map_ascii_key_code_to_key(self, key_code): map = { 0: Keys.NULL, diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 922bd85be..46f86c7ee 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -258,7 +258,7 @@ def textfield_should_contain(self, locator, expected, message=None): Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. """ - actual = self.element_finder.get_value(locator, 'text field') + actual = self._get_value(locator, 'text field') if expected not in actual: if is_noney(message): message = "Text field '%s' should have contained text '%s' "\ @@ -275,11 +275,7 @@ def textfield_value_should_be(self, locator, expected, message=None): Key attributes for text fields are `id` and `name`. See `introduction` for details about locating elements. """ - element = self.find_element(locator, tag='text field', required=False) - if not element: - element = self.find_element(locator, tag='file upload', - required=False) - actual = element.get_attribute('value') if element else None + actual = self._get_value(locator, 'text field') if actual != expected: if is_noney(message): message = "Value of text field '%s' should have been '%s' "\ @@ -296,16 +292,12 @@ def textarea_should_contain(self, locator, expected, message=None): Key attributes for text areas are `id` and `name`. See `introduction` for details about locating elements. """ - actual = self.element_finder.get_value(locator, 'text area') - if actual is not None: - if expected not in actual: - if is_noney(message): - message = "Text field '%s' should have contained text '%s' "\ - "but it contained '%s'." % (locator, expected, actual) - raise AssertionError(message) - else: - raise ValueError("Element locator '%s' did not match any " - "elements." % locator) + actual = self._get_value(locator, 'text area') + if expected not in actual: + if is_noney(message): + message = "Text area '%s' should have contained text '%s' " \ + "but it had '%s'." % (locator, expected, actual) + raise AssertionError(message) self.info("Text area '%s' contains text '%s'." % (locator, expected)) @keyword @@ -317,16 +309,12 @@ def textarea_value_should_be(self, locator, expected, message=None): Key attributes for text areas are `id` and `name`. See `introduction` for details about locating elements. """ - actual = self.element_finder.get_value(locator, 'text area') - if actual is not None: - if expected != actual: - if is_noney(message): - message = "Text field '%s' should have contained text '%s' "\ - "but it contained '%s'." % (locator, expected, actual) - raise AssertionError(message) - else: - raise ValueError("Element locator '%s' did not match any " - "elements." % locator) + actual = self._get_value(locator, 'text area') + if expected != actual: + if is_noney(message): + message = "Text area '%s' should have had text '%s' " \ + "but it had '%s'." % (locator, expected, actual) + raise AssertionError(message) self.info("Content of text area '%s' is '%s'." % (locator, expected)) @keyword @@ -374,6 +362,9 @@ def page_should_not_contain_button(self, locator, message=None, loglevel='INFO') self.assert_page_not_contains(locator, 'button', message, loglevel) self.assert_page_not_contains(locator, 'input', message, loglevel) + def _get_value(self, locator, tag): + return self.find_element(locator, tag).get_attribute('value') + def _get_checkbox(self, locator): return self.find_element(locator, tag='input') diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index 4bf9b631c..c8f49f48c 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -17,17 +17,12 @@ import time from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.keywords.element import ElementKeywords -from SeleniumLibrary.utils import (is_truthy, is_falsy, - secs_to_timestr, timestr_to_secs) +from SeleniumLibrary.errors import ElementNotFound +from SeleniumLibrary.utils import is_noney, secs_to_timestr class WaitingKeywords(LibraryComponent): - def __init__(self, ctx): - LibraryComponent.__init__(self, ctx) - self.element = ElementKeywords(ctx) - @keyword def wait_for_condition(self, condition, timeout=None, error=None): """Waits until the given ``condition`` is true or ``timeout`` expires. @@ -48,11 +43,11 @@ def wait_for_condition(self, condition, timeout=None, error=None): if 'return' not in condition: raise ValueError("Condition '%s' did not have mandatory 'return'." % condition) - if is_falsy(error): - error = "Condition '%s' did not become true in " % condition self._wait_until( - timeout, error, - lambda: self.browser.execute_script(condition) is True) + lambda: self.browser.execute_script(condition) is True, + "Condition '%s' did not become true in ." % condition, + timeout, error + ) @keyword def wait_until_page_contains(self, text, timeout=None, error=None): @@ -68,9 +63,9 @@ def wait_until_page_contains(self, text, timeout=None, error=None): `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - if is_falsy(error): - error = "Text '%s' did not appear in " % text - self._wait_until(timeout, error, self.element.is_text_present, text) + self._wait_until(lambda: self.is_text_present(text), + "Text '%s' did not appear in ." % text, + timeout, error) @keyword def wait_until_page_does_not_contain(self, text, timeout=None, error=None): @@ -86,13 +81,9 @@ def wait_until_page_does_not_contain(self, text, timeout=None, error=None): `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def check_present(): - present = self.element.is_text_present(text) - if not present: - return - else: - return error or "Text '%s' did not disappear in %s" % (text, self._format_timeout(timeout)) - self._wait_until_no_error(timeout, check_present) + self._wait_until(lambda: not self.is_text_present(text), + "Text '%s' did not disappear in ." % text, + timeout, error) @keyword def wait_until_page_contains_element(self, locator, timeout=None, error=None): @@ -108,11 +99,11 @@ def wait_until_page_contains_element(self, locator, timeout=None, error=None): `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def is_element_present(locator): - return self.find_element(locator, required=False) is not None - if is_falsy(error): - error = "Element '%s' did not appear in " % locator - self._wait_until(timeout, error, is_element_present, locator) + self._wait_until( + lambda: self.find_element(locator, required=False) is not None, + "Element '%s' did not appear in ." % locator, + timeout, error + ) @keyword def wait_until_page_does_not_contain_element(self, locator, timeout=None, error=None): @@ -128,13 +119,11 @@ def wait_until_page_does_not_contain_element(self, locator, timeout=None, error= `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def check_present(): - present = self.find_element(locator, required=False) - if not present: - return - else: - return error or "Element '%s' did not disappear in %s" % (locator, self._format_timeout(timeout)) - self._wait_until_no_error(timeout, check_present) + self._wait_until( + lambda: self.find_element(locator, required=False) is None, + "Element '%s' did not disappear in ." % locator, + timeout, error + ) @keyword def wait_until_element_is_visible(self, locator, timeout=None, error=None): @@ -150,15 +139,11 @@ def wait_until_element_is_visible(self, locator, timeout=None, error=None): Element`, `Wait For Condition` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def check_visibility(): - visible = self.element.is_visible(locator) - if visible: - return - elif visible is None: - return error or "Element locator '%s' did not match any elements after %s" % (locator, self._format_timeout(timeout)) - else: - return error or "Element '%s' was not visible in %s" % (locator, self._format_timeout(timeout)) - self._wait_until_no_error(timeout, check_visibility) + self._wait_until( + lambda: self.find_element(locator).is_displayed(), + "Element '%s' not visible after ." % locator, + timeout, error + ) @keyword def wait_until_element_is_not_visible(self, locator, timeout=None, error=None): @@ -174,15 +159,11 @@ def wait_until_element_is_not_visible(self, locator, timeout=None, error=None): Element`, `Wait For Condition` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def check_hidden(): - visible = self.element.is_visible(locator) - if not visible: - return - elif visible is None: - return error or "Element locator '%s' did not match any elements after %s" % (locator, self._format_timeout(timeout)) - else: - return error or "Element '%s' was still visible in %s" % (locator, self._format_timeout(timeout)) - self._wait_until_no_error(timeout, check_hidden) + self._wait_until( + lambda: not self.find_element(locator).is_displayed(), + "Element '%s' still visible after ." % locator, + timeout, error + ) @keyword def wait_until_element_is_enabled(self, locator, timeout=None, error=None): @@ -198,18 +179,11 @@ def wait_until_element_is_enabled(self, locator, timeout=None, error=None): Element`, `Wait For Condition` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - def check_enabled(): - element = self.find_element(locator, required=False) - if not element: - return error or "Element locator '%s' did not match any elements after %s" % (locator, self._format_timeout(timeout)) - - enabled = not element.get_attribute("disabled") - if enabled: - return - else: - return error or "Element '%s' was not enabled in %s" % (locator, self._format_timeout(timeout)) - - self._wait_until_no_error(timeout, check_enabled) + self._wait_until( + lambda: not self.find_element(locator).get_attribute('disabled'), + "Element '%s' was not enabled in ." % locator, + timeout, error + ) @keyword def wait_until_element_contains(self, locator, text, timeout=None, error=None): @@ -225,15 +199,11 @@ def wait_until_element_contains(self, locator, text, timeout=None, error=None): `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - element = self.find_element(locator) - def check_text(): - actual = element.text - if text in actual: - return - else: - return error or "Text '%s' did not appear in %s to element '%s'. " \ - "Its text was '%s'." % (text, self._format_timeout(timeout), locator, actual) - self._wait_until_no_error(timeout, check_text) + self._wait_until( + lambda: text in self.find_element(locator).text, + "Element '%s' did not get text '%s' in ." % (locator, text), + timeout, error + ) @keyword def wait_until_element_does_not_contain(self, locator, text, timeout=None, error=None): @@ -249,30 +219,30 @@ def wait_until_element_does_not_contain(self, locator, text, timeout=None, error `Wait Until Element Is Visible` and BuiltIn keyword `Wait Until Keyword Succeeds`. """ - element = self.find_element(locator) - def check_text(): - actual = element.text - if text not in actual: - return + self._wait_until( + lambda: text not in self.find_element(locator).text, + "Element '%s' still had text '%s' after ." % (locator, text), + timeout, error + ) + + def _wait_until(self, condition, error, timeout=None, custom_error=None): + timeout = self.get_timeout(timeout) + if is_noney(custom_error): + error = error.replace('', secs_to_timestr(timeout)) + else: + error = custom_error + self._wait_until_worker(condition, timeout, error) + + def _wait_until_worker(self, condition, timeout, error): + max_time = time.time() + timeout + not_found = None + while time.time() < max_time: + try: + if condition(): + return + except ElementNotFound as err: + not_found = str(err) else: - return error or "Text '%s' did not disappear in %s from element '%s'." % (text, self._format_timeout(timeout), locator) - self._wait_until_no_error(timeout, check_text) - - def _wait_until(self, timeout, error, function, *args): - error = error.replace('', self._format_timeout(timeout)) - def wait_func(): - return None if function(*args) else error - self._wait_until_no_error(timeout, wait_func) - - def _wait_until_no_error(self, timeout, wait_func, *args): - maxtime = time.time() + self.get_timeout(timeout) - while True: - timeout_error = wait_func(*args) - if not timeout_error: - return - if time.time() > maxtime: - raise AssertionError(timeout_error) + not_found = None time.sleep(0.2) - - def _format_timeout(self, timeout): - return secs_to_timestr(self.get_timeout(timeout)) + raise AssertionError(not_found or error) diff --git a/src/SeleniumLibrary/locators/elementfinder.py b/src/SeleniumLibrary/locators/elementfinder.py index dd9edb87b..e9daa47aa 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -19,8 +19,8 @@ from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import ContextAware -from SeleniumLibrary.utils import (escape_xpath_value, events, is_falsy, - is_noney) +from SeleniumLibrary.errors import ElementNotFound +from SeleniumLibrary.utils import escape_xpath_value, events, is_falsy from .customlocator import CustomLocator @@ -71,40 +71,14 @@ def find(self, locator, tag=None, first_only=True, required=True, elements = strategy(criteria, tag, constraints, parent=parent or self.browser) if required and not elements: - raise ValueError("Element locator '{}' did not match any " - "elements.".format(locator)) + raise ElementNotFound("Element with locator '{}' not found." + .format(locator)) if first_only: if not elements: return None return elements[0] return elements - def assert_page_contains(self, locator, tag=None, message=None, - loglevel='INFO'): - if not self.find(locator, tag, required=False): - if is_noney(message): - message = ("Page should have contained %s '%s' but did not." - % (tag or 'element', locator)) - self.ctx.log_source(loglevel) # TODO: Could this moved to base - raise AssertionError(message) - logger.info("Current page contains %s '%s'." - % (tag or 'element', locator)) - - def assert_page_not_contains(self, locator, tag=None, message=None, - loglevel='INFO'): - if self.find(locator, tag, required=False): - if is_noney(message): - message = ("Page should not have contained %s '%s'." - % (tag or 'element', locator)) - self.ctx.log_source(loglevel) # TODO: Could this moved to base - raise AssertionError(message) - logger.info("Current page does not contain %s '%s'." - % (tag or 'element', locator)) - - def get_value(self, locator, tag=None): - element = self.find(locator, tag, required=False) - return element.get_attribute('value') if element else None - def register(self, strategy_name, strategy_keyword, persist=False): strategy = CustomLocator(self.ctx, strategy_name, strategy_keyword) if strategy.name in self._strategies: @@ -120,11 +94,10 @@ def unregister(self, strategy_name): if strategy_name in self._default_strategies: raise RuntimeError("Cannot unregister the default strategy '%s'." % strategy_name) - elif strategy_name not in self._strategies: - logger.info("Cannot unregister the non-registered strategy '%s'." - % strategy_name) - else: - del self._strategies[strategy_name] + if strategy_name not in self._strategies: + raise RuntimeError("Cannot unregister the non-registered strategy '%s'." + % strategy_name) + del self._strategies[strategy_name] def _is_webelement(self, element): # Hook for unit tests @@ -135,9 +108,8 @@ def _disallow_webelement_parent(self, element): raise ValueError('This method does not allow WebElement as parent') def _find_by_identifier(self, criteria, tag, constraints, parent): - elements = self._normalize_result(parent.find_elements_by_id(criteria)) - elements.extend(self._normalize_result( - parent.find_elements_by_name(criteria))) + elements = self._normalize(parent.find_elements_by_id(criteria)) \ + + self._normalize(parent.find_elements_by_name(criteria)) return self._filter_elements(elements, tag, constraints) def _find_by_id(self, criteria, tag, constraints, parent): @@ -215,7 +187,7 @@ def _find_by_default(self, criteria, tag, constraints, parent): ' and ' if xpath_constraints else '', ' or '.join(xpath_searchers) ) - return self._normalize_result(parent.find_elements_by_xpath(xpath)) + return self._normalize(parent.find_elements_by_xpath(xpath)) def _get_xpath_constraints(self, constraints): xpath_constraints = [self._get_xpath_constraint(name, value) @@ -251,7 +223,7 @@ def _get_tag_and_constraints(self, tag): tag = 'input' constraints['type'] = ['date', 'datetime-local', 'email', 'month', 'number', 'password', 'search', 'tel', - 'text', 'time', 'url', 'week'] + 'text', 'time', 'url', 'week', 'file'] elif tag == 'file upload': tag = 'input' constraints['type'] = 'file' @@ -288,10 +260,11 @@ def _element_matches(self, element, tag, constraints): return True def _filter_elements(self, elements, tag, constraints): - elements = self._normalize_result(elements) + elements = self._normalize(elements) if tag is None: return elements - return [element for element in elements if self._element_matches(element, tag, constraints)] + return [element for element in elements + if self._element_matches(element, tag, constraints)] def _get_attrs_with_url(self, key_attrs, criteria): attrs = [] @@ -311,7 +284,10 @@ def _get_base_url(self): url = '/'.join(url.split('/')[:-1]) return url - def _normalize_result(self, elements): + def _normalize(self, elements): + # Apparently IEDriver has returned invalid data earlier and recently + # ChromeDriver has done sometimes returned None: + # https://github.com/SeleniumHQ/selenium/issues/4555 if not isinstance(elements, list): logger.debug("WebDriver find returned %s" % elements) return [] diff --git a/test/acceptance/keywords/content_assertions.robot b/test/acceptance/keywords/content_assertions.robot index 822940692..f139f390d 100644 --- a/test/acceptance/keywords/content_assertions.robot +++ b/test/acceptance/keywords/content_assertions.robot @@ -115,7 +115,7 @@ Element Should Contain ... Element 'some_id' should have contained text 'non existing text' but its text was 'This text is inside an identified element'. ... Element Should Contain some_id non existing text Run Keyword And Expect Error - ... ValueError: Element locator 'missing_id' did not match any elements. + ... Element with locator 'missing_id' not found. ... Element Should Contain missing_id This should report missing element. Element Should Not Contain @@ -125,7 +125,7 @@ Element Should Not Contain ... Element 'some_id' should not contain text 'This text is inside an identified element' but it did. ... Element Should Not Contain some_id This text is inside an identified element Run Keyword And Expect Error - ... ValueError: Element locator 'missing_id' did not match any elements. + ... Element with locator 'missing_id' not found. ... Element Should Not Contain missing_id This should report missing element. Element Text Should Be @@ -138,7 +138,7 @@ Get Text ${str} = Get Text some_id Should Match ${str} This text is inside an identified element Run Keyword And Expect Error - ... ValueError: Element locator 'missing_id' did not match any elements. + ... Element with locator 'missing_id' not found. ... Get Text missing_id Element Should Be Visible @@ -287,7 +287,7 @@ TextArea Should Contain TextArea Should Contain comment ${EMPTY} Input Text comment This is a comment. Run Keyword And Expect Error - ... Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.'. + ... Text area 'comment' should have contained text 'Hello World!' but it had 'This is a comment.'. ... TextArea Should Contain comment Hello World! TextArea Value Should Be @@ -295,7 +295,7 @@ TextArea Value Should Be TextArea Value Should Be comment ${EMPTY} Input Text comment This is a comment. Run Keyword And Expect Error - ... Text field 'comment' should have contained text 'Hello World!' but it contained 'This is a comment.'. + ... Text area 'comment' should have had text 'Hello World!' but it had 'This is a comment.'. ... TextArea Value Should Be comment Hello World! Clear Element Text comment TextArea Value Should Be comment ${EMPTY} diff --git a/test/acceptance/keywords/element_focus.robot b/test/acceptance/keywords/element_focus.robot index df74603bc..43d673a8f 100644 --- a/test/acceptance/keywords/element_focus.robot +++ b/test/acceptance/keywords/element_focus.robot @@ -5,13 +5,11 @@ Resource ../resource.robot *** Test Cases *** Should Be Focused - [Documentation] Verify that element is Focused [Setup] Go To Page "mouse/index.html" Click Element el_for_focus Element Should Be Focused el_for_focus Should Not Be Focused - [Documentation] Verify that element is not Focused [Setup] Go To Page "mouse/index.html" Click Element el_for_focus Run Keyword And Expect Error @@ -20,13 +18,11 @@ Should Not Be Focused Element Should Be Focused el_for_focus Unexistent Element Not Focused - [Documentation] Missing element returns locator error [Setup] Go To Page "mouse/index.html" Click Element el_for_focus Run Keyword And Expect Error - ... ValueError: Element locator 'Unexistent_element' did not match any elements. + ... Element with locator 'Unexistent_element' not found. ... Element Should Be Focused Unexistent_element - Element Should Be Focused el_for_focus Span Element Not Focused [Documentation] Focus on not Focusable Span @@ -45,7 +41,6 @@ Table Element Not Focused ... Element Should Be Focused simpleTable Radio Button Should Be Focused - [Documentation] Radio Button with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Click Element xpath=//input[@name='sex' and @value='male'] Element Should Be Focused xpath=//input[@name='sex' and @value='male'] @@ -54,7 +49,6 @@ Radio Button Should Be Focused ... Element Should Be Focused xpath=//input[@name='sex' and @value='female'] Checkbox Should Be Focused - [Documentation] Checkbox with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Click Element xpath=//input[@name='can_send_sms'] Element Should Be Focused xpath=//input[@name='can_send_sms'] @@ -63,7 +57,6 @@ Checkbox Should Be Focused ... Element Should Be Focused xpath=//input[@name='can_send_email'] Select Button Should Be Focused - [Documentation] Select Button with xpath should be focused [Setup] Go To Page "forms/prefilled_email_form.html" Mouse Down xpath=//select[@name='preferred_channel'] Element Should Be Focused xpath=//select[@name='preferred_channel'] diff --git a/test/acceptance/keywords/elements.robot b/test/acceptance/keywords/elements.robot index 9eac7e5cb..4b46a0fb9 100644 --- a/test/acceptance/keywords/elements.robot +++ b/test/acceptance/keywords/elements.robot @@ -16,7 +16,7 @@ Get Web Element ${link}= Get WebElement //div[@id="div_id"]/a Should Be Equal @{links}[0] ${link} Run Keyword and Expect Error - ... ValueError: Element locator 'id=non_existing_elem' did not match any elements. + ... Element with locator 'id=non_existing_elem' not found. ... Get WebElement id=non_existing_elem More Get Elements @@ -71,14 +71,14 @@ Get Horizontal Position ${pos}= Get Horizontal Position link=Link Should Be True ${pos} > 0 Run Keyword And Expect Error - ... ValueError: Element locator 'non-existent' did not match any elements. + ... Element with locator 'non-existent' not found. ... Get Horizontal Position non-existent Get Vertical Position ${pos}= Get Vertical Position link=Link Should Be True ${pos} > 0 Run Keyword And Expect Error - ... ValueError: Element locator 'non-existent' did not match any elements. + ... Element with locator 'non-existent' not found. ... Get Horizontal Position non-existent Get Element Size @@ -86,7 +86,7 @@ Get Element Size Should be True ${height} > 0 Should be True ${width} > 0 Run Keyword And Expect Error - ... ValueError: Element locator 'non-existent' did not match any elements. + ... Element with locator 'non-existent' not found. ... Get Element Size non-existent Get Empty Element Size diff --git a/test/acceptance/keywords/mouse.robot b/test/acceptance/keywords/mouse.robot index 5e5b4fbf4..f22fd6b06 100644 --- a/test/acceptance/keywords/mouse.robot +++ b/test/acceptance/keywords/mouse.robot @@ -10,7 +10,7 @@ Mouse Over Mouse Over el_for_mouseover Textfield Value Should Be el_for_mouseover mouseover el_for_mouseover Run Keyword And Expect Error - ... ValueError: Element locator 'not_there' did not match any elements. + ... Element with locator 'not_there' not found. ... Mouse Over not_there Mouse Out @@ -18,7 +18,7 @@ Mouse Out Mouse Out el_for_mouseout Textfield Value Should Be el_for_mouseout mouseout el_for_mouseout Run Keyword And Expect Error - ... ValueError: Element locator 'not_there' did not match any elements. + ... Element with locator 'not_there' not found. ... Mouse Out not_there Mouse Down @@ -26,7 +26,7 @@ Mouse Down Mouse Down el_for_mousedown Textfield Value Should Be el_for_mousedown mousedown el_for_mousedown Run Keyword And Expect Error - ... ValueError: Element locator 'not_there' did not match any elements. + ... Element with locator 'not_there' not found. ... Mouse Down not_there Mouse Up @@ -34,7 +34,7 @@ Mouse Up Mouse Up el_for_mouseup Textfield Value Should Be el_for_mouseup mouseup el_for_mouseup Run Keyword And Expect Error - ... ValueError: Element locator 'not_there' did not match any elements. + ... Element with locator 'not_there' not found. ... Mouse Up not_there Simulate Event diff --git a/test/acceptance/keywords/waiting.robot b/test/acceptance/keywords/waiting.robot index 386850b78..4ea5409c6 100644 --- a/test/acceptance/keywords/waiting.robot +++ b/test/acceptance/keywords/waiting.robot @@ -8,7 +8,7 @@ Wait For Condition Title Should Be Original Wait For Condition return window.document.title == "Changed" Run Keyword And Expect Error - ... Condition 'return window.document.title == "Invalid"' did not become true in 100 milliseconds + ... Condition 'return window.document.title == "Invalid"' did not become true in 100 milliseconds. ... Wait For Condition return window.document.title == "Invalid" ${0.1} Wait For Condition requires `return` @@ -18,36 +18,41 @@ Wait For Condition requires `return` Wait Until Page Contains Wait Until Page Contains New Content 2 s - Run Keyword And Expect Error Text 'invalid' did not appear in 100 milliseconds + Run Keyword And Expect Error + ... Text 'invalid' did not appear in 100 milliseconds. ... Wait Until Page Contains invalid 0.1 Wait Until Page Does Not Contain Wait Until Page Does Not Contain This is content 2 s - Run Keyword And Expect Error Text 'Initially hidden' did not disappear in 100 milliseconds + Run Keyword And Expect Error + ... Text 'Initially hidden' did not disappear in 100 milliseconds. ... Wait Until Page Does Not Contain Initially hidden 0.1 Wait Until Page Contains Element [Documentation] Tests also that format characters (e.g. %c) are handled correctly in error messages Wait Until Page Contains Element new div 2 seconds - Run Keyword And Expect Error Element '%cnon-existent' did not appear in 100 milliseconds + Run Keyword And Expect Error + ... Element '%cnon-existent' did not appear in 100 milliseconds. ... Wait Until Page Contains Element %cnon-existent 0.1 seconds Wait Until Page Does Not Contain Element [Documentation] Tests also that format characters (e.g. %c) are handled correctly in error messages Wait Until Page Does Not Contain Element not_present 2 seconds - Run Keyword And Expect Error Element 'content' did not disappear in 100 milliseconds + Run Keyword And Expect Error + ... Element 'content' did not disappear in 100 milliseconds. ... Wait Until Page Does Not Contain Element content 0.1 seconds Wait Until Element Is Visible - Run Keyword And Expect Error Element 'hidden' was not visible in 100 milliseconds - ... Wait Until Element Is Visible hidden 0.1 - Wait Until Element Is Visible hidden 2 s Run Keyword And Expect Error - ... Element locator 'invalid' did not match any elements after 100 milliseconds - ... Wait Until Element Is Visible invalid 0.1 + ... Element 'hidden' not visible after 10 milliseconds. + ... Wait Until Element Is Visible hidden 0.01 Run Keyword And Expect Error - ... User error message Wait Until Element Is Visible invalid 0.1 ... User error message + ... Wait Until Element Is Visible hidden 0.01 User error message + Wait Until Element Is Visible hidden 2 s + Run Keyword And Expect Error + ... Element with locator 'invalid' not found. + ... Wait Until Element Is Visible invalid 0.1 Wait Until Element Is Visible with locator and error arguments Wait Until Element Is Visible hidden error=My error message @@ -56,41 +61,44 @@ Wait Until Element Is Visible with locator only Wait Until Element Is Visible hidden Wait Until Element Is Enabled - Run Keyword And Expect Error Element 'id=disabled' was not enabled in 100 milliseconds - ... Wait Until Element Is Enabled id=disabled 0.1 + Run Keyword And Expect Error + ... Element 'id=disabled' was not enabled in 2 milliseconds. + ... Wait Until Element Is Enabled id=disabled 2ms + Run Keyword And Expect Error + ... User error message + ... Wait Until Element Is Enabled id=disabled 0.003 User error message Wait Until Element Is Enabled id=disabled 2 s Run Keyword And Expect Error - ... Element locator 'id=invalid' did not match any elements after 100 milliseconds + ... Element with locator 'id=invalid' not found. ... Wait Until Element Is Enabled id=invalid 0.1 - Run Keyword And Expect Error User error message Wait Until Element Is Enabled - ... id=invalid 0.1 User error message Wait Until Element Contains Run Keyword And Expect Error - ... Text 'New' did not appear in 100 milliseconds to element 'id=content'. Its text was 'This is content'. - ... Wait Until Element Contains id=content New 0.1 + ... Element 'id=content' did not get text 'New' in 1 millisecond. + ... Wait Until Element Contains id=content New 0.001 + Run Keyword And Expect Error + ... User error message + ... Wait Until Element Contains id=content New 99ms User error message Wait Until Element Contains content New Content 2 s Wait Until Element Contains content New 2 s Run Keyword And Expect Error ... User error message Wait Until Element Contains ... content Error 0.1 User error message - Run Keyword And Expect Error - ... ValueError: Element locator 'id=invalid' did not match any elements. - ... Wait Until Element Contains id=invalid content 0.1 Wait Until Element Does Not Contain Run Keyword And Expect Error - ... Text 'This is' did not disappear in 100 milliseconds from element 'id=content'. + ... Element 'id=content' still had text 'This is' after 100 milliseconds. ... Wait Until Element Does Not Contain id=content This is 0.1 Wait Until Element Does Not Contain content This is 2 s Wait Until Element Does Not Contain id=content content 2 s - Run Keyword And Expect Error User error message Wait Until Element Does Not Contain - ... content New Content 0.1 User error message + Run Keyword And Expect Error + ... User error message + ... Wait Until Element Does Not Contain content New Content 0.1 User error message Timeout can be zero Run Keyword And Expect Error - ... Text 'New Content' did not appear in 0 seconds to element 'content'. Its text was 'This is content'. + ... Element 'content' did not get text 'New Content' in 0 seconds. ... Wait Until Element Contains content New Content 0 Run Keyword And Expect Error - ... Text 'New Content' did not appear in 0 seconds to element 'content'. Its text was 'This is content'. + ... Element 'content' did not get text 'New Content' in 0 seconds. ... Wait Until Element Contains content New Content ${0} diff --git a/test/acceptance/locators/custom.robot b/test/acceptance/locators/custom.robot index 350a5c415..bd79adb4e 100644 --- a/test/acceptance/locators/custom.robot +++ b/test/acceptance/locators/custom.robot @@ -16,10 +16,14 @@ Ensure Locator Auto Unregisters Setup Custom Locator Ensure Attempting to Unregister a Default Locator Fails - Run Keyword And Expect Error * Remove Location Strategy id - -Ensure Unregistering a Non-Existent Locator Does Not Fail - Teardown Custom Locator + Run Keyword And Expect Error + ... Cannot unregister the default strategy 'id'. + ... Remove Location Strategy id + +Ensure Unregistering Non-Existent Locator Fails + Run Keyword And Expect Error + ... Cannot unregister the non-registered strategy 'non-existing'. + ... Remove Location Strategy non-existing Ensure a Custom Locator can be Unregistered Setup Custom Locator persist diff --git a/test/unit/keywords/test_keyword_arguments_element.py b/test/unit/keywords/test_keyword_arguments_element.py index 71eefbbdd..f60882bcc 100644 --- a/test/unit/keywords/test_keyword_arguments_element.py +++ b/test/unit/keywords/test_keyword_arguments_element.py @@ -15,35 +15,6 @@ def setUp(self): def tearDown(self): unstub() - def test_element_should_contain(self): - locator = '//div' - actual = 'bar' - expected = 'foo' - when(self.element)._get_text(locator).thenReturn(actual) - message = ("Element '%s' should have contained text '%s' but " - "its text was '%s'." % (locator, expected, actual)) - with self.assertRaises(AssertionError) as error: - self.element.element_should_contain('//div', expected) - self.assertEqual(str(error), message) - - with self.assertRaises(AssertionError) as error: - self.element.element_should_contain('//div', expected, 'foobar') - self.assertEqual(str(error), 'foobar') - - def test_element_should_not_contain(self): - locator = '//div' - actual = 'bar' - when(self.element)._get_text(locator).thenReturn(actual) - message = ("Element '%s' should not contain text '%s' but " - "it did." % (locator, actual)) - with self.assertRaises(AssertionError) as error: - self.element.element_should_not_contain('//div', actual) - self.assertEqual(str(error), message) - - with self.assertRaises(AssertionError) as error: - self.element.element_should_not_contain('//div', actual, 'foobar') - self.assertEqual(str(error), 'foobar') - def test_locator_should_match_x_times(self): locator = '//div' when(self.element).find_elements(locator).thenReturn([]) @@ -53,24 +24,6 @@ def test_locator_should_match_x_times(self): with self.assertRaisesRegexp(AssertionError, 'foobar'): self.element.locator_should_match_x_times(locator, 1, 'foobar') - def test_element_should_be_visible(self): - locator = '//div' - when(self.element).is_visible(locator).thenReturn(None) - with self.assertRaisesRegexp(AssertionError, 'should be visible'): - self.element.element_should_be_visible(locator) - - with self.assertRaisesRegexp(AssertionError, 'foobar'): - self.element.element_should_be_visible(locator, 'foobar') - - def test_element_should_not_be_visible(self): - locator = '//div' - when(self.element).is_visible(locator).thenReturn(True) - with self.assertRaisesRegexp(AssertionError, 'should not be visible'): - self.element.element_should_not_be_visible(locator) - - with self.assertRaisesRegexp(AssertionError, 'foobar'): - self.element.element_should_not_be_visible(locator, 'foobar') - def test_element_text_should_be(self): locator = '//div' element = mock() diff --git a/test/unit/keywords/test_keyword_arguments_formelement.py b/test/unit/keywords/test_keyword_arguments_formelement.py index 3dbcf16c0..b1f742082 100644 --- a/test/unit/keywords/test_keyword_arguments_formelement.py +++ b/test/unit/keywords/test_keyword_arguments_formelement.py @@ -33,20 +33,18 @@ def test_submit_form_true(self): def test_textfield_should_contain(self): locator = '//input' - self.ctx.element_finder = mock() - when(self.ctx.element_finder).get_value(locator, - 'text field').thenReturn('no') + element = mock() + when(self.form).find_element(locator, 'text field').thenReturn(element) + when(element).get_attribute('value').thenReturn('no') with self.assertRaisesRegexp(AssertionError, 'should have contained'): self.form.textfield_should_contain(locator, 'text') - with self.assertRaisesRegexp(AssertionError, 'foobar'): self.form.textfield_should_contain(locator, 'text', 'foobar') def test_textfield_value_should_be(self): locator = '//input' element = mock() - when(self.form).find_element(locator, tag='text field', - required=False).thenReturn(element) + when(self.form).find_element(locator, 'text field').thenReturn(element) when(element).get_attribute('value').thenReturn('no') with self.assertRaisesRegexp(AssertionError, 'text field'): self.form.textfield_value_should_be(locator, 'value') @@ -55,9 +53,9 @@ def test_textfield_value_should_be(self): def test_textarea_should_contain(self): locator = '//input' - self.ctx.element_finder = mock() - when(self.ctx.element_finder).get_value(locator, - 'text area').thenReturn('no') + element = mock() + when(self.form).find_element(locator, 'text area').thenReturn(element) + when(element).get_attribute('value').thenReturn('no') with self.assertRaisesRegexp(AssertionError, 'should have contained'): self.form.textarea_should_contain(locator, 'value') with self.assertRaisesRegexp(AssertionError, 'foobar error'): @@ -65,10 +63,10 @@ def test_textarea_should_contain(self): def test_textarea_value_should_be(self): locator = '//input' - self.ctx.element_finder = mock() - when(self.ctx.element_finder).get_value(locator, - 'text area').thenReturn('no') - with self.assertRaisesRegexp(AssertionError, 'should have been'): - self.form.textfield_value_should_be(locator, 'value') + element = mock() + when(self.form).find_element(locator, 'text area').thenReturn(element) + when(element).get_attribute('value').thenReturn('no') + with self.assertRaisesRegexp(AssertionError, 'should have had'): + self.form.textarea_value_should_be(locator, 'value') with self.assertRaisesRegexp(AssertionError, 'foobar'): - self.form.textfield_value_should_be(locator, 'value', 'foobar') + self.form.textarea_value_should_be(locator, 'value', 'foobar') diff --git a/test/unit/keywords/test_keyword_arguments_waiting.py b/test/unit/keywords/test_keyword_arguments_waiting.py index 6e307fff6..557fea397 100644 --- a/test/unit/keywords/test_keyword_arguments_waiting.py +++ b/test/unit/keywords/test_keyword_arguments_waiting.py @@ -26,7 +26,7 @@ def test_wait_for_condition(self): def test_wait_until_page_contains(self): text = 'text' - when(self.waiting.element).is_text_present(text).thenReturn(None) + when(self.waiting).is_text_present(text).thenReturn(None) with self.assertRaisesRegexp(AssertionError, "Text 'text' did not"): self.waiting.wait_until_page_contains(text) with self.assertRaisesRegexp(AssertionError, "error"): diff --git a/test/unit/locators/test_elementfinder.py b/test/unit/locators/test_elementfinder.py index df73c76ce..7d3b02e77 100644 --- a/test/unit/locators/test_elementfinder.py +++ b/test/unit/locators/test_elementfinder.py @@ -2,6 +2,7 @@ from mockito import any, mock, verify, when, unstub +from SeleniumLibrary.errors import ElementNotFound from SeleniumLibrary.locators.elementfinder import ElementFinder @@ -198,22 +199,11 @@ def setUp(self): def tearDown(self): unstub() - def test_find_with_invalid_prefix(self): - with self.assertRaises(ValueError) as error: + def test_non_exisisting_prefix(self): + with self.assertRaises(ElementNotFound): self.finder.find("something=test1") - self.assertEqual(str(error), - "Element locator with prefix 'something' " - "is not supported.") - with self.assertRaises(ValueError) as error: - self.finder.find(" by ID =test1") - self.assertEqual(str(error), - "Element locator with prefix 'by ID' is " - "not supported.") - - def test_find_with_null_browser(self): - ctx = mock() - finder = ElementFinder(ctx) - self.assertRaises(AttributeError, finder.find, None, "id=test1") + with self.assertRaises(ElementNotFound): + self.finder.find("foo:bar") def test_find_with_no_tag(self): self.finder.find("test1", required=False) @@ -322,7 +312,7 @@ def test_find_with_text_field_synonym(self): verify(self.browser).find_elements_by_xpath( "//input[@type[. = 'date' or . = 'datetime-local' or . = 'email' or " ". = 'month' or . = 'number' or . = 'password' or . = 'search' or " - ". = 'tel' or . = 'text' or . = 'time' or . = 'url' or . = 'week'] and " + ". = 'tel' or . = 'text' or . = 'time' or . = 'url' or . = 'week' or . = 'file'] and " "(@id='test1' or @name='test1' or @value='test1' or @src='test1' or " "@src='http://localhost/test1')]") @@ -499,13 +489,15 @@ def test_find_by_id_with_synonym_and_constraints(self): self.assertEqual(result, [elements[3]]) result = self.finder.find("id=test1", tag='text field', first_only=False) - self.assertEqual(result, [elements[5], elements[8]]) + self.assertEqual(result, [elements[5], elements[7], elements[8]]) result = self.finder.find("id=test1", tag='file upload', first_only=False) self.assertEqual(result, [elements[7]]) def test_find_returns_bad_values(self): # selenium.webdriver.ie.webdriver.WebDriver sometimes returns these + # and ChromeDriver has also returned None: + # https://github.com/SeleniumHQ/selenium/issues/4555 locators = ('find_elements_by_id', 'find_elements_by_name', 'find_elements_by_xpath', 'find_elements_by_link_text', 'find_elements_by_css_selector', From afcd188b5440b0708ee80594f737ddfdb28ff4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 17 Oct 2017 21:21:42 +0300 Subject: [PATCH 05/12] Small cleanup to logging --- src/SeleniumLibrary/base/librarycomponent.py | 7 ++----- src/SeleniumLibrary/keywords/browsermanagement.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/SeleniumLibrary/base/librarycomponent.py b/src/SeleniumLibrary/base/librarycomponent.py index 232db6952..b3e8d4795 100644 --- a/src/SeleniumLibrary/base/librarycomponent.py +++ b/src/SeleniumLibrary/base/librarycomponent.py @@ -25,9 +25,6 @@ from .robotlibcore import PY2 -LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] - - class LibraryComponent(ContextAware): def info(self, msg, html=False): @@ -37,8 +34,8 @@ def debug(self, msg, html=False): logger.debug(msg, html) def log(self, msg, level='INFO', html=False): - if level.upper() in LOG_LEVELS: - logger.write(msg, level, html) + if not is_noney(level): + logger.write(msg, level.upper(), html) def warn(self, msg, html=False): logger.warn(msg, html) diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 19443cabe..389036a3f 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -459,7 +459,7 @@ def log_source(self, loglevel='INFO'): (no logging). """ source = self.get_source() - self.log(source, loglevel.upper()) + self.log(source, loglevel) return source @keyword From c30f82558c8a46289ede8e4a46a18aeadd9f7efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 17 Oct 2017 23:11:34 +0300 Subject: [PATCH 06/12] Move frame related kws to own lib component --- src/SeleniumLibrary/__init__.py | 10 +- src/SeleniumLibrary/keywords/__init__.py | 1 + .../keywords/browsermanagement.py | 29 ----- src/SeleniumLibrary/keywords/element.py | 58 ---------- src/SeleniumLibrary/keywords/frames.py | 108 ++++++++++++++++++ 5 files changed, 115 insertions(+), 91 deletions(-) create mode 100644 src/SeleniumLibrary/keywords/frames.py diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index e34a63f3d..ddd0acc47 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -25,6 +25,7 @@ CookieKeywords, ElementKeywords, FormElementKeywords, + FrameKeywords, JavaScriptKeywords, RunOnFailureKeywords, ScreenshotKeywords, @@ -329,14 +330,15 @@ def __init__(self, timeout=5.0, implicit_wait=0.0, libraries = [ AlertKeywords(self), BrowserManagementKeywords(self), - RunOnFailureKeywords(self), + CookieKeywords(self), ElementKeywords(self), - TableElementKeywords(self), FormElementKeywords(self), - SelectElementKeywords(self), + FrameKeywords(self), JavaScriptKeywords(self), - CookieKeywords(self), + RunOnFailureKeywords(self), ScreenshotKeywords(self), + SelectElementKeywords(self), + TableElementKeywords(self), WaitingKeywords(self) ] self._browsers = BrowserCache() diff --git a/src/SeleniumLibrary/keywords/__init__.py b/src/SeleniumLibrary/keywords/__init__.py index 19a67584a..5776bc2a1 100644 --- a/src/SeleniumLibrary/keywords/__init__.py +++ b/src/SeleniumLibrary/keywords/__init__.py @@ -19,6 +19,7 @@ from .cookie import CookieKeywords from .element import ElementKeywords from .formelement import FormElementKeywords +from .frames import FrameKeywords from .javascript import JavaScriptKeywords from .runonfailure import RunOnFailureKeywords from .screenshot import ScreenshotKeywords diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 389036a3f..1bb0e73b4 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -327,27 +327,6 @@ def set_window_position(self, x, y): """ self.browser.set_window_position(int(x), int(y)) - @keyword - def select_frame(self, locator): - """Sets frame identified by ``locator`` as the current frame. - - Key attributes for frames are `id` and `name.` See `introduction` for - details about locating elements. - - See `Unselect Frame` to cancel the frame selection and return to the Main frame. - - Please note that the frame search always start from the document root or main frame. - - Example: - | Select Frame | xpath: //frame[@name='top]/iframe[@name='left'] | # Selects the 'left' iframe | - | Click Link | foo | # Clicks link 'foo' in 'left' iframe | - | Unselect Frame | | # Returns to main frame | - | Select Frame | left | # Selects the 'top' frame | - """ - self.info("Selecting frame '%s'." % locator) - element = self.find_element(locator) - self.browser.switch_to.frame(element) - @keyword def select_window(self, locator=None): """Selects the window matching locator and return previous window handle. @@ -395,14 +374,6 @@ def list_windows(self): """Return all current window handles as a list.""" return self.browser.window_handles - @keyword - def unselect_frame(self): - """Sets the top frame as the current frame. - - In practice cancels a previous `Select Frame` call. - """ - self.browser.switch_to.default_content() - @keyword def get_location(self): """Returns the current browser URL.""" diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index a44169ddc..8b436e68c 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -46,40 +46,6 @@ def get_webelements(self, locator): """ return self.find_elements(locator) - @keyword - def current_frame_should_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains ``text``. - - See `Page Should Contain` for explanation about the ``loglevel`` - argument. - - Prior to SeleniumLibrary 3.0 this keyword was named - `Current Frame Contains`. - """ - if not self.is_text_present(text): - self.ctx.log_source(loglevel) - raise AssertionError("Frame should have contained text '%s' " - "but did not." % text) - self.info("Current frame contains text '%s'." % text) - - @keyword - def current_frame_contains(self, text, loglevel='INFO'): - """Deprecated. Use `Current Frame Should Contain` instead.""" - self.current_frame_should_contain(text, loglevel) - - @keyword - def current_frame_should_not_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains ``text``. - - See `Page Should Contain` for explanation about the ``loglevel`` - argument. - """ - if self.is_text_present(text): - self.ctx.log_source(loglevel) - raise AssertionError("Frame should not have contained text '%s' " - "but it did." % text) - self.info("Current frame did not contain text '%s'." % text) - @keyword def element_should_contain(self, locator, expected, message=None): """Verifies that element ``locator`` contains text ``expected``. @@ -120,22 +86,6 @@ def element_should_not_contain(self, locator, expected, message=None): self.info("Element '%s' does not contain text '%s'." % (locator, expected)) - @keyword - def frame_should_contain(self, locator, text, loglevel='INFO'): - """Verifies that frame identified by ``locator`` contains ``text``. - - See the `Locating elements` section for details about the locator - syntax. - - See `Page Should Contain` for explanation about the ``loglevel`` - argument. - """ - if not self._frame_contains(locator, text): - self.ctx.log_source(loglevel) - raise AssertionError("Frame '%s' should have contained text '%s' " - "but did not." % (locator, text)) - self.info("Frame '%s' contains text '%s'." % (locator, text)) - @keyword def page_should_contain(self, text, loglevel='INFO'): """Verifies that current page contains ``text``. @@ -780,14 +730,6 @@ def remove_location_strategy(self, strategy_name): """ self.element_finder.unregister(strategy_name) - def _frame_contains(self, locator, text): - element = self.find_element(locator) - self.browser.switch_to.frame(element) - self.info("Searching for text from frame '%s'." % locator) - found = self.is_text_present(text) - self.browser.switch_to.default_content() - return found - def _is_enabled(self, locator): element = self.find_element(locator) if element.tag_name.lower() not in {'input', 'select', 'textarea', diff --git a/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py new file mode 100644 index 000000000..cd02bce2c --- /dev/null +++ b/src/SeleniumLibrary/keywords/frames.py @@ -0,0 +1,108 @@ +# Copyright 2008-2011 Nokia Networks +# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors +# Copyright 2016- Robot Framework Foundation +# +# 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. + +from SeleniumLibrary.base import LibraryComponent, keyword + + +class FrameKeywords(LibraryComponent): + + @keyword + def select_frame(self, locator): + """Sets frame identified by ``locator`` as the current frame. + + Key attributes for frames are `id` and `name.` See `introduction` for + details about locating elements. + + See `Unselect Frame` to cancel the frame selection and return to the Main frame. + + Please note that the frame search always start from the document root or main frame. + + Example: + | Select Frame | xpath: //frame[@name='top]/iframe[@name='left'] | # Selects the 'left' iframe | + | Click Link | foo | # Clicks link 'foo' in 'left' iframe | + | Unselect Frame | | # Returns to main frame | + | Select Frame | left | # Selects the 'top' frame | + """ + self.info("Selecting frame '%s'." % locator) + element = self.find_element(locator) + self.browser.switch_to.frame(element) + + @keyword + def unselect_frame(self): + """Sets the top frame as the current frame. + + In practice cancels a previous `Select Frame` call. + """ + self.browser.switch_to.default_content() + + @keyword + def current_frame_should_contain(self, text, loglevel='INFO'): + """Verifies that current frame contains ``text``. + + See `Page Should Contain` for explanation about the ``loglevel`` + argument. + + Prior to SeleniumLibrary 3.0 this keyword was named + `Current Frame Contains`. + """ + if not self.is_text_present(text): + self.log_source(loglevel) + raise AssertionError("Frame should have contained text '%s' " + "but did not." % text) + self.info("Current frame contains text '%s'." % text) + + @keyword + def current_frame_contains(self, text, loglevel='INFO'): + """Deprecated. Use `Current Frame Should Contain` instead.""" + self.current_frame_should_contain(text, loglevel) + + @keyword + def current_frame_should_not_contain(self, text, loglevel='INFO'): + """Verifies that current frame contains ``text``. + + See `Page Should Contain` for explanation about the ``loglevel`` + argument. + """ + if self.is_text_present(text): + self.log_source(loglevel) + raise AssertionError("Frame should not have contained text '%s' " + "but it did." % text) + self.info("Current frame did not contain text '%s'." % text) + + @keyword + def frame_should_contain(self, locator, text, loglevel='INFO'): + """Verifies that frame identified by ``locator`` contains ``text``. + + See the `Locating elements` section for details about the locator + syntax. + + See `Page Should Contain` for explanation about the ``loglevel`` + argument. + """ + if not self._frame_contains(locator, text): + self.log_source(loglevel) + raise AssertionError("Frame '%s' should have contained text '%s' " + "but did not." % (locator, text)) + self.info("Frame '%s' contains text '%s'." % (locator, text)) + + def _frame_contains(self, locator, text): + element = self.find_element(locator) + self.browser.switch_to.frame(element) + self.info("Searching for text from frame '%s'." % locator) + found = self.is_text_present(text) + self.browser.switch_to.default_content() + return found + From b78a22c2296f9b5641308085cc541621b0fc377e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Wed, 18 Oct 2017 11:56:04 +0300 Subject: [PATCH 07/12] Consistent handling of enabled status. "Element Should (Not) Be Enabled" and "Wait Until Element Is Enabled" now all validate that the element is enabled and not readonly. Also removed broken element type validation from the former keywords. Fixes #958. --- src/SeleniumLibrary/base/context.py | 5 ++++ src/SeleniumLibrary/keywords/element.py | 16 ++---------- src/SeleniumLibrary/keywords/waiting.py | 2 +- ...ement_should_be_enabled_and_disabled.robot | 25 +++++++------------ test/acceptance/keywords/waiting.robot | 6 +++++ .../forms/enabled_disabled_fields_form.html | 24 ++++++++++++++---- .../html/javascript/delayed_events.html | 11 ++++++-- 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/SeleniumLibrary/base/context.py b/src/SeleniumLibrary/base/context.py index 103a3fbb6..061d1ed75 100644 --- a/src/SeleniumLibrary/base/context.py +++ b/src/SeleniumLibrary/base/context.py @@ -77,3 +77,8 @@ def find_elements(self, locator, tag=None, parent=None): def is_text_present(self, text): locator = "xpath://*[contains(., %s)]" % escape_xpath_value(text) return self.find_element(locator, required=False) is not None + + def is_element_enabled(self, locator, tag=None): + element = self.find_element(locator, tag) + return (element.is_enabled() and + element.get_attribute('readonly') is None) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 8b436e68c..a379da354 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -191,7 +191,7 @@ def element_should_be_disabled(self, locator): See the `Locating elements` section for details about the locator syntax. """ - if self._is_enabled(locator): + if self.is_element_enabled(locator): raise AssertionError("Element '%s' is enabled." % locator) @keyword @@ -201,7 +201,7 @@ def element_should_be_enabled(self, locator): See the `Locating elements` section for details about the locator syntax. """ - if not self._is_enabled(locator): + if not self.is_element_enabled(locator): raise AssertionError("Element '%s' is disabled." % locator) @keyword @@ -730,18 +730,6 @@ def remove_location_strategy(self, strategy_name): """ self.element_finder.unregister(strategy_name) - def _is_enabled(self, locator): - element = self.find_element(locator) - if element.tag_name.lower() not in {'input', 'select', 'textarea', - 'button', 'option'}: - raise ValueError("Element '%s' is '%s', not an input element." - % (locator, element.tag_name)) - if not element.is_enabled(): - return False - if element.get_attribute('readonly') in {'readonly', 'true'}: - return False - return True - def _map_ascii_key_code_to_key(self, key_code): map = { 0: Keys.NULL, diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index c8f49f48c..4b9b3f34d 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -180,7 +180,7 @@ def wait_until_element_is_enabled(self, locator, timeout=None, error=None): Succeeds`. """ self._wait_until( - lambda: not self.find_element(locator).get_attribute('disabled'), + lambda: self.is_element_enabled(locator), "Element '%s' was not enabled in ." % locator, timeout, error ) diff --git a/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot b/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot index ca3de54ff..9a12a40f1 100644 --- a/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot +++ b/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot @@ -1,57 +1,50 @@ *** Settings *** -Documentation Tests disabled elements +Documentation Tests disabled and readonly elements Test Setup Go To Page "forms/enabled_disabled_fields_form.html" Resource ../resource.robot *** Test Cases *** Input Text - [Documentation] Input Text Should Be Enabled Not Disabled enabled_input Should Be Disabled Not Enabled readonly_input Should Be Disabled Not Enabled disabled_input Input Password - [Documentation] Input Password Should Be Enabled Not Disabled enabled_password Should Be Disabled Not Enabled readonly_password Should Be Disabled Not Enabled disabled_password Input Button - [Documentation] Input Button Should Be Enabled Not Disabled enabled_input_button Should Be Disabled Not Enabled disabled_input_button Textarea - [Documentation] Textarea Should Be Enabled Not Disabled enabled_textarea Should Be Disabled Not Enabled readonly_textarea Should Be Disabled Not Enabled disabled_textarea Button - [Documentation] Button Should Be Enabled Not Disabled enabled_button Should Be Disabled Not Enabled disabled_button Option - [Documentation] Option Should Be Enabled Not Disabled enabled_option Should Be Disabled Not Enabled disabled_option Disabled with different syntaxes - [Documentation] Disabled with different syntaxes Should Be Disabled Not Enabled disabled_only - Should Be Disabled Not Enabled disabled_with_equals_sign + Should Be Disabled Not Enabled disabled_with_equals_only Should Be Disabled Not Enabled disabled_empty Should Be Disabled Not Enabled disabled_invalid_value +Readonly with different syntaxes + Should Be Disabled Not Enabled readonly_only + Should Be Disabled Not Enabled readonly_with_equals_only + Should Be Disabled Not Enabled readonly_empty + Should Be Disabled Not Enabled readonly_invalid_value + Not Input nor Editable Element - [Documentation] Not Input nor Editable Element - Run Keyword And Expect Error - ... ValueError: Element 'table1' is 'table', not an input element. - ... Element Should Be Enabled table1 - Run Keyword And Expect Error - ... ValueError: Element 'table1' is 'table', not an input element. - ... Element Should Be Disabled table1 + Should Be Enabled Not Disabled table1 *** Keywords *** Should Be Enabled Not Disabled diff --git a/test/acceptance/keywords/waiting.robot b/test/acceptance/keywords/waiting.robot index 4ea5409c6..0101082d6 100644 --- a/test/acceptance/keywords/waiting.robot +++ b/test/acceptance/keywords/waiting.robot @@ -72,6 +72,12 @@ Wait Until Element Is Enabled ... Element with locator 'id=invalid' not found. ... Wait Until Element Is Enabled id=invalid 0.1 +Wait Until Element Is Enabled with readonly element + Run Keyword And Expect Error + ... Element 'readonly' was not enabled in 2 milliseconds. + ... Wait Until Element Is Enabled readonly 2ms + Wait Until Element Is Enabled readonly 2 s + Wait Until Element Contains Run Keyword And Expect Error ... Element 'id=content' did not get text 'New' in 1 millisecond. diff --git a/test/resources/html/forms/enabled_disabled_fields_form.html b/test/resources/html/forms/enabled_disabled_fields_form.html index 6f3e31aa7..bcbfb93b7 100644 --- a/test/resources/html/forms/enabled_disabled_fields_form.html +++ b/test/resources/html/forms/enabled_disabled_fields_form.html @@ -11,11 +11,11 @@ Readonly input: - + Disabled input: - + Enabled password: @@ -23,7 +23,7 @@ Readonly password: - + Disabled password: @@ -38,7 +38,7 @@ @@ -70,7 +70,7 @@ disabled= - + disabled="" @@ -79,6 +79,20 @@ disabled="kekkonen" + readonly + + + + readonly= + + + + readonly="" + + + + readonly="kekkonen" + diff --git a/test/resources/html/javascript/delayed_events.html b/test/resources/html/javascript/delayed_events.html index 91afd1d6f..c781163dd 100644 --- a/test/resources/html/javascript/delayed_events.html +++ b/test/resources/html/javascript/delayed_events.html @@ -9,6 +9,7 @@ setTimeout('changeTitle()', 1000) setTimeout('unhideContent()', 1000) setTimeout('enableContent()', 1000) + setTimeout('readwriteContent()', 1000) } function addElement() { @@ -26,7 +27,7 @@ document.title = "Changed" document.bgColor = '#FF00FF' } - + function changeContent() { document.getElementById("content").firstChild.nodeValue = "New Content" } @@ -34,9 +35,14 @@ function unhideContent() { document.getElementById("hidden").style.display = "block" } + function enableContent() { document.getElementById("disabled").disabled = false; } + + function readwriteContent() { + document.getElementById("readonly").readOnly = false; + } @@ -46,5 +52,6 @@
Element that should disappear
+ - \ No newline at end of file + From eac3ecc69a812cbae16f80e6524a57a7543104f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Wed, 18 Oct 2017 14:07:02 +0300 Subject: [PATCH 08/12] Doc that elements considered enabled cannot be read-only --- src/SeleniumLibrary/keywords/element.py | 6 ++++++ src/SeleniumLibrary/keywords/waiting.py | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index a379da354..c9af2853c 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -188,6 +188,9 @@ def assign_id_to_element(self, locator, id): def element_should_be_disabled(self, locator): """Verifies that element identified with ``locator`` is disabled. + This keyword considers also elements that are read-only to be + disabled. + See the `Locating elements` section for details about the locator syntax. """ @@ -198,6 +201,9 @@ def element_should_be_disabled(self, locator): def element_should_be_enabled(self, locator): """Verifies that element identified with ``locator`` is enabled. + This keyword considers also elements that are read-only to be + disabled. + See the `Locating elements` section for details about the locator syntax. """ diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index 4b9b3f34d..cd1fb1caa 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -169,8 +169,9 @@ def wait_until_element_is_not_visible(self, locator, timeout=None, error=None): def wait_until_element_is_enabled(self, locator, timeout=None, error=None): """Waits until element specified with `locator` is enabled. - Fails if `timeout` expires before the element is enabled. See - `introduction` for more information about `timeout` and its + Fails if `timeout` expires before the element is enabled. Element + is considered enabled if it is not disabled nor read-only. + See `introduction` for more information about `timeout` and its default value. `error` can be used to override the default error message. @@ -178,6 +179,9 @@ def wait_until_element_is_enabled(self, locator, timeout=None, error=None): See also `Wait Until Page Contains`, `Wait Until Page Contains Element`, `Wait For Condition` and BuiltIn keyword `Wait Until Keyword Succeeds`. + + Considering read-only elements to be disabled is a new feature + in SeleniumLibrary 3.0. """ self._wait_until( lambda: self.is_element_enabled(locator), From 44cba5259b73335af2b580bf7fcc58ca8b7c6b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 19 Oct 2017 14:27:42 +0300 Subject: [PATCH 09/12] Base class for SeLib exceptions --- src/SeleniumLibrary/errors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/errors.py b/src/SeleniumLibrary/errors.py index 2e66b3b1c..8217f34ec 100644 --- a/src/SeleniumLibrary/errors.py +++ b/src/SeleniumLibrary/errors.py @@ -15,5 +15,9 @@ # limitations under the License. -class ElementNotFound(Exception): +class SeleniumLibraryException(Exception): ROBOT_SUPPRESS_NAME = True + + +class ElementNotFound(SeleniumLibraryException): + pass From 8106c837dc89723603321f454c314205f88931f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 19 Oct 2017 14:33:42 +0300 Subject: [PATCH 10/12] Doc fixes based on review comments --- src/SeleniumLibrary/keywords/element.py | 4 ++-- src/SeleniumLibrary/keywords/frames.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index c9af2853c..f39b4ae40 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -439,7 +439,7 @@ def focus(self, locator): @keyword def drag_and_drop(self, locator, target): - """Drags ``source`` element into ``target`` element. + """Drags element identified by ``locator`` into ``target`` element. The ``locator`` argument is the locator of the dragged element and the ``target`` is the locator of the target. See the @@ -631,7 +631,7 @@ def page_should_contain_link(self, locator, message=None, loglevel='INFO'): @keyword def page_should_not_contain_link(self, locator, message=None, loglevel='INFO'): - """Verifies image identified by ``locator`` is not found from current page. + """Verifies link identified by ``locator`` is not found from current page. See the `Locating elements` section for details about the locator syntax. Key attributes for links are ``id``, ``name``, ``href`` and diff --git a/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py index cd02bce2c..0587e5401 100644 --- a/src/SeleniumLibrary/keywords/frames.py +++ b/src/SeleniumLibrary/keywords/frames.py @@ -71,7 +71,7 @@ def current_frame_contains(self, text, loglevel='INFO'): @keyword def current_frame_should_not_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains ``text``. + """Verifies that current frame does not contains ``text``. See `Page Should Contain` for explanation about the ``loglevel`` argument. From ee68369f3edf1b52452183c22c4b9b0f59e666de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 19 Oct 2017 14:33:49 +0300 Subject: [PATCH 11/12] Test "Page Should Contain" with text spanning multiple elements. --- test/acceptance/keywords/content_assertions.robot | 5 +++++ test/resources/html/index.html | 4 ++-- test/resources/html/links.html | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/acceptance/keywords/content_assertions.robot b/test/acceptance/keywords/content_assertions.robot index f139f390d..761c9a41f 100644 --- a/test/acceptance/keywords/content_assertions.robot +++ b/test/acceptance/keywords/content_assertions.robot @@ -35,6 +35,11 @@ Page Should Contain ... Page should have contained text 'non existing text' but did not. ... Page Should Contain non existing text +Page Should Contain with text having internal elements + Page Should Contain This is the haystack and somewhere on this page is a needle. + Go to page "links.html" + Page Should Contain Relative with text after + Page Should Contain With Custom Log Level [Documentation] LOG 2.1:10 DEBUG REGEXP: (?i) Run Keyword And Expect Error diff --git a/test/resources/html/index.html b/test/resources/html/index.html index 68a6645c9..0651e3b74 100644 --- a/test/resources/html/index.html +++ b/test/resources/html/index.html @@ -3,9 +3,9 @@ (root)/index.html -

This is the haystack and somewhere on this page is a needle.

+

This is the haystack and somewhere on this page is a needle.

This is more text

This text is inside an identified element - \ No newline at end of file + diff --git a/test/resources/html/links.html b/test/resources/html/links.html index bf9c0eafc..2f163adcf 100644 --- a/test/resources/html/links.html +++ b/test/resources/html/links.html @@ -5,7 +5,7 @@
- Relative
+ Relative with text after
Relative to sub-directory
Absolute external link
Link with id
From 7bc39a99048c3bbe2bf97a01bf22c0d4601b54ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Thu, 19 Oct 2017 15:23:38 +0300 Subject: [PATCH 12/12] Little more cleanup. - User `find_elements` instead of `find_element` when possible. - Avoid `strategy=value` locator syntax in code. - Enhance error messages when radio buttons not found. - Cleanup `Choose File` test and test it with Firefox. - TableElementFinder._locator to public class variable. - Remove useless documentation. --- src/SeleniumLibrary/keywords/element.py | 3 +- src/SeleniumLibrary/keywords/formelement.py | 19 +++++++--- .../locators/tableelementfinder.py | 36 +++++++++---------- .../keywords/checkbox_and_radio_buttons.robot | 9 ++++- .../keywords/forms_and_buttons.robot | 18 +++------- .../test_keyword_arguments_element.py | 4 +-- 6 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index f39b4ae40..237b8a4f7 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -703,8 +703,7 @@ def get_matching_xpath_count(self, xpath, return_str=True): Example: | count = | `Get Matching Xpath Count` | //div[@id='sales-pop'] | """ - count = len(self.find_element("xpath:" + xpath, first_only=False, - required=False)) + count = len(self.find_elements('xpath:' + xpath)) return str(count) if is_truthy(return_str) else count @keyword diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 46f86c7ee..e84ad2b0f 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -17,6 +17,7 @@ import os from SeleniumLibrary.base import LibraryComponent, keyword +from SeleniumLibrary.errors import ElementNotFound from SeleniumLibrary.utils import is_noney @@ -369,15 +370,23 @@ def _get_checkbox(self, locator): return self.find_element(locator, tag='input') def _get_radio_buttons(self, group_name): - xpath = "xpath=//input[@type='radio' and @name='%s']" % group_name + xpath = "xpath://input[@type='radio' and @name='%s']" % group_name self.debug('Radio group locator: ' + xpath) - return self.find_element(xpath, first_only=False) + elements = self.find_elements(xpath) + if not elements: + raise ElementNotFound("No radio button with name '%s' found." + % group_name) + return elements def _get_radio_button_with_value(self, group_name, value): - xpath = "xpath=//input[@type='radio' and @name='%s' and (@value='%s' or @id='%s')]" \ - % (group_name, value, value) + xpath = "xpath://input[@type='radio' and @name='%s' and " \ + "(@value='%s' or @id='%s')]" % (group_name, value, value) self.debug('Radio group locator: ' + xpath) - return self.find_element(xpath) + try: + return self.find_element(xpath) + except ElementNotFound: + raise ElementNotFound("No radio button with name '%s' and " + "value '%s' found." % (group_name, value)) def _get_value_from_radio_buttons(self, elements): for element in elements: diff --git a/src/SeleniumLibrary/locators/tableelementfinder.py b/src/SeleniumLibrary/locators/tableelementfinder.py index dc4b18e3a..3d86eada9 100644 --- a/src/SeleniumLibrary/locators/tableelementfinder.py +++ b/src/SeleniumLibrary/locators/tableelementfinder.py @@ -18,30 +18,27 @@ class TableElementFinder(ContextAware): - - def __init__(self, ctx): - ContextAware.__init__(self, ctx) - self._locators = { - 'content': ['//*'], - 'header': ['//th'], - 'footer': ['//tfoot//td'], - 'row': ['//tr[{row}]//*'], - 'last-row': ['//tbody/tr[position()=last()-({row}-1)]'], - 'col': ['//tr//*[self::td or self::th][{col}]'], - 'last-col': ['//tbody/tr/td[position()=last()-({col}-1)]', - '//tbody/tr/td[position()=last()-({col}-1)]'] - } + locators = { + 'content': ['//*'], + 'header': ['//th'], + 'footer': ['//tfoot//td'], + 'row': ['//tr[{row}]//*'], + 'last-row': ['//tbody/tr[position()=last()-({row}-1)]'], + 'col': ['//tr//*[self::td or self::th][{col}]'], + 'last-col': ['//tbody/tr/td[position()=last()-({col}-1)]', + '//tbody/tr/td[position()=last()-({col}-1)]'] + } def find_by_content(self, table_locator, content): - locators = self._locators['content'] + locators = self.locators['content'] return self._search_in_locators(table_locator, locators, content) def find_by_header(self, table_locator, content): - locators = self._locators['header'] + locators = self.locators['header'] return self._search_in_locators(table_locator, locators, content) def find_by_footer(self, table_locator, content): - locators = self._locators['footer'] + locators = self.locators['footer'] return self._search_in_locators(table_locator, locators, content) def find_by_row(self, table_locator, row, content): @@ -51,7 +48,7 @@ def find_by_row(self, table_locator, row, content): row = row[1:] location_method = "last-row" locators = [locator.format(row=row) - for locator in self._locators[location_method]] + for locator in self.locators[location_method]] return self._search_in_locators(table_locator, locators, content) def find_by_col(self, table_locator, col, content): @@ -61,14 +58,13 @@ def find_by_col(self, table_locator, col, content): col = col[1:] location_method = "last-col" locators = [locator.format(col=col) - for locator in self._locators[location_method]] + for locator in self.locators[location_method]] return self._search_in_locators(table_locator, locators, content) def _search_in_locators(self, table_locator, locators, content): table = self.find_element(table_locator) for locator in locators: - elements = self.find_element(locator, first_only=False, - required=False, parent=table) + elements = self.find_elements(locator, parent=table) for element in elements: if content is None: return element diff --git a/test/acceptance/keywords/checkbox_and_radio_buttons.robot b/test/acceptance/keywords/checkbox_and_radio_buttons.robot index 7ce2218bd..f81204929 100644 --- a/test/acceptance/keywords/checkbox_and_radio_buttons.robot +++ b/test/acceptance/keywords/checkbox_and_radio_buttons.robot @@ -54,7 +54,14 @@ Radio Button Should Not Be Selected ... Radio Button Should Not Be Selected sex Clicking Radio Button Should Trigger Onclick Event - [Documentation] Clicking Radio Button Should Trigger Onclick Event [Setup] Go To Page "javascript/dynamic_content.html" Select Radio Button group title Title Should Be Changed by Button + +Radio button not found + Run Keyword And Expect Error + ... No radio button with name 'nonex' and value 'whatever' found. + ... Select Radio Button nonex whatever + Run Keyword And Expect Error + ... No radio button with name 'nonex' found. + ... Radio button should be set to nonex whatever diff --git a/test/acceptance/keywords/forms_and_buttons.robot b/test/acceptance/keywords/forms_and_buttons.robot index 0e1e65c92..89a1b72f7 100644 --- a/test/acceptance/keywords/forms_and_buttons.robot +++ b/test/acceptance/keywords/forms_and_buttons.robot @@ -1,5 +1,4 @@ *** Settings *** -Documentation Tests forms and buttons Test Setup Go To Page "forms/named_submit_buttons.html" Resource ../resource.robot Library OperatingSystem @@ -14,7 +13,6 @@ Submit Form Verify Location Is "${FORM SUBMITTED}" Submit Form Without Args - [Documentation] Submit Form Without Args [Setup] Go To Page "forms/form_without_name.html" Submit Form Verify Location Is "target/first.html" @@ -25,28 +23,23 @@ Click Ok Button By Name Verify Location Is "${FORM SUBMITTED}" Click Cancel Button By Name - [Documentation] Click Cancel Button By Name Click Button cancel_button Value Should Be Cancel Click Ok Button By Value - [Documentation] Click Ok Button By Value Click Button Ok Verify Location Is "${FORM SUBMITTED}" Click Cancel Button By Value - [Documentation] Click Cancel Button By Value Click Button Cancel Value Should Be Cancel Click button created with