Skip to content

Commit 51afed6

Browse files
authored
Documentation for extending library (#1029)
Adding documentation how to extend the SeleniumLibrary and which kind of methods are available it the library public API.
1 parent 2f847c9 commit 51afed6

9 files changed

Lines changed: 310 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
Extending SeleniumLibrary
2+
=========================
3+
4+
.. contents::
5+
6+
Introduction
7+
------------
8+
Starting from SeleniumLibrary 3.0, the library has moved to use Robot Framework
9+
`dynamic library API`_. To ease the usage of the dynamic library API, the SeleniumLibrary uses
10+
a `PythonLibCore`_ project to handle the keyword keyword discovery, running the keywords and
11+
other things to related to the dynamic library API. For more details please read the Robot
12+
Framework `dynamic library API`_ documentation.
13+
14+
Public API methods
15+
------------------
16+
All the methods, which are exposed as keywords, are available in the library public API. Generally
17+
keywords are converted to lover case and spaces are converted to underscores. Example `Open Browser`_
18+
keyword is available as ``open_browser`` method. Please note that method name can be overwritten
19+
with the ``@keyword`` decorator, but in the SeleniumLibrary 3.0.0 release does not contain keywords
20+
where the keyword name would differ from the method name (other than the keywords name case).
21+
22+
Methods or attributes which are not keywords but are available in the public API
23+
--------------------------------------------------------------------------------
24+
The SeleniumLibrary instance also contains methods and attributes which are not keywords but are
25+
useful when extending the SeleniumLibrary. The available methods are:
26+
27+
================ =============================================================
28+
Method Description
29+
================ =============================================================
30+
find_element Finds first element matching ``locator``.
31+
find_elements Find all elements matching ``locator``.
32+
register_driver Add's a ``driver`` to the library WebDriverCache.
33+
run_keyword Responsible for executing keywords.
34+
failure_occurred Method that is executed when a SeleniumLibrary keyword fails.
35+
================ =============================================================
36+
37+
Also there are the following public attributes available:
38+
39+
========================= ================================================================
40+
Attribute Description
41+
========================= ================================================================
42+
driver Current active driver.
43+
timeout Default value for ``timeouts`` used with ``Wait ...`` keywords.
44+
implicit_wait Default value for ``implicit wait`` used when locating elements.
45+
run_on_failure_keyword Default action for the `run-on-failure functionality`.
46+
screenshot_root_directory Location where possible screenshots are created
47+
========================= ================================================================
48+
49+
For more details about the methods, please read the individual method documentation and many
50+
of the attributes are explained in the library `keyword documentation`_.
51+
52+
Extending SeleniumLibrary by pull requests
53+
------------------------------------------
54+
Before making your own extension, private or public, please consider would the extension be
55+
generally useful in the SeleniumLibrary. If the extension would be useful for others, then please
56+
create an issue and a pull request. More details how SeleniumLibrary project handles the
57+
enhancement requests can be read from the CONTRIBUTING.rst `Enhancement requests`_ chapter.
58+
59+
General prinsibles for extending SeleniumLibrary
60+
------------------------------------------------
61+
The prinsibles described in the Robot Framework User Guide, `Extending existing test libraries`_
62+
chapter also apply when extending the SeleniumLibrary. There are two different ways to
63+
extend the SeleniumLibrary.
64+
65+
1) Create a library which also the contains existing SeleniumLibrary keywords, example by using `inheritance`_.
66+
2) Create library which contains only new keywords.
67+
68+
When creating a library, which also includes the existing SeleniumLibrary keywords, there are
69+
extra steps which needs to be taken account, because SeleniunLibrary uses `PythonLibCore`_
70+
and the `dynamic library API`_. All methods which should be published as keywords must be
71+
decorated with ``@keyword`` decorator. The ``@keyword`` decorator can be imported in following way::
72+
73+
from SeleniumLibrary.base import keyword
74+
75+
Keywords should be inside of a ``class`` and the ``add_library_components`` method
76+
must be called to add keywords. The ``add_library_components`` method is inherited from the
77+
`PythonLibCore`_ project and the method must contains list of classes which contains the
78+
new keywords.
79+
80+
Creating a new library by using inheritance
81+
-------------------------------------------
82+
Perhaps the easiest way to extend the SeleniumLibrary is to inherit the SeleniumLibrary and add
83+
new keywords methods to a new library. The `inheritance example`_ shows how to declare new
84+
keyword ``Get Browser Desired Capabilities`` and how to overwrite existing ``Open Browser`` keyword.
85+
86+
Because the ``InheritSeleniumLibrary`` class foes not overwrite the SeleniumLibrary ``init`` method,
87+
the ``add_library_components`` is called automatically. Then the ``InheritSeleniumLibrary`` class methods
88+
which are decorated with ``@keyword`` decorator are added to the ``InheritSeleniumLibrary``
89+
library keywords. Also existing keywords from SeleniumLibrary are added as library keywords.
90+
91+
Because the methods are not anymore directly available in the SeleniumLibrary class, it not
92+
anymore possible to call the original method example like this::
93+
94+
super(ClassName, self).open_browser(url, browser, alias, remote_url,
95+
desired_capabilities, ff_profile_dir)
96+
97+
Instead user must call the method from the class instance which implements the keyword, example::
98+
99+
browser_management = BrowserManagementKeywords(self)
100+
browser_management.open_browser(url, 'chrome')
101+
102+
Creating a new library from multiple classes
103+
--------------------------------------------
104+
Decomposition is a good way to split library to smaller name spaces and it usually eases the
105+
library testing. The `decomposition example`_ shows how the ``Get Browser Desired Capabilities``
106+
and ``Open Browser`` keywords can divided to own classes.
107+
108+
The example also shows the usage of the ``context`` object and the `LibraryComponent`_ class.
109+
The ``context`` object is a instance of the SeleniunLibrary which provides access to the
110+
SeleniumLibrary methods for the ``BrowserKeywords`` and ``DesiredCapabilitiesKeywords`` classes.
111+
Example ``context`` object provides access to the Selenium WebDriver instance.
112+
113+
The ``LibraryComponent`` is a wrapper class, which provides easier shortcuts the ``context`` object
114+
methods and example provides general logging methods. Example the Selenium WebDriver instance in
115+
the context: ``self.ctx.driver``, but the ``LibraryComponent`` provides a shortcut and it can be
116+
accessed with: ``self.driver``
117+
118+
119+
Creating a new library by getting active library instance
120+
---------------------------------------------------------
121+
Getting the active library instance provides way to create a new library that it does not
122+
automatically contain keywords from the SeleniumLibrary. This eases the name space
123+
handling and if only new keywords are created, user does not have to prefix the keywords with the
124+
library name. This way also allows user to freely choose the Robot Framework `library API`_.
125+
The `instance example`_ shows a way how the active SeleniumLibrary is get from the Robot Framework.
126+
The example shows how to declare ``Get Browser Desired Capabilities`` and ``Open Browser`` keywords
127+
to the new library and the `instance example`_ uses the `static keyword API`_ to declare new
128+
keywords.
129+
130+
Extending the SeleniumLibrary dynamically
131+
-----------------------------------------
132+
It is possible to modify keywords directly in the SeleniumLibrary. This can be done calling
133+
``add_library_components`` method from the SeleniumLibrary instance and then using the
134+
``BuiltIn().reload_library()`` method to reload the SeleniumLibrary.
135+
136+
The `modify SeleniumLibrary`_ example uses the Robot Framework `Listener API`_ to extend the
137+
SeleniumLibrary with a keywords in the ``KeywordClass``. The ``NewKeywords``class is library
138+
which is also a listener and it uses the ``start_suite`` method to trigger the modifications
139+
to the SeleniumLibrary. The example gets the active library instance from the Robot Framework
140+
and uses the ``add_library_components`` method to add the ``KeywordClass`` keywords in to
141+
the SeleniumLibrary directly.
142+
143+
.. _dynamic library API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api
144+
.. _PythonLibCore: https://github.com/robotframework/PythonLibCore
145+
.. _Open Browser: http://robotframework.org/SeleniumLibrary/SeleniumLibrary.html#Open%20Browser
146+
.. _keyword documentation: http://robotframework.org/SeleniumLibrary/SeleniumLibrary.html
147+
.. _Enhancement requests: https://github.com/robotframework/SeleniumLibrary/blob/master/CONTRIBUTING.rst#enhancement-requests
148+
.. _Extending existing test libraries: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#extending-existing-test-libraries
149+
.. _inheritance: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/inheritance/InheritSeleniumLibrary.py
150+
.. _inheritance example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/inheritance/InheritSeleniumLibrary.py
151+
.. _decomposition example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/decomposition/Decomposition.py
152+
.. _instance example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/get_instance/GetSeleniumLibraryInstance.py
153+
.. _LibraryComponent: https://github.com/robotframework/SeleniumLibrary/blob/master/src/SeleniumLibrary/base/librarycomponent.py
154+
.. _library API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#different-test-library-apis
155+
.. _static keyword API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-static-keywords
156+
.. _modify SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/modify_seleniumlibrary/NewKeywords.py
157+
.. _Listener API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from SeleniumLibrary import SeleniumLibrary
2+
from SeleniumLibrary.base import keyword, LibraryComponent
3+
from SeleniumLibrary.keywords import BrowserManagementKeywords
4+
5+
6+
class BrowserKeywords(LibraryComponent):
7+
8+
def __init__(self, ctx):
9+
LibraryComponent.__init__(self, ctx)
10+
11+
@keyword
12+
def open_browser(self, host):
13+
url = 'http://{}.com/'.format(host)
14+
browser_management = BrowserManagementKeywords(self.ctx)
15+
browser_management.open_browser(url, 'chrome')
16+
17+
18+
class DesiredCapabilitiesKeywords(LibraryComponent):
19+
20+
def __init__(self, ctx):
21+
LibraryComponent.__init__(self, ctx)
22+
23+
@keyword
24+
def get_browser_desired_capabilities(self):
25+
self.info('Getting currently open browser desired capabilities')
26+
return self.driver.desired_capabilities
27+
28+
29+
class Decomposition(SeleniumLibrary):
30+
31+
def __init__(self, timeout=5.0, implicit_wait=0.0,
32+
run_on_failure='Capture Page Screenshot',
33+
screenshot_root_directory=None):
34+
SeleniumLibrary.__init__(self, timeout=timeout, implicit_wait=implicit_wait,
35+
run_on_failure=run_on_failure,
36+
screenshot_root_directory=screenshot_root_directory)
37+
self.add_library_components([BrowserKeywords(self),
38+
DesiredCapabilitiesKeywords(self)])
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
*** Settings ***
2+
Library ./Decomposition.py
3+
4+
*** Test Cases ***
5+
Decomposition Example
6+
Open Browser google
7+
${capabilities} = Get Browser Desired Capabilities
8+
Log ${capabilities}
9+
[Teardown] Close Browser
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from robot.api import logger
2+
from robot.libraries.BuiltIn import BuiltIn
3+
4+
5+
def open_browser(host):
6+
url = 'http://{}.com/'.format(host)
7+
sl = BuiltIn().get_library_instance('SeleniumLibrary')
8+
sl.open_browser(url, 'chrome')
9+
10+
11+
def get_browser_desired_capabilities():
12+
logger.info('Getting currently open browser desired capabilities')
13+
sl = BuiltIn().get_library_instance('SeleniumLibrary')
14+
return sl.driver.desired_capabilities
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*** Settings ***
2+
Library SeleniumLibrary
3+
Library ./GetSeleniumLibraryInstance.py
4+
5+
*** Test Cases ***
6+
Use InheritSeleniumLibrary Open Browser Keyword
7+
GetSeleniumLibraryInstance.Open Browser google
8+
${capabilities} = GetSeleniumLibraryInstance.Get Browser Desired Capabilities
9+
Log ${capabilities}
10+
[Teardown] Close Browser
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from robot.api import logger
2+
from SeleniumLibrary import SeleniumLibrary
3+
from SeleniumLibrary.base import keyword
4+
from SeleniumLibrary.keywords import BrowserManagementKeywords
5+
6+
7+
class InheritSeleniumLibrary(SeleniumLibrary):
8+
9+
@keyword
10+
def open_browser(self, host):
11+
url = 'http://{}.com/'.format(host)
12+
browser_management = BrowserManagementKeywords(self)
13+
browser_management.open_browser(url, 'chrome')
14+
15+
@keyword
16+
def get_browser_desired_capabilities(self):
17+
logger.info('Getting currently open browser desired capabilities')
18+
return self.driver.desired_capabilities
19+
20+
def not_keywords_but_public_methods(self):
21+
logger.info('Python public method not a keyword, because it is not '
22+
'decorated with @keyword decorator')
23+
24+
def _private_method_are_not_keywords(self):
25+
logger.info('Python private method is not a keyword, because it is not '
26+
'decorated with @keyword decorator')
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
*** Settings ***
2+
Library ./InheritSeleniumLibrary.py
3+
4+
*** Test Cases ***
5+
Use InheritSeleniumLibrary Open Browser Keyword
6+
Open Browser google
7+
${capabilities} = Get Browser Desired Capabilities
8+
Log ${capabilities}
9+
[Teardown] Close Browser
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from robot.api import logger
2+
from robot.libraries.BuiltIn import BuiltIn
3+
from SeleniumLibrary.base import keyword
4+
from SeleniumLibrary.base import LibraryComponent
5+
from SeleniumLibrary.keywords import BrowserManagementKeywords
6+
7+
8+
class KeywordClass(LibraryComponent):
9+
10+
def __init__(self, ctx):
11+
LibraryComponent.__init__(self, ctx)
12+
13+
@keyword
14+
def open_browser(self, host):
15+
logger.info('This is keyword from KeywordClass')
16+
url = 'http://{}.com/'.format(host)
17+
browser_management = BrowserManagementKeywords(self.ctx)
18+
browser_management.open_browser(url, 'chrome')
19+
20+
@keyword
21+
def get_browser_desired_capabilities(self):
22+
logger.info('Getting currently open browser desired capabilities')
23+
return self.driver.desired_capabilities
24+
25+
26+
class NewKeywords(object):
27+
28+
ROBOT_LISTENER_API_VERSION = 2
29+
30+
def __init__(self):
31+
self.ROBOT_LIBRARY_LISTENER = self
32+
33+
def start_suite(self, name, attributes):
34+
sl = BuiltIn().get_library_instance('SeleniumLibrary')
35+
sl.add_library_components([KeywordClass(sl)])
36+
BuiltIn().reload_library('SeleniumLibrary')
37+
self.added = True
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*** Settings ***
2+
Library SeleniumLibrary
3+
Library ./NewKeywords.py
4+
5+
*** Test Cases ***
6+
Use New Keywords From SeleniumLibrary
7+
SeleniumLibrary.Open Browser google
8+
${capabilities} = SeleniumLibrary.Get Browser Desired Capabilities
9+
Log ${capabilities}
10+
[Teardown] Close Browser

0 commit comments

Comments
 (0)