Skip to content

Latest commit

 

History

History
379 lines (313 loc) · 25 KB

extending.rst

File metadata and controls

379 lines (313 loc) · 25 KB

SeleniumLibrary offers three main ways to creating new functionality for SeleniumLibrary: Plugin API, EventFiringWebDriver and building new libraries on top the SeleniumLibrary (later referred as extending SeleniumLibrary.) Plugin API and extending SeleniumLibrary allows similar access to the SeleniumLibrary public API and offers their own pros and cons for building custom functionality on top the SeleniumLibrary. The EventFiringWebDriver offers listener-like interface to the Selenium API. The plugin API and EventFiringWebDriver are new in SeleniumLibrary 4.0.

SeleniumLibrary offers plugins as a way to modify, add library keywords and modify some of the internal functionality without creating new library or hacking the source code. Plugins can be only loaded in the library import, with the plugins argument and SeleniumLibrary does not offer way to unload the plugins from the SeleniumLibrary. Creating new plugins is more strict than creating new libraries, but plugins allows more access to the methods that are used to implement the keywords.

The EventFiringWebDriver is an listener type of API offered by the Selenium. The EventFiringWebDriver allows to listen Selenium API calls and allows users to fire events before and after Selenium API methods. Refer the to the Selenium EventFiringWebDriver documentation what Selenium API methods are supported and how to EventFiringWebDriver works.

As with any Robot Framework Python library, new libraries can be build on top of the SeleniumLibrary by inheriting the SeleniumLibrary, getting active library instance from Robot Framework or by other means which are available from Python or from Robot Framework. Building new libraries allows library creator freedom to choose which keywords new library offers and more flexibility how new libraries are created than what the plugin API offers.

Before making your own plugin or library on top of the SeleniumLibrary, private or public, please consider if the plugin or extension be generally useful in the SeleniumLibrary. If the plugin or extension would be useful for others, then please create an issue and perhaps a pull request. More details on how the SeleniumLibrary project handles the enhancement requests can be found in the CONTRIBUTING.rst Enhancement requests chapter.

The plugin API and extending SeleniumLibrary have same access to the SeleniumLibrary public API. All the methods, which are exposed as keywords, are available in the SeleniumLibrary public API. Generally keywords are converted to lower case and spaces are converted to underscores. For example, the Open Browser keyword is available as open_browser method. The method name can be overwritten with the @keyword decorator, but the SeleniumLibrary 4.0.0 release does not contain keywords where the keyword name would differ from the method name (other than the keyword case.) Please note that keywords created by the plugins may not follow these rules and it is good to verify the method name from the plugin API source.

The SeleniumLibrary also contains methods and attributes which are not keywords, but are useful when creating plugin or extending the SeleniumLibrary. The available methods are:

Method Description
find_element Finds first element matching locator.
find_elements Find all elements matching locator.
get_keyword_tags Responsible for returning keywords tags for Robot Framework dynamic library API.
register_driver Add's a Selenium driver to the library WebDriverCache.
run_keyword Responsible for executing keywords by Robot Framework dynamic library API.
failure_occurred Method that is executed when a SeleniumLibrary keyword fails.

Also there are the following public attributes available:

Attribute Description
driver Current active driver.
event_firing_webdriver Reference to a class implementing event firing selenium support.
timeout Default value for timeouts used with Wait ... keywords.
page_load_timeout Default value to wait for page load to complete until error is raised.
implicit_wait Default value for implicit wait used when locating elements.
run_on_failure_keyword Default action for the run-on-failure functionality.
screenshot_root_directory Location where possible screenshots are created

For more details about the methods, please read the individual method documentation and many of the attributes are explained in the library keyword documentation. please note that plugins may alter the functionality of the method or attributes and documentation applies only for the core SeleniumLibrary.

When instance is created from the SeleniumLibrary, example when library is imported in the test data, there is an order in the initialisation. At first all classes defining SeleniumLibrary keywords are discovered. As a second event, discovery for the EventFiringWebDriver is done. At third event, plugins are discovered. As a last event, keywords are found from SeleniumLibrary classes and plugins. Because plugins are discovered last, they may example alter the EventFiringWebDriver. Consult the plugin's documentation for more details.

SeleniumLibrary offers plugins as a way to modify, add library keywords and modify some of the internal functionality without creating new library or hacking the source code. See plugin example how plugins can be implemented.

Importing plugins is similar when importing Robot Framework libraries. It is possible import plugin with using physical path or with plugin name exactly in same way as importing libraries in Robot Framework. SeleniumLibrary plugins are searched from the same module search path as Robot Framework searches libraries. It is only possible to import plugins written in Python, other programming languages or Robot Framework test data is not supported. Like with Robot Framework library imports, plugin names are case sensitive and spaces are not supported in the plugin name. It is possible to import multiple plugins at the same time by separating plugins with comma. It is possible to have space before and after the comma. Plugins are imported in the order they defined in the plugins argument. If two or more plugins declare the same keyword or modify the same method/attribute in the SeleniumLibrary, the last plugin to perform the changes will overwrite the changes made by other plugins. Example of plugin imports:

