There are three main ways to create new functionality for Browser library: Plugin API, extending the library with a JavaScript module and building new libraries on top the Browser library. Each way offers their own unique pros and cons. Plugin API and extending Browser library allow similar access to the Python public API library.
When an instance is created from the Browser library, for example when Browser library is imported in the test data, there is an order of the initialization. At first all classes defining Browser library keywords are discovered. As a second event, keywords from the JavaScript module are discovered. As a third and last event, keywords from plugins are discovered.
Browser library offers plugins as a way to modify and add library keywords, while also modifying some of the internal functionality of Browser library without creating a new library or hacking the source code. See plugin example on how plugins can be implemented.
Importing plugins is similar to importing Robot Framework libraries. It is possible to import plugin using a physical path or with exact name of the plugin in the same way libraries are imported in Robot Framework. Browser library plugins are found 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 the plugins with a comma. It is possible to have space before and after the comma. Plugins are imported in the order they are defined in the plugins argument. If two or more plugins declare the same keyword or modify the same method/attribute in the library, the last plugin to perform the changes will overwrite the changes made by other plugins. Example of plugin imports:
Library Browser plugins=${CURDIR}/MyPlugin.py # Imports plugin with physical pathLibrary Browser plugins=plugins.MyPlugin, plugins.MyOtherPlugin # Imports two plugins with nameWhen Browser library creates instances from the plugin classes, it will by default initiate the class with a single
argument, called library. library is the instance of the Browser library and it provides access to the library
Public API.
It is also possible to provide optional arguments to the plugins. Arguments must be separated with a semicolon from the plugin. Browser library will not convert arguments to any specific type and everything is by default unicode. The plugin is responsible for converting the arguments to proper types. Example of importing plugin with arguments:
Library Browser plugins=plugins.Plugin;ArgOne;ArgTwo # Import two plugins with two arguments: ArgOne and ArgTwoIt is also possible to provide a variable number of arguments and keywords arguments. Named arguments must be defined
first, variable number of arguments second, and keywords arguments last. All arguments must be separated with a
semicolon. For example, if plugin __init__ is defined like this:
from Browser.base.librarycomponent import LibraryComponent
class Plugin(LibraryComponent):
def __init__(self, library, arg, *varargs, **kwargs):
# Code to implement the plugin.Then it is possible to plugin with these arguments:
Library Browser plugins=plugins.Plugin;argument1;varg1;varg2;kw1=kwarg1;kw2=kwarg2Then 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__. 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 keywords in the Browser
library. For instance, the
Cookie class inherits
the
LibraryComponent
and uses the @keyword decorator to mark which methods are exposed as keywords.
Plugins must be implemented as Python classes and plugins must inherit the Browser library LibraryComponent class.
Plugin __init__ must support at least one positional argument library which will grant access to the Browser object.
Also, optional arguments are supported. See Plugin arguments for more details how to provide optional arguments to plugins.
Browser library uses Robot Framework's
dynamic library API,
with the help of the Python Library Core. The main difference when
compared to libraries using dynamic library API, is that plugins are not responsible for implementing the dynamic
library API. Browser library is handling the dynamic library API requirements towards Robot Framework. For plugins,
this means that methods that implement keywords must be decorated with the @keyword decorator. The @keyword
decorator can be imported from Robot Framework and used in the following way:
from robot.api.deco import keyword
from Browser.base.librarycomponent import LibraryComponent
class Plugin(LibraryComponent):
@keyword
def plugin_keyword(self):
# More code here to implement the keywordIn the next example you see a more complex example.
It implements the init function to initialize a custom javascript module "jsplugin.js"
that contains a function mouseWheel.
This javascript module has the same requirements as the js keyword extensions of Browser library.
These keywords are however not exposed to Robot Framework but can be called from a Python function
using the self.call_js_keyword function.
See Python keyword mouse_wheel.
The second Python keyword get_location_object uses the Browser keyword Evaluate Javascript
to get the location object from the browser. As long as no access to Playwright is needed,
Evaluate Javascript may be sufficient.
To call a Browser library keyword from a plugin, use self.library.<keyword name to call> syntax.
You can call any Browser keyword that way.
Python plugin example:
import json
from pathlib import Path
from robot.api import logger
from robot.api.deco import keyword
from robot.utils import DotDict
from Browser import Browser
from Browser.base.librarycomponent import LibraryComponent
class ExamplePlugin(LibraryComponent):
def __init__(self, library: Browser):
super().__init__(library)
self.initialize_js_extension(Path(__file__).parent / "jsplugin.js")
@keyword
def mouse_wheel(self, x: int, y: int):
"""This keyword calls a custom javascript keyword from the file jsplugin.js."""
return self.call_js_keyword("mouseWheel", x=x, y=y, logger=None, page=None)
@keyword
def get_location_object(self) -> dict:
"""Returns the location object of the current page.
This keyword calls the python keyword `Evaluate Javascript` to get the location object."""
location_dict = self.library.evaluate_javascript(None, f"window.location")
logger.info(f"Location object:\n {json.dumps(location_dict, indent=2)}")
return DotDict(location_dict)Javascript plugin example "jsplugin.js":
async function mouseWheel(x, y, logger, page) {
logger(`Mouse wheel at ${x}, ${y}`);
await page.mouse.wheel(x, y);
logger("Returning a funny string");
return await page.evaluate("document.scrollingElement.scrollTop");
}
exports.__esModule = true;
exports.mouseWheel = mouseWheel;Browser library does not suppress exceptions raised during plugin importation or during keyword discovery from the plugins. In this case the whole library import will fail and keywords can not be used from that import.
By default exceptions raised by Browser library keywords will trigger the run on failure functionality, and this also applies to keywords created or modified by the plugins. However, it must be noted that plugins can alter the library run on failure functionality. Refer to the plugin documentation for further details.
To separate keywords which are added or modified by plugins, Browser will add a plugin keyword tag to all keywords
added or modified by plugins. Because of this, when Browser library keyword documentation is generated by
libdoc
it is easy to separate keywords which are added or modified by plugins. Browser keyword documentation that includes plugins can be generated with the following command:
libdoc Browser::plugins=/path/to/Plugin.py Browser.html
Browser library uses PythonLibCore (PLC), and because plugins are classes defining keywords, plugins can also act as listeners. See more details in PLC listener documentation.