Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
decorator >= 3.3.2
selenium
robotframework >= 2.6.0
86 changes: 66 additions & 20 deletions src/Selenium2Library/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
from .keywords import *
import warnings

from .keywords.alert import AlertKeywords
from .keywords.browsermanagement import BrowserManagementKeywords
from .keywords.cookie import CookieKeywords
from .keywords.element import ElementKeywords
from .keywords.formelement import FormElementKeywords
from .keywords.javascript import JavaScriptKeywords
from .keywords.runonfailure import RunOnFailureKeywords
from .keywords.screenshot import ScreenshotKeywords
from .keywords.selectelement import SelectElementKeywords
from .keywords.tableelement import TableElementKeywords
from .keywords.waiting import WaitingKeywords
from .robotlibcore import DynamicCore
from .utils import BrowserCache
from .utils import LibraryListener
from .version import VERSION


__version__ = VERSION


class Selenium2Library(
LoggingKeywords,
RunOnFailureKeywords,
BrowserManagementKeywords,
ElementKeywords,
TableElementKeywords,
FormElementKeywords,
SelectElementKeywords,
JavaScriptKeywords,
CookieKeywords,
ScreenshotKeywords,
WaitingKeywords,
AlertKeywords
):
class Selenium2Library(DynamicCore):
"""Selenium2Library is a web testing library for Robot Framework.

This document is about using Selenium2Library. For information about
Expand Down Expand Up @@ -151,8 +151,7 @@ def __init__(self,
timeout=5.0,
implicit_wait=0.0,
run_on_failure='Capture Page Screenshot',
screenshot_root_directory=None
):
screenshot_root_directory=None):

"""Selenium2Library can be imported with optional arguments.

Expand Down Expand Up @@ -184,10 +183,57 @@ def __init__(self,
| Library `|` Selenium2Library `|` implicit_wait=5 `|` run_on_failure=Log Source | # Sets default implicit_wait to 5 seconds and runs `Log Source` on failure |
| Library `|` Selenium2Library `|` timeout=10 `|` run_on_failure=Nothing | # Sets default timeout to 10 seconds and does nothing on failure |
"""
for base in Selenium2Library.__bases__:
base.__init__(self)
self._run_on_failure_keyword = None
self._running_on_failure_routine = False
self._speed_in_secs = 0.0
self._timeout_in_secs = 5.0
self._implicit_wait_in_secs = 5.0
libraries = [
AlertKeywords(self),
BrowserManagementKeywords(self),
RunOnFailureKeywords(self),
ElementKeywords(self),
TableElementKeywords(self),
FormElementKeywords(self),
SelectElementKeywords(self),
JavaScriptKeywords(self),
CookieKeywords(self),
ScreenshotKeywords(self),
WaitingKeywords(self)
]
self._browsers = BrowserCache()
DynamicCore.__init__(self, libraries)
self.screenshot_root_directory = screenshot_root_directory
self.set_selenium_timeout(timeout)
self.set_selenium_implicit_wait(implicit_wait)
self.register_keyword_to_run_on_failure(run_on_failure)
self.ROBOT_LIBRARY_LISTENER = LibraryListener()

def run_keyword(self, name, args, kwargs):
try:
return DynamicCore.run_keyword(self, name, args, kwargs)
except Exception:
RunOnFailureKeywords(self).run_on_failure()
raise

def register_browser(self, browser, alias):
return self._browsers.register(browser, alias)

@property
def _browser(self):
"""Current active browser"""
if not self._browsers.current:
raise RuntimeError('No browser is open')
return self._browsers.current

@property
def _cache(self):
warnings.warn('"Selenium2Library._cache" is deprecated, '
'use public API instead.', DeprecationWarning)
return self._browsers

def _current_browser(self):
warnings.warn('"Selenium2Library._current_browser" is deprecated, '
'use "Selenium2Library.browser" instead.',
DeprecationWarning)
return self.browser
97 changes: 97 additions & 0 deletions src/Selenium2Library/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from robot.api import logger
from selenium.webdriver.remote.webelement import WebElement

from .locators.elementfinder import ElementFinder


LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR']


class Base(object):

def __init__(self, ctx):
self.ctx = ctx
self.element_finder = ElementFinder()

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)

def warn(self, msg, html=False):
logger.warn(msg, html)

@property
def browser(self):
return self.ctx._browser

@property
def browsers(self):
return self.ctx._browsers

# TODO: Move logic in elementfinder.ElementFinder but keep method as proxy
# in Base class
def element_find(self, locator, first_only=True, required=True, tag=None):
if isinstance(locator, basestring):
elements = self.element_finder.find(self.browser, locator, tag)
if required and len(elements) == 0:
raise ValueError(
"Element locator '{}' did not match any elements.".format(
locator
)
)
if first_only:
if not elements:
return None
return elements[0]
elif isinstance(locator, WebElement):
elements = locator
return elements

# TODO: Move logic in elementfinder.ElementFinder but keep method as proxy
# in Base class
def get_value(self, locator, tag=None):
element = self.element_find(
locator, required=False, tag=tag
)
return element.get_attribute('value') if element is not None else None

# TODO: Move logic in elementfinder.ElementFinder but keep method as proxy
# in Base class
def page_contains_element(self, locator, tag=None,
message=None, loglevel='INFO'):
element_name = tag if tag else 'element'
if not self.element_find(locator, required=False, tag=tag):
if not message:
message = (
"Page should have contained %s "
"'%s' but did not" % (element_name, locator)
)
self.ctx.log_source(loglevel)
raise AssertionError(message)
self.info(
"Current page contains %s '%s'." % (element_name, locator)
)

# TODO: Move logic in elementfinder.ElementFinder but keep method as proxy
# in Base class
def page_not_contains_element(self, locator, tag=None,
message=None, loglevel='INFO'):
element_name = tag if tag else 'element'
if self.element_find(locator, required=False, tag=tag):
if not message:
message = (
"Page should not have contained %s '%s'"
% (element_name, locator)
)
self.ctx.log_source(loglevel)
raise AssertionError(message)
self.info(
"Current page does not contain %s '%s'."
% (element_name, locator)
)
28 changes: 0 additions & 28 deletions src/Selenium2Library/keywords/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +0,0 @@
from .logging import LoggingKeywords
from .runonfailure import RunOnFailureKeywords
from .browsermanagement import BrowserManagementKeywords
from .element import ElementKeywords
from .tableelement import TableElementKeywords
from .formelement import FormElementKeywords
from .selectelement import SelectElementKeywords
from .javascript import JavaScriptKeywords
from .cookie import CookieKeywords
from .screenshot import ScreenshotKeywords
from .waiting import WaitingKeywords
from .alert import AlertKeywords


__all__ = [
"LoggingKeywords",
"RunOnFailureKeywords",
"BrowserManagementKeywords",
"ElementKeywords",
"TableElementKeywords",
"FormElementKeywords",
"SelectElementKeywords",
"JavaScriptKeywords",
"CookieKeywords",
"ScreenshotKeywords",
"WaitingKeywords",
"AlertKeywords"
]
47 changes: 27 additions & 20 deletions src/Selenium2Library/keywords/alert.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import time

from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from .keywordgroup import KeywordGroup

from Selenium2Library.base import Base
from Selenium2Library.robotlibcore import keyword

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use ..base instead of Selenium2Library.base? Robot uses the latter but the former would make the library name change easier. This isn't a big deal in general and definitely not in the context of this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets decide this one when we decide should we rename the library.


class AlertKeywords(KeywordGroup):

__ACCEPT_ALERT = 'accept'
__DISMISS_ALERT = 'dismiss'
class AlertKeywords(Base):

def __init__(self):
self._next_alert_dismiss_type = self.__ACCEPT_ALERT
ACCEPT_ALERT = 'accept'
DISMISS_ALERT = 'dismiss'

# Public
def __init__(self, ctx):
Base.__init__(self, ctx)
self._next_alert_dismiss_type = self.ACCEPT_ALERT

@keyword
def input_text_into_prompt(self, text):
"""Types the given `text` into alert box. """
try:
Expand All @@ -24,6 +26,7 @@ def input_text_into_prompt(self, text):
except WebDriverException:
raise RuntimeError('There were no alerts')

@keyword
def alert_should_be_present(self, text=''):
"""Verifies an alert is present and dismisses it.

Expand All @@ -34,16 +37,18 @@ def alert_should_be_present(self, text=''):
will fail unless the alert is dismissed by this
keyword or another like `Get Alert Message`.
"""
alert_text = self._handle_alert(self.__ACCEPT_ALERT)
alert_text = self._handle_alert(self.ACCEPT_ALERT)
if text and alert_text != text:
raise AssertionError("Alert text should have been "
"'%s' but was '%s'"
% (text, alert_text))

@keyword
def choose_cancel_on_next_confirmation(self):
"""Cancel will be selected the next time `Confirm Action` is used."""
self._next_alert_dismiss_type = self.__DISMISS_ALERT
self._next_alert_dismiss_type = self.DISMISS_ALERT

@keyword
def choose_ok_on_next_confirmation(self):
"""Undo the effect of using keywords `Choose Cancel On Next Confirmation`. Note
that Selenium's overridden window.confirm() function will normally
Expand All @@ -59,8 +64,9 @@ def choose_ok_on_next_confirmation(self):
consume it by using a keywords such as `Get Alert Message`, or else
the following selenium operations will fail.
"""
self._next_alert_dismiss_type = self.__ACCEPT_ALERT
self._next_alert_dismiss_type = self.ACCEPT_ALERT

@keyword
def confirm_action(self):
"""Dismisses currently shown confirmation dialog and returns it's message.

Expand All @@ -79,9 +85,10 @@ def confirm_action(self):
| Confirm Action | | # Chooses Cancel |
"""
text = self._handle_alert(self._next_alert_dismiss_type)
self._next_alert_dismiss_type = self.__DISMISS_ALERT
self._next_alert_dismiss_type = self.DISMISS_ALERT
return text

@keyword
def get_alert_message(self, dismiss=True):
"""Returns the text of current JavaScript alert.

Expand All @@ -91,10 +98,11 @@ def get_alert_message(self, dismiss=True):
dismissed by this keyword or another like `Dismiss Alert`.
"""
if dismiss:
return self._handle_alert(self.__DISMISS_ALERT)
return self._handle_alert(self.DISMISS_ALERT)
else:
return self._handle_alert()

@keyword
def dismiss_alert(self, accept=True):
""" Returns true if alert was confirmed, false if it was dismissed

Expand All @@ -103,7 +111,7 @@ def dismiss_alert(self, accept=True):
dismissed by this keyword or another like `Get Alert Message`.
"""
if accept:
return self._handle_alert(self.__ACCEPT_ALERT)
return self._handle_alert(self.ACCEPT_ALERT)
else:
return self._handle_alert()

Expand All @@ -115,7 +123,7 @@ def _handle_alert(self, dismiss_type=None):
alert.text
alert.dismiss

This function creates a re-try funtionality to better support
This function creates a re-try functionality to better support
alerts in Chrome.
"""
retry = 0
Expand All @@ -130,12 +138,11 @@ def _handle_alert(self, dismiss_type=None):
def _alert_worker(self, dismiss_type=None):
alert = self._wait_alert()
text = ' '.join(alert.text.splitlines())
if dismiss_type == self.__DISMISS_ALERT:
if dismiss_type == self.DISMISS_ALERT:
alert.dismiss()
elif dismiss_type == self.__ACCEPT_ALERT:
elif dismiss_type == self.ACCEPT_ALERT:
alert.accept()
return text

def _wait_alert(self):
return WebDriverWait(self._current_browser(), 1).until(
EC.alert_is_present())
return WebDriverWait(self.browser, 1).until(EC.alert_is_present())
Loading