| Library | SeleniumLibrary | plugins=${CURDIR}/MyPlugin.py                   | # Imports plugin with physical path |
| Library | SeleniumLibrary | plugins=plugins.MyPlugin, plugins.MyOtherPlugin | # Import two plugins with name      |

When SeleniumLibrary creates instances from the plugin classes, it will by default initiate the class with a single argument, called ctx (context). ctx is the instance of the SeleniummLibrary and it provides access to the SeleniumLibrary Public API.

It is also possible to provide optional arguments to the plugins. Arguments must be separated with a semicolon from the plugin. SeleniumLibrary will not convert arguments to any specific type and everything is by default unicode. Plugin is responsible for converting the argument to proper types. Example of importing plugin with arguments:

| Library | SeleniumLibrary | plugins=plugins.Plugin;ArgOne;ArgTwo | # Import two plugins with two arguments: ArgOne and ArgTwo |

It is also possible to provide variable number of arguments and keywords arguments. Named arguments must be defined first, variable number of arguments as second and keywords arguments as last. All arguments must be separated with semicolon. Example if plugin __init__ is defined like this:

class Plugin(LibraryComponent):

    def __init__(self, ctx, arg, *varargs, **kwargs):
        # Code to implement the plugin.

Then, for example, it is possible to plugin with these arguments:

| Library | SeleniumLibrary | plugins=plugins.Plugin;argument1;varg1;varg2;kw1=kwarg1;kw2=kwarg2 |

Then the argument1 is given the arg in the __init__. The varg1 and varg2 variable number arguments are given to the *varargs argument in the __init__. Finally, the kw1=kwarg1 and kw2=kwarg2 keyword arguments are given to the **kwargs in the __init__. As in Python, there can be zero or more variable number and keyword arguments.

Generally speaking, plugins are not any different from the classes that are used to implement keyword in the SeleniumLibrary. Example like with BrowserManagementKeywords class inherits the LibraryComponent and uses @keyword decorator to mark which methods are exposed as keywords.

Plugins must be implemented as Python classes and plugins must inherit the SeleniumLibrary LibraryComponent class. Plugin __init__ must support at least one argument: ctx. Also optional arguments are supported, see Plugin arguments for more details how to provide optional arguments to plugins.

SeleniumLibrary uses Robot Framework dynamic library API. The main difference, when compared to libraries using dynamic library API, is that plugins are not responsible for implementing the dynamic library API. SeleniumLibrary is handling the dynamic library API requirements towards Robot Framework. For plugins this means that methods that implements keywords, must be decorated with @keyword decorator. The @keyword decorator can be imported from Robot Framework and used in the following way:

from robot.api.deco import keyword

class Plugin(LibraryComponent):

    @keyword
    def keyword(self):
        self.driver....  # More code here to implement the keyword

SeleniumLibrary does not suppress exception raised during plugin import or during keywords discovery from the plugins. In this case the whole SeleniumLibrary import will fail and SeleniumLibrary keywords can not be used from that import.

By default when exceptions raised by SeleniumLibrary keywords will trigger the run on failure functionality, this also applies keywords created or modified by the plugins. But it must be noted that plugins can alter the SeleniumLibrary run on failure functionality and refer to the plugin documentation for further details.

Although ctx provides access to the SeleniumLibrary Public API, the LibraryComponent provides more methods and attributes and also an IDE friendly access to the plugin API, Example currently active browser can be found from self.ctx.driver, the LibraryComponent exposes the browser as: self.driver and most IDE can discover the completion automatically. Plugin classes must inherit the LibraryComponent.

The following methods are available from the LibraryComponent class:

Method Description
find_element Finds first element matching locator.
find_elements Find all elements matching locator.
is_text_present Returns True if text is present in the page.
is_element_enabled Returns True if element is enabled.
is_visible Returns True if element is visible.
log_source Calls method defining the Log Source keyword.
assert_page_contains Raises AssertionError if element is not found from the page.
assert_page_not_contains Raises AssertionError if element is found from the page.
get_timeout By default returns SeleniumLibrary timeout argument value. With argument converts string with Robot Framework timestr_to_secs to seconds.
info Wrapper to robot.api.logger.info method.
debug Wrapper to robot.api.logger.debug method.
warn Wrapper to robot.api.logger.warn method.
log Wrapper to robot.api.logger.write method.

Also following attributes are available from the LibraryComponent class:

Attribute Description
driver Currently active browser/WebDriver instance in the SeleniumLibrary.
drivers Cache for the opened browsers/WebDriver instances.
element_finder Read/write attribute for the ElementFinder instance.
ctx Instance of the SeleniumLibrary.
log_dir Folder where output files are written.
event_firing_webdriver Read/write attribute for the SeleniumLibrary EventFiringWebDriver instance.

See the SeleniumLibrary init, the LibraryComponent and the ContextAware classes for further implementation details.

To separate keywords which are added or modified by plugins, SeleniumLibrary will add plugin keyword tag to all keywords added or modified from plugins. When SeleniumLibrary keyword documentation, with plugins, is generated by libdoc it is easy to separate keywords which are added or modified by plugins. Keyword documentation can be example generated by following command:

python -m robot.libdoc SeleniumLibrary::plugins=/path/to/Plugin.py ./SeleniumLibraryWithPlugin.html

The EventFiringWebDriver is an listener type of API offered by the Selenium. In practice EventFiringWebDriver offers way to intercept Selenium API call, made by SeleniumLibrary or by other library keywords and fire separate Selenium events. Events can be fired before and after Selenium API call.

SeleniumLibrary offers support for Selenium EventFiringWebDriver listener class by providing possibility to import the listener class by event_firing_webdriver argument. Importing EventFiringWebDriver is similar when importing Robot Framework libraries. It is possible import EventFiringWebDriver with using physical path or with name exactly in same way as importing libraries in Robot Framework. EventFiringWebDriver class is searched from the same module search path as Robot Framework searches libraries. It is only possible to import EventFiringWebDriver class written in Python, other programming languages or Robot Framework test data is not supported. Like with Robot Framework library imports, EventFiringWebDriver class name is case sensitive and spaces are not supported in the class name. It is possible to import only one EventFiringWebDriver class. Example of EventFiringWebDriver imports:

| Library | SeleniumLibrary | event_firing_webdriver=${CURDIR}/MyListener.py | # Imports EventFiringWebDriver with physical path |

Refer the to the Selenium EventFiringWebDriver documentation what Selenium API methods are supported and how to EventFiringWebDriver works. Also there is simple EventFiringWebDriver example for more details.

Starting from SeleniumLibrary 3.0, the library has moved to use Robot Framework dynamic library API. To ease the usage of the dynamic library API, the SeleniumLibrary uses a PythonLibCore project to handle the most the dynamic library API requirements, except running the keyword and providing keywords tags. For more details please about the dynamic library API, read the Robot Framework dynamic library API documentation.

The principles described in the Robot Framework User Guide, Extending existing test libraries chapter also apply when extending SeleniumLibrary. There are two different ways to extend the SeleniumLibrary.

  1. Create a library which also contains the existing SeleniumLibrary keywords, example by using inheritance.
  2. Create library which contains only new keywords.

When creating a library, which also includes the existing SeleniumLibrary keywords, there are extra steps which needs to be taken account, because SeleniumLibrary uses PythonLibCore and the dynamic library API. All methods which should be published as keywords must be decorated with the @keyword decorator. The @keyword decorator can be imported in the following way:

from robot.api.deco import keyword

Keywords should be inside of a class and the add_library_components method must be called to add keywords. The add_library_components method is inherited from the PythonLibCore project and the method must contain list of classes which contain the new keywords.

Perhaps the easiest way to extend the SeleniumLibrary is to inherit the SeleniumLibrary and add new keywords methods to a new library. The inheritance example shows how to declare a new keyword Get Browser Desired Capabilities and how to overwrite the existing Open Browser keyword.

Because the InheritSeleniumLibrary class does not overwrite the SeleniumLibrary init method, the add_library_components is called automatically. Then the InheritSeleniumLibrary class methods which are decorated with @keyword decorator are added to the InheritSeleniumLibrary library keywords. Also existing keywords from SeleniumLibrary are added as library keywords.

Because the methods are no longer directly available in the SeleniumLibrary class, it's not possible to call the original method example like this:

super(ClassName, self).open_browser(url, browser, alias, remote_url,
                                    desired_capabilities, ff_profile_dir)

Instead user must call the method from the class instance which implements the keyword, example:

browser_management = BrowserManagementKeywords(self)
browser_management.open_browser(url, 'chrome')

Decomposition is a good way to split library into smaller namespaces and it usually eases the testing of the library. The decomposition example shows how the Get Browser Desired Capabilities and Open Browser keywords can be divided into their own classes.

The example also shows the usage of the ctx (context) object and the LibraryComponent class. The ctx object is an instance of the SeleniumLibrary which provides access to the SeleniumLibrary Public API for the BrowserKeywords and DesiredCapabilitiesKeywords classes.

The LibraryComponent is a wrapper class, which provides easier shortcuts to the ctx object methods and the example provides general logging methods. Example the Selenium WebDriver instance in the context: self.ctx.driver, but the LibraryComponent provides a shortcut and it can be accessed with: self.driver

Getting the active library instance provides a way to create a new library that does not automatically contain keywords from the SeleniumLibrary. This eases the name space handling and if only new keywords are created, the user does not have to prefix the keywords with the library name. This way also allows users to freely choose the Robot Framework library API. The instance example shows a way to get the active SeleniumLibrary from the Robot Framework. The example shows how to declare Get Browser Desired Capabilities and Open Browser keywords in the new library and the instance example uses the static keyword API to declare new keywords.