Principle:MarketSquare Robotframework browser Plugin Loading Mechanism
Plugin Loading Mechanism
Overview
The robotframework-browser library supports dynamic plugin loading at library import time. When the Browser library is instantiated with a plugins argument, the specified plugin classes are discovered, instantiated, and their keywords are merged with the library's built-in keyword set. This entire process happens before any test execution begins, making plugin keywords available from the first test case.
Core Concept: Dynamic Discovery and Instantiation
Plugin loading follows a pipeline:
- Specification -- The user provides plugin identifiers (module paths, file paths, or comma-separated lists) via the
pluginsargument - Parsing --
PluginParserfromrobotlibcoreresolves each identifier to a Python class - Instantiation -- Each plugin class is constructed with the
Browserlibrary instance as its argument - Registration -- Plugin component instances are added to the library's component list
- Keyword Merging --
DynamicCore.__init__collects keywords from all components (built-in and plugin) - Tagging -- Plugin keywords are tracked in
_plugin_keywordsand auto-tagged with"Plugin"
Plugin Specification Format
Plugins are specified as a string (or list of strings) when importing the Browser library. Several formats are supported:
Module Path
*** Settings ***
Library Browser plugins=mypackage.myplugin.MyPlugin
The PluginParser imports the module and locates the class. The class must be a subclass of LibraryComponent.
File Path
*** Settings ***
Library Browser plugins=${CURDIR}/MyPlugin.py
When a file path is provided, PluginParser loads the module from the file system. The class name is inferred from the module contents.
Plugin with Arguments
*** Settings ***
Library Browser plugins=mypackage.MyPlugin;arg1;arg2
Semicolons separate the plugin path from positional arguments. These arguments are passed to the plugin constructor after the mandatory library parameter.
Multiple Plugins
*** Settings ***
Library Browser plugins=mypackage.PluginA,mypackage.PluginB
Comma-separated plugin specifications load multiple plugins simultaneously. Each is instantiated independently.
The Loading Pipeline in Detail
Step 1: Browser.__init__ Processes the plugins Argument
When the Browser class is instantiated, its __init__ method checks whether plugins is provided. The relevant code is at Browser/browser.py lines 894-900:
if plugins:
parser = PluginParser(LibraryComponent, [self])
parsed_plugins = parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
self._plugin_keywords = parser.get_plugin_keywords(parsed_plugins)
else:
self._plugin_keywords = []
The libraries list already contains all built-in keyword modules (like Interaction, Getters, Network, etc.). Plugin instances are appended to this list.
Step 2: PluginParser Construction
PluginParser is instantiated with two arguments:
| Argument | Value | Purpose |
|---|---|---|
| Base class | LibraryComponent |
Specifies that all plugins must inherit from this class. PluginParser validates this.
|
| Constructor args | [self] (the Browser instance) |
Arguments passed to each plugin constructor. Since LibraryComponent.__init__ expects a Browser instance, the library passes itself.
|
Step 3: parse_plugins Resolves and Instantiates
parser.parse_plugins(plugins) handles the string parsing, module importing, class instantiation, and returns a list of plugin component instances. The PluginParser from robotlibcore:
- Splits comma-separated plugin specifications
- For each specification, splits on semicolons to separate the class path from arguments
- Imports the module (by dotted path or file path)
- Instantiates the class with
[self] + extra_args - Validates that the instance is a subclass of
LibraryComponent
Step 4: Keyword Collection
parser.get_plugin_keywords(parsed_plugins) extracts the keyword names from all plugin instances. These names are stored in self._plugin_keywords for later use in tagging.
Step 5: DynamicCore Registration
After plugins are appended to the libraries list, DynamicCore.__init__(self, libraries, translation_file) is called. This scans all components for @keyword-decorated methods and registers them as Robot Framework keywords.
Keyword Merging and Conflicts
Plugin keywords are merged into the same namespace as built-in keywords. This means:
- No namespacing -- Plugin keywords are called by name just like built-in keywords
- Name conflicts -- If a plugin defines a keyword with the same name as a built-in keyword, the behavior depends on the
DynamicCoreimplementation (typically the last-registered wins) - Best practice -- Plugin authors should use unique, descriptive keyword names to avoid conflicts
Plugin Keyword Tagging
The Browser.get_keyword_tags() method (at lines 1301-1305) automatically adds the "Plugin" tag to any keyword whose name appears in self._plugin_keywords:
def get_keyword_tags(self, name: str) -> list:
tags = list(DynamicCore.get_keyword_tags(self, name))
if name in self._plugin_keywords:
tags.append("Plugin")
return tags
This provides a runtime mechanism to distinguish plugin keywords from built-in keywords without requiring plugin authors to manually tag their keywords.
Plugin Lifecycle
| Phase | Timing | What Happens |
|---|---|---|
| Import | Library Browser plugins=... |
Plugin classes are discovered, instantiated, and registered. JavaScript extensions are loaded. |
| Execution | Test keywords are called | Plugin keywords execute identically to built-in keywords. |
| Teardown | Library scope ends | No special plugin teardown mechanism. Plugins can implement Robot Framework listener methods if cleanup is needed. |
Plugins can also implement the Robot Framework Listener API by setting ROBOT_LISTENER_API_VERSION on the class. The ExamplePlugin in the test suite demonstrates this with ROBOT_LISTENER_API_VERSION = 2 and an end_keyword method.
Error Handling
- If a plugin module cannot be imported,
PluginParserraises anImportErrorduring library instantiation - If a plugin class does not inherit from
LibraryComponent,PluginParserraises a validation error - If a plugin's
__init__raises an exception (e.g., a JavaScript extension file not found), the error propagates and prevents library initialization
Domains
Implemented By
Related
- MarketSquare_Robotframework_browser_PluginParser_Parse_Plugins -- Implementation details of the
PluginParsercall - MarketSquare_Robotframework_browser_Plugin_Architecture -- The architecture that plugins extend
- MarketSquare_Robotframework_browser_LibraryComponent_Init -- The base class all plugins must inherit