Principle:MarketSquare Robotframework browser JavaScript Extension Integration
JavaScript Extension Integration
Overview
The robotframework-browser library enables a powerful cross-language integration pattern: Python keyword methods can load and call JavaScript functions that run in the Node.js process alongside Playwright. This bridges the Python test layer with the JavaScript browser automation layer, allowing plugins to perform operations that require direct access to Playwright's JavaScript API, DOM manipulation, or custom browser-side logic.
Core Concept: Python-to-JavaScript Bridge
The Browser library operates as a two-process system:
- A Python process running Robot Framework and the Browser library
- A Node.js process running Playwright and handling browser automation via gRPC
JavaScript extensions allow plugin authors to write functions in JavaScript that execute in the Node.js process. These functions have direct access to Playwright objects and can perform operations that would be difficult or impossible to express purely through the gRPC protocol.
+------------------+ gRPC +------------------+ | Python Process | <--------------> | Node.js Process | | (Robot Framework| | (Playwright) | | + Plugins) | | + JS Extensions | +------------------+ +------------------+
How JavaScript Extensions Work
1. Extension Registration
A plugin calls self.initialize_js_extension(path) during its __init__ method. This sends the JavaScript file path to the Node.js process via gRPC, which loads the module and registers its exported functions as callable keywords.
class MyPlugin(LibraryComponent):
def __init__(self, library: Browser) -> None:
super().__init__(library)
self.initialize_js_extension(
Path(__file__).parent.resolve() / "my_extension.js"
)
2. JavaScript Function Definition
The JavaScript file exports async functions. Each exported function becomes a callable keyword:
async function mouseWheel(x, y, logger, page) {
logger(`Mouse wheel at ${x}, ${y}`);
await page.mouse.wheel(x, y);
return await page.evaluate("document.scrollingElement.scrollTop");
}
exports.__esModule = true;
exports.mouseWheel = mouseWheel;
3. Keyword Invocation
From a Python keyword method, the plugin calls the JavaScript function by name:
@keyword
def mouse_wheel(self, x: int, y: int):
return self.call_js_keyword("mouseWheel", y=y, x=x)
Reserved Parameter Names
JavaScript extension functions use reserved parameter names to receive Playwright objects automatically. When the Node.js process invokes the function, parameters with these names are injected with live Playwright instances rather than passed from the Python side:
| Parameter Name | Injected Object | Description |
|---|---|---|
page |
Playwright Page |
The currently active page object. Provides access to all page-level Playwright APIs. |
context |
Playwright BrowserContext |
The current browser context. Used for context-level operations like cookies, permissions, etc. |
browser |
Playwright Browser |
The browser instance. Used for browser-level operations. |
logger |
Logger function | A logging function that sends log messages back to the Robot Framework log. |
playwright |
Playwright module | The Playwright module itself. Provides access to selectors, devices, and other module-level APIs. |
These reserved parameters are identified on the Python side and replaced with the sentinel value "RESERVED" before transmission. The Node.js process detects this sentinel and substitutes the actual Playwright objects:
# From Browser/browser.py - call_js_keyword method
reserved = {
"logger": "RESERVED",
"playwright": "RESERVED",
"page": "RESERVED",
"context": "RESERVED",
"browser": "RESERVED",
}
Argument Passing
Non-reserved arguments are serialized as JSON and transmitted to the Node.js process. The JavaScript function receives them as native JavaScript values. This means:
- Python
str,int,float,bool,Nonemap to their JavaScript equivalents - Python
dictmaps to JavaScript objects - Python
listmaps to JavaScript arrays - Complex Python objects must be serialized before passing
Return Values
The JavaScript function's return value is serialized to JSON and sent back to Python. If the function returns undefined or no value, the Python side receives None. Otherwise, the JSON response is parsed back into Python objects.
Logging from JavaScript
The logger parameter in JavaScript extensions provides a way to send log messages back to the Robot Framework log output. Each call to logger("message") creates a log entry that appears in the Robot Framework report:
async function myFunction(logger, page) {
logger("Starting custom operation");
// ... do work ...
logger("Operation completed successfully");
}
These log messages appear at the INFO level in Robot Framework's log output.
Use Cases
JavaScript extensions are particularly useful for:
- Custom mouse/keyboard interactions not exposed through standard keywords (e.g., mouse wheel scrolling)
- Direct DOM queries that bypass Playwright's selector engine
- Browser API access such as Web Storage, Service Workers, or IndexedDB
- Performance measurements using the Navigation Timing API or Performance Observer
- Custom wait conditions expressed as JavaScript predicates
- Integration with page-specific JavaScript APIs in single-page applications
Two Loading Paths
There are two ways JavaScript extensions can be loaded:
1. Via the jsextension library argument:
*** Settings ***
Library Browser jsextension=${CURDIR}/my_extension.js
Keywords from this path are created automatically as methods on a LibraryComponent instance using dynamic code generation.
2. Via a plugin's __init__ method:
class MyPlugin(LibraryComponent):
def __init__(self, library):
super().__init__(library)
self.initialize_js_extension(Path(__file__).parent / "extension.js")
In this path, the plugin author calls initialize_js_extension to register the functions, then creates Python keyword methods that call self.call_js_keyword(). This gives the plugin author control over argument handling, return value processing, and keyword naming.
Domains
Implemented By
Related
- MarketSquare_Robotframework_browser_Initialize_Js_Extension -- API reference for
initialize_js_extensionandcall_js_keyword - MarketSquare_Robotframework_browser_Plugin_Architecture -- The plugin system that hosts JavaScript extensions
- MarketSquare_Robotframework_browser_Keyword_Method_Implementation -- How Python keyword methods wrap JavaScript calls