diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index bbb2b4757..ddd0acc47 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -25,6 +25,7 @@ CookieKeywords, ElementKeywords, FormElementKeywords, + FrameKeywords, JavaScriptKeywords, RunOnFailureKeywords, ScreenshotKeywords, @@ -64,10 +65,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 == @@ -100,7 +101,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 +109,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 +136,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 +172,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. @@ -316,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/base/context.py b/src/SeleniumLibrary/base/context.py index 52e16e160..061d1ed75 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,11 +39,46 @@ 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) - @property - def table_element_finder(self): - return self.ctx.table_element_finder + 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) + + 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/base/librarycomponent.py b/src/SeleniumLibrary/base/librarycomponent.py index 1273fe366..b3e8d4795 100644 --- a/src/SeleniumLibrary/base/librarycomponent.py +++ b/src/SeleniumLibrary/base/librarycomponent.py @@ -25,14 +25,8 @@ from .robotlibcore import PY2 -LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'] - - class LibraryComponent(ContextAware): - def __init__(self, ctx): - ContextAware.__init__(self, ctx) - def info(self, msg, html=False): logger.info(msg, html) @@ -40,21 +34,36 @@ 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) + 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..8217f34ec --- /dev/null +++ b/src/SeleniumLibrary/errors.py @@ -0,0 +1,23 @@ +# 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 SeleniumLibraryException(Exception): + ROBOT_SUPPRESS_NAME = True + + +class ElementNotFound(SeleniumLibraryException): + pass 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 327f573ba..1bb0e73b4 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 @@ -328,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. @@ -396,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.""" @@ -431,8 +401,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 @@ -460,7 +430,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 diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index fb29e7958..237b8a4f7 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -18,240 +18,206 @@ 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 +from SeleniumLibrary.utils import (is_falsy, is_noney, is_truthy, + plural_or_not as s) 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. + """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. - - See `introduction` for details about locating elements. - - 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. - """ - 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`. - - See `Page Should Contain ` for explanation about `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) - - @keyword - def current_frame_contains(self, text, loglevel='INFO'): - """Deprecated. Use `Current Frame Should Contain` instead.""" - self.current_frame_should_contain(text, loglevel) + """Returns list of WebElement objects matching the ``locator``. - @keyword - def current_frame_should_not_contain(self, text, loglevel='INFO'): - """Verifies that current frame contains `text`. + See the `Locating elements` section for details about the locator + syntax. - See `Page Should Contain ` for explanation about `loglevel` argument. + 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. """ - 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) + return self.find_elements(locator) @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)) - actual = self._get_text(locator) + actual = self.find_element(locator).text 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) + self.info("Element '%s' contains text '%s'." % (locator, expected)) @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) + actual = self.find_element(locator).text 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`. - - See `Page Should Contain ` for explanation about `loglevel` argument. - - Key attributes for frames are `id` and `name.` See `introduction` for - details about locating elements. - """ - 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) + self.info("Element '%s' does not contain text '%s'." + % (locator, expected)) @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. + + This keyword considers also elements that are read-only to be + 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)) + if self.is_element_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. + + This keyword considers also elements that are read-only to be + 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 not self._is_enabled(locator): - raise AssertionError("Element '%s' is disabled." % (locator)) + if not self.is_element_enabled(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": @@ -259,187 +225,182 @@ 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 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=''): - """Verifies that the element identified by `locator` is NOT visible. - - This is the opposite of `Element Should Be Visible`. + def element_should_not_be_visible(self, locator, message=None): + """Verifies that the element identified by ``locator`` is NOT 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. + 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: - if is_falsy(message): + 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) 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): + text = self.find_element(locator).text + if 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, 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) + return self.get_element_attribute(locator, 'value') @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) + return self.find_element(locator).text @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) @@ -449,10 +410,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) @@ -461,7 +422,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`. """ @@ -474,68 +438,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 element identified by ``locator`` 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 @@ -545,46 +506,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`. """ @@ -606,16 +564,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:])) @@ -624,14 +586,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): @@ -639,54 +601,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 link 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) @@ -697,108 +658,71 @@ 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. - 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``. + + 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` - - The default return type is `str` but it can changed to `int` by setting - the ``return_str`` argument to Python False. + """Returns number of elements matching ``xpath``. - One should not use the xpath= prefix for 'xpath'. XPath is assumed. + The default return type is string, but it can changed to an integer + by setting the ``return_str`` argument to a false value. - Correct: - | count = | Get Matching Xpath Count | //div[@id='sales-pop'] - Incorrect: - | count = | Get Matching Xpath Count | xpath=//div[@id='sales-pop'] + The ``xpath`` should not contain ``xpath:`` prefix. - 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, - required=False)) + count = len(self.find_elements('xpath:' + xpath)) 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. - - Trying to add a custom location strategy with the same name as one that - already exists will cause the keyword to fail. - - Custom locator keyword example: - | Custom Locator Strategy | - | | [Arguments] | ${browser} | ${criteria} | ${tag} | ${constraints} | - | | ${retVal}= | Execute Javascript | return window.document.getElementById('${criteria}'); | - | | [Return] | ${retVal} | + """Adds a custom location strategy. - Usage example: - | Add Location Strategy | custom | Custom Locator Strategy | - | Page Should Contain Element | custom=my_id | + See `Custom locators` for information how to create and use + custom strategies. `Remove Location Strategy` can be used to + remove a registered strategy. - 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) @@ -806,47 +730,11 @@ 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) - 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 _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 not self.form_element._is_form_element(element): - raise AssertionError("ERROR: Element %s is not an input." % locator) - if not element.is_enabled(): - return False - read_only = element.get_attribute('readonly') - if read_only == 'readonly' or read_only == 'true': - 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, @@ -880,24 +768,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 8abc40e9c..e84ad2b0f 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -17,14 +17,12 @@ import os from SeleniumLibrary.base import LibraryComponent, keyword -from SeleniumLibrary.utils import is_falsy +from SeleniumLibrary.errors import ElementNotFound +from SeleniumLibrary.utils import is_noney class FormElementKeywords(LibraryComponent): - def __init__(self, ctx): - LibraryComponent.__init__(self, ctx) - @keyword def submit_form(self, locator=None): """Submits a form identified by `locator`. @@ -34,8 +32,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() @@ -50,7 +48,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): @@ -62,11 +60,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 @@ -78,7 +76,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 @@ -116,7 +114,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 @@ -128,7 +126,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 @@ -153,7 +151,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 @@ -167,8 +165,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 @@ -204,8 +202,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 @@ -216,7 +214,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 @@ -225,11 +223,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 @@ -241,7 +239,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 @@ -253,7 +251,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. @@ -261,16 +259,16 @@ def textfield_should_contain(self, locator, expected, message=''): 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_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. @@ -278,20 +276,16 @@ def textfield_value_should_be(self, locator, expected, message=''): 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_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,19 +293,16 @@ def textarea_should_contain(self, locator, expected, message=''): 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_falsy(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 '" + locator + "' did not match any elements.") + 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 - 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,15 +310,12 @@ def textarea_value_should_be(self, locator, expected, message=''): 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_falsy(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 '" + locator + "' did not match any elements.") + 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 @@ -344,7 +332,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. @@ -361,7 +349,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. @@ -375,19 +363,30 @@ def page_should_not_contain_button(self, locator, message='', 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') 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: @@ -399,9 +398,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/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py new file mode 100644 index 000000000..0587e5401 --- /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 does not 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 + 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/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index 4bf9b631c..cd1fb1caa 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,22 +159,19 @@ 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): """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. @@ -197,19 +179,15 @@ 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`. - """ - 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) + Considering read-only elements to be disabled is a new feature + in SeleniumLibrary 3.0. + """ + self._wait_until( + lambda: self.is_element_enabled(locator), + "Element '%s' was not enabled in ." % locator, + timeout, error + ) @keyword def wait_until_element_contains(self, locator, text, timeout=None, error=None): @@ -225,15 +203,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 +223,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 801ad158a..e9daa47aa 100644 --- a/src/SeleniumLibrary/locators/elementfinder.py +++ b/src/SeleniumLibrary/locators/elementfinder.py @@ -19,6 +19,7 @@ from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import ContextAware +from SeleniumLibrary.errors import ElementNotFound from SeleniumLibrary.utils import escape_xpath_value, events, is_falsy from .customlocator import CustomLocator @@ -70,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_falsy(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_falsy(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: @@ -119,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 @@ -134,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): @@ -214,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) @@ -250,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' @@ -287,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 = [] @@ -310,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/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/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..f81204929 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,11 +50,18 @@ 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 - [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/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..761c9a41f 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,27 @@ 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 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 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 +63,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 + ... Element with locator 'missing_id' not found. + ... 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 + ... Element with locator 'missing_id' not found. + ... 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 + ... Element with locator 'missing_id' not found. + ... 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 +268,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 +281,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 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 - [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 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} 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 +315,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..43d673a8f 100644 --- a/test/acceptance/keywords/element_focus.robot +++ b/test/acceptance/keywords/element_focus.robot @@ -5,59 +5,68 @@ 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 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 - Element Should Be Focused el_for_focus + Run Keyword And Expect Error + ... Element with locator 'Unexistent_element' not found. + ... Element Should Be Focused Unexistent_element 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/element_should_be_enabled_and_disabled.robot b/test/acceptance/keywords/element_should_be_enabled_and_disabled.robot index 78e72dfa0..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,55 +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 ERROR: Element table1 is not an input. - ... Element Should Be Enabled table1 - Run Keyword And Expect Error ERROR: Element table1 is not an input. - ... Element Should Be Disabled table1 + Should Be Enabled Not Disabled table1 *** Keywords *** Should Be Enabled Not Disabled diff --git a/test/acceptance/keywords/elements.robot b/test/acceptance/keywords/elements.robot index d36927418..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 @@ -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 + ... 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 Could not determine position for 'non-existent' + Should Be True ${pos} > 0 + Run Keyword And Expect Error + ... Element with locator 'non-existent' not found. ... 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 + ... Element with locator 'non-existent' not found. + ... 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/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 + disabled="" @@ -79,6 +79,20 @@ disabled="kekkonen" + readonly + + + + readonly= + + + + readonly="" + + + + readonly="kekkonen" + 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/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 + 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
diff --git a/test/unit/keywords/test_keyword_arguments_element.py b/test/unit/keywords/test_keyword_arguments_element.py index 33b053bfb..19a6346bf 100644 --- a/test/unit/keywords/test_keyword_arguments_element.py +++ b/test/unit/keywords/test_keyword_arguments_element.py @@ -15,63 +15,15 @@ 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_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) 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() @@ -87,8 +39,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') @@ -99,9 +50,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, - required=False).thenReturn([]) + when(self.element).find_elements('xpath:' + locator).thenReturn([]) count = self.element.get_matching_xpath_count(locator) self.assertEqual(count, '0') count = self.element.get_matching_xpath_count(locator, 'True') @@ -112,9 +61,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..b1f742082 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() @@ -34,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') @@ -56,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'): @@ -66,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',