Skip to content

Commit b70e51a

Browse files
authored
1 parent 7d88445 commit b70e51a

File tree

10 files changed

+244
-43
lines changed

10 files changed

+244
-43
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from robot.api import logger
2+
from selenium.webdriver.support.events import AbstractEventListener
3+
4+
5+
class MyListener(AbstractEventListener):
6+
7+
def before_navigate_to(self, url, driver):
8+
logger.info("Before navigate to %s" % url)
9+
10+
def after_navigate_to(self, url, driver):
11+
logger.info("After navigate to %s" % url)
12+
13+
def before_click(self, element, driver):
14+
logger.info("Before click")
15+
16+
def after_click(self, element, driver):
17+
logger.info("After click")
18+
19+
def before_change_value_of(self, element, driver):
20+
logger.info("Before clear and send_keys")
21+
22+
def after_change_value_of(self, element, driver):
23+
logger.info("After clear and send_keys")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
*** Settings ***
2+
Library SeleniumLibrary event_firing_webdriver=${CURDIR}/MyListener.py
3+
Suite Teardown Close All Browsers
4+
5+
*** Variable ***
6+
${SERVER}= localhost:7000
7+
${BROWSER}= Chrome
8+
${REMOTE_URL}= ${NONE}
9+
${DESIRED_CAPABILITIES}= ${NONE}
10+
${ROOT}= http://${SERVER}/html
11+
${FRONT_PAGE}= ${ROOT}/
12+
13+
*** Test Cases ***
14+
Open Browser To Start Page
15+
[Documentation]
16+
... LOG 1:12 DEBUG Wrapping driver to event_firing_webdriver.
17+
Open Browser ${FRONT PAGE} ${BROWSER} remote_url=${REMOTE_URL}
18+
... desired_capabilities=${DESIRED_CAPABILITIES}
19+
20+
Event Firing Webdriver Go To (WebDriver)
21+
[Documentation]
22+
... STARTS 1:2 Before navigate to
23+
... STARTS 1:6 After navigate to
24+
Go To ${ROOT}/forms/named_submit_buttons.html
25+
26+
Event Firing Webdriver Input Text (WebElement)
27+
[Documentation]
28+
... LOG 1:5 INFO Before clear and send_keys
29+
... LOG 1:9 INFO After clear and send_keys
30+
... LOG 1:10 INFO Before clear and send_keys
31+
... LOG 1:14 INFO After clear and send_keys
32+
Input Text //input[@name="textfield"] FooBar
33+
34+
Event Firing Webdriver Click Element (WebElement)
35+
[Documentation]
36+
... LOG 1:5 INFO Before click
37+
... LOG 1:9 INFO After click
38+
Click Element //input[@name="ok_button"]

src/SeleniumLibrary/__init__.py

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class SeleniumLibrary(DynamicCore):
6565
- `Run-on-failure functionality`
6666
- `Boolean arguments`
6767
- `Plugins`
68+
- `EventFiringWebDriver`
6869
- `Thread support`
6970
- `Importing`
7071
- `Shortcuts`
@@ -455,6 +456,35 @@ class inherits the
455456
456457
| python -m robot.libdoc SeleniumLibrary::plugins=/path/to/Plugin.py ./SeleniumLibraryWithPlugin.html
457458
459+
= EventFiringWebDriver =
460+
461+
The Selenium
462+
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.event_firing_webdriver.html#module-selenium.webdriver.support.event_firing_webdriver|EventFiringWebDriver]
463+
offers listener API for firing events before and after certain Selenium API calls.
464+
SeleniumLibrary offers support for Selenium ``EventFiringWebDriver`` listener class, by providing possibility
465+
to import the listener class with ``event_firing_webdriver`` argument. Refer to the Selenium
466+
``EventFiringWebDriver`` documentation which Selenium API methods which can fire events and how the Selenium
467+
listener class should be implemented.
468+
469+
== Importing listener class ==
470+
471+
Importing Selenium listener class is similar when importing Robot Framework
472+
[http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#importing-libraries|libraries]. It
473+
is possible import Selenium listener class with using
474+
[http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#using-physical-path-to-library|physical path]
475+
or with
476+
[http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#using-library-name|listener name],
477+
exactly in same way as importing libraries in Robot Framework. Selenium listener class is searched from the same
478+
[http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#module-search-path|module search path]
479+
as Robot Framework searches libraries. It is only possible to import listener class written in Python, other
480+
programming languages or Robot Framework test data is not supported. Like with Robot Framework library imports,
481+
Selenium listener class name is case sensitive and spaces are not supported in the class name. It is only
482+
possible to import one Selenium listener class and it is not possible to provide arguments for the Selenium
483+
listener class.
484+
485+
| Library | SeleniumLibrary | event_firing_webdriver=listner.SeleniumListener | # Improts listener with name. |
486+
| Library | SeleniumLibrary | event_firing_webdriver=${CURDIR}/MyListener.py | # Imports listner with physical path. |
487+
458488
= Thread support =
459489
460490
SeleniumLibrary is not thread safe. This is mainly due because the underlying
@@ -468,7 +498,8 @@ class inherits the
468498

469499
def __init__(self, timeout=5.0, implicit_wait=0.0,
470500
run_on_failure='Capture Page Screenshot',
471-
screenshot_root_directory=None, plugins=None):
501+
screenshot_root_directory=None, plugins=None,
502+
event_firing_webdriver=None):
472503
"""SeleniumLibrary can be imported with several optional arguments.
473504
474505
- ``timeout``:
@@ -482,6 +513,9 @@ def __init__(self, timeout=5.0, implicit_wait=0.0,
482513
the directory where the log file is written is used.
483514
- ``plugins``:
484515
Allows extending the SeleniumLibrary with external Python classes.
516+
- ``event_firing_webdriver``:
517+
Class for wrapping Selenium with
518+
[https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.event_firing_webdriver.html#module-selenium.webdriver.support.event_firing_webdriver|EventFiringWebDriver]
485519
"""
486520
self.timeout = timestr_to_secs(timeout)
487521
self.implicit_wait = timestr_to_secs(implicit_wait)
@@ -508,21 +542,15 @@ def __init__(self, timeout=5.0, implicit_wait=0.0,
508542
WindowKeywords(self)
509543
]
510544
if is_truthy(plugins):
511-
parsed_plugins = self._string_to_modules(plugins)
512-
for index, plugin in enumerate(self._import_modules(parsed_plugins)):
513-
if not isclass(plugin):
514-
message = "Importing test library: '%s' failed." % parsed_plugins[index].plugin
515-
raise DataError(message)
516-
plugin = plugin(self, *parsed_plugins[index].args,
517-
**parsed_plugins[index].kw_args)
518-
if not isinstance(plugin, LibraryComponent):
519-
message = 'Plugin does not inherit SeleniumLibrary.base.LibraryComponent'
520-
raise PluginError(message)
521-
self._store_plugin_keywords(plugin)
522-
libraries.append(plugin)
545+
plugin_libs = self._parse_plugins(plugins)
546+
libraries = libraries + plugin_libs
523547
self._drivers = WebDriverCache()
524548
DynamicCore.__init__(self, libraries)
525549
self.ROBOT_LIBRARY_LISTENER = LibraryListener()
550+
if is_truthy(event_firing_webdriver):
551+
self.event_firing_webdriver = self._parse_listener(event_firing_webdriver)
552+
else:
553+
self.event_firing_webdriver = None
526554

527555
_speed_in_secs = Deprecated('_speed_in_secs', 'speed')
528556
_timeout_in_secs = Deprecated('_timeout_in_secs', 'timeout')
@@ -633,28 +661,55 @@ def _run_on_failure(self):
633661
DeprecationWarning)
634662
self.failure_occurred()
635663

636-
def _string_to_modules(self, plugins):
637-
Plugin = namedtuple('Plugin', 'plugin, args, kw_args')
638-
parsed_plugins = []
639-
for plugin in plugins.split(','):
640-
plugin = plugin.strip()
641-
plugin_and_args = plugin.split(';')
642-
plugin_name = plugin_and_args.pop(0)
664+
def _parse_plugins(self, plugins):
665+
libraries = []
666+
importer = Importer('test library')
667+
for parsed_plugin in self._string_to_modules(plugins):
668+
plugin = importer.import_class_or_module(parsed_plugin.module)
669+
if not isclass(plugin):
670+
message = "Importing test library: '%s' failed." % parsed_plugin.module
671+
raise DataError(message)
672+
plugin = plugin(self, *parsed_plugin.args,
673+
**parsed_plugin.kw_args)
674+
if not isinstance(plugin, LibraryComponent):
675+
message = 'Plugin does not inherit SeleniumLibrary.base.LibraryComponent'
676+
raise PluginError(message)
677+
self._store_plugin_keywords(plugin)
678+
libraries.append(plugin)
679+
return libraries
680+
681+
def _parse_listener(self, event_firing_webdriver):
682+
listener_module = self._string_to_modules(event_firing_webdriver)
683+
listener_count = len(listener_module )
684+
if listener_count > 1:
685+
message = 'Is is possible import only one listener but there was %s listeners.' % listener_count
686+
raise ValueError(message)
687+
listener_module = listener_module[0]
688+
importer = Importer('test library')
689+
listener = importer.import_class_or_module(listener_module.module)
690+
if not isclass(listener):
691+
message = "Importing test Selenium lister class '%s' failed." % listener_module.module
692+
raise DataError(message)
693+
return listener
694+
695+
def _string_to_modules(self, modules):
696+
Module = namedtuple('Module', 'module, args, kw_args')
697+
parsed_modules = []
698+
for module in modules.split(','):
699+
module = module.strip()
700+
module_and_args = module.split(';')
701+
module_name = module_and_args.pop(0)
643702
kw_args = {}
644703
args = []
645-
for argument in plugin_and_args:
704+
for argument in module_and_args:
646705
if '=' in argument:
647706
key, value = argument.split('=')
648707
kw_args[key] = value
649708
else:
650709
args.append(argument)
651-
plugin = Plugin(plugin=plugin_name, args=args, kw_args=kw_args)
652-
parsed_plugins.append(plugin)
653-
return parsed_plugins
654-
655-
def _import_modules(self, plugins):
656-
importer = Importer('test library')
657-
return [importer.import_class_or_module(plugin.plugin) for plugin in plugins]
710+
module = Module(module=module_name, args=args, kw_args=kw_args)
711+
parsed_modules.append(module)
712+
return parsed_modules
658713

659714
def _store_plugin_keywords(self, plugin):
660715
dynamic_core = DynamicCore([plugin])

src/SeleniumLibrary/keywords/browsermanagement.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import types
1919

2020
from selenium import webdriver
21+
from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver
2122

2223
from SeleniumLibrary.base import keyword, LibraryComponent
2324
from SeleniumLibrary.locators import WindowManager
@@ -129,6 +130,7 @@ def open_browser(self, url, browser='firefox', alias=None,
129130
self.info("Opening browser '%s' to base url '%s'." % (browser, url))
130131
driver = self._make_driver(browser, desired_capabilities,
131132
ff_profile_dir, remote_url)
133+
driver = self._wrap_event_firing_webdriver(driver)
132134
try:
133135
driver.get(url)
134136
except Exception:
@@ -186,8 +188,15 @@ def create_webdriver(self, driver_name, alias=None, kwargs={},
186188
driver = creation_func(**init_kwargs)
187189
self.debug("Created %s WebDriver instance with session id %s."
188190
% (driver_name, driver.session_id))
191+
driver = self._wrap_event_firing_webdriver(driver)
189192
return self.ctx.register_driver(driver, alias)
190193

194+
def _wrap_event_firing_webdriver(self, driver):
195+
if not self.ctx.event_firing_webdriver:
196+
return driver
197+
self.debug('Wrapping driver to event_firing_webdriver.')
198+
return EventFiringWebDriver(driver, self.ctx.event_firing_webdriver())
199+
191200
@keyword
192201
def switch_browser(self, index_or_alias):
193202
"""Switches between active browsers using ``index_or_alias``.

utest/test/api/MyListener.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from robot.api import logger
2+
from selenium.webdriver.support.events import AbstractEventListener
3+
4+
5+
class MyListener(AbstractEventListener):
6+
7+
def before_navigate_to(self, url, driver):
8+
logger.info("Before navigate to %s" % url)
9+
10+
def after_navigate_to(self, url, driver):
11+
logger.info("After navigate to %s" % url)
12+
13+
def before_click(self, element, driver):
14+
logger.info("Before click")
15+
16+
def after_click(self, element, driver):
17+
logger.info("After click")
18+
19+
def before_change_value_of(self, element, driver):
20+
logger.info("Before send_keys")
21+
22+
def after_change_value_of(self, element, driver):
23+
logger.info("After send_keys")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from robot.api import logger
2+
from selenium.webdriver.support.events import AbstractEventListener
3+
4+
5+
class MyListener(AbstractEventListener):
6+
7+
def before_navigate_to(self, url, driver):
8+
logger.info("Before navigate to %s" % url)
9+
10+
def after_navigate_to(self, url, driver):
11+
logger.info("After navigate to %s" % url)
12+
13+
def before_click(self, element, driver):
14+
logger.info("Before click")
15+
16+
def after_click(self, element, driver):
17+
logger.info("After click")
18+
19+
def before_change_value_of(self, element, driver):
20+
logger.info("Before send_keys")
21+
22+
def after_change_value_of(self, element, driver):
23+
logger.info("After send_keys")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import os
2+
import unittest
3+
4+
from robot.errors import DataError
5+
from selenium.webdriver.support.events import AbstractEventListener
6+
7+
from SeleniumLibrary import SeleniumLibrary
8+
9+
10+
class EventFiringWebDriverSeleniumLibrary(unittest.TestCase):
11+
12+
@classmethod
13+
def setUpClass(cls):
14+
cls.root_dir = os.path.dirname(os.path.abspath(__file__))
15+
cls.listener = os.path.join(cls.root_dir, 'MyListener.py')
16+
17+
def test_import_event_firing_webdriver(self):
18+
sl = SeleniumLibrary(event_firing_webdriver=self.listener)
19+
isinstance(sl.event_firing_webdriver, AbstractEventListener)
20+
21+
def test_no_event_firing_webdriver(self):
22+
sl = SeleniumLibrary()
23+
self.assertIsNone(sl.event_firing_webdriver)
24+
25+
def test_import_event_firing_webdriver_error_module(self):
26+
listener = os.path.join(self.root_dir, 'MyListenerWrongName.py')
27+
with self.assertRaises(DataError):
28+
SeleniumLibrary(event_firing_webdriver=listener)
29+
30+
def test_too_many_event_firing_webdriver(self):
31+
with self.assertRaises(ValueError):
32+
SeleniumLibrary(event_firing_webdriver='%s,%s' % (self.listener, self.listener))

0 commit comments

Comments
 (0)