Skip to content
Merged
61 changes: 38 additions & 23 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
CookieKeywords,
ElementKeywords,
FormElementKeywords,
FrameKeywords,
JavaScriptKeywords,
RunOnFailureKeywords,
ScreenshotKeywords,
Expand Down Expand Up @@ -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 ==

Expand Down Expand Up @@ -100,17 +101,17 @@ 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 ===

The explicit locator strategy is specified with a prefix using either
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`.
Expand All @@ -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 ===

Expand All @@ -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.

Expand Down Expand Up @@ -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()
Expand Down
43 changes: 40 additions & 3 deletions src/SeleniumLibrary/base/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand All @@ -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)
33 changes: 21 additions & 12 deletions src/SeleniumLibrary/base/librarycomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,45 @@
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)

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):
Expand Down
23 changes: 23 additions & 0 deletions src/SeleniumLibrary/errors.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/SeleniumLibrary/keywords/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 3 additions & 33 deletions src/SeleniumLibrary/keywords/browsermanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading