Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:MarketSquare Robotframework browser Js Module Exports Pattern

From Leeroopedia

Document Type

Pattern Doc -- Describes the canonical CommonJS module structure for JavaScript extension files that provide custom Robot Framework keywords to the Browser library.

Pattern Summary

A JavaScript extension module must use CommonJS exports with exports.__esModule = true and individually exported functions. Each exported function becomes a Robot Framework keyword. Functions may be async or synchronous, may use reserved parameter names for Playwright object injection, and may attach a .rfdoc property for documentation.

Source References

File Lines Description
atest/test/05_JS_Tests/funky.js L1-74 Comprehensive example with multiple function styles
atest/test/05_JS_Tests/another.js L1-5 Minimal example with arrow function export

Comprehensive Example

The following is the complete funky.js extension module from the test suite, demonstrating all supported patterns:

async function myFunkyKeyword(page, args, logger) {
    const h = await page.locator(args[0]);
    logger("Logging something funky");
    return await h.evaluate((e) => e.textContent = "Funk yeah!");
}

async function myNewStyleFunkyKeyword(selector, page, logger) {
    const h = await page.$(selector);
    logger("Logging something funky here");
    return await h.evaluate((e) => e.textContent = "Funk yeah again!");
}

let browserServer;

async function createRemoteBrowser(logger, playwright) {
    logger("Launching chromium server");
    browserServer = await playwright.chromium.launchServer({headless: false});
    logger("Returning server address");
    return browserServer.wsEndpoint();
}

async function withDefaultValue(a = "default") {
    return a.toUpperCase();
}
withDefaultValue.rfdoc = "This function returns the default value if no argument is passed";

async function closeRemoteBrowser() {
    return browserServer.kill();
}

function crashKeyword() {
    throw Error("Crash");
}

async function moreDefaults(bTrue = true,
                            bFalse = false,
                            integer = 123,
                            floater = 1.3,
                            text = "hello",
                            nothing = null,
                            undefineder = undefined) {
    return {
        bTrue,
        bFalse,
        integer,
        floater,
        text,
        nothing,
        "undefineder": undefineder || null
    };
}

async function contextAndBrowserDemo(message, context, browser, logger) {
    logger(`Message: ${message}`);
    logger(`Browser: ${browser}`);
    logger(`Context: ${context}`)
    const pages = context.pages();
    logger(`Number of pages in context: ${pages.length}`);
    return {
        message,
        browserType: browser?.browserType().name() ?? "NO BROWSER",
        pageCount: pages.length
    };
}

exports.__esModule = true;
exports.myFunkyKeyword = myFunkyKeyword;
exports.createRemoteBrowser = createRemoteBrowser;
exports.closeRemoteBrowser = closeRemoteBrowser;
exports.crashKeyword = crashKeyword;
exports.withDefaultValue = withDefaultValue;
exports.moreDefaults = moreDefaults;
exports.myNewStyleFunkyKeyword = myNewStyleFunkyKeyword;
exports.contextAndBrowserDemo = contextAndBrowserDemo;

Minimal Example

The another.js file shows the smallest viable extension module using an arrow function:

exports.__esModule = true;
exports.myOtherKeyword = (arg, logger) => {
    logger("Logging something else");
    return arg;
}

This produces a single keyword My Other Keyword that accepts one user argument (arg) and one reserved parameter (logger).

Pattern Breakdown

1. Module Header: __esModule Flag

exports.__esModule = true;

This flag is required. It signals to the Browser library's module loader that this is a properly structured extension module following ES module interop conventions.

2. Legacy Style: args Array

async function myFunkyKeyword(page, args, logger) {
    const h = await page.locator(args[0]);
    // ...
}

The args parameter maps to Python's *args. The caller passes positional arguments which arrive as an array. Reserved parameters page and logger are injected automatically.

3. New Style: Named Parameters

async function myNewStyleFunkyKeyword(selector, page, logger) {
    const h = await page.$(selector);
    // ...
}

The selector parameter is a named user-supplied argument. This style is preferred for clarity and type safety.

4. Default Values

async function moreDefaults(bTrue = true,
                            bFalse = false,
                            integer = 123,
                            floater = 1.3,
                            text = "hello",
                            nothing = null,
                            undefineder = undefined) {

Default values are introspected by the Node.js side and reported to Python. The Python wrapper converts them using the mapping defined in _js_value_to_python_value():

JS Default Python Default
true True
false False
null None
undefined None
123 123
1.3 1.3
"hello" "hello"

5. Custom Documentation with .rfdoc

withDefaultValue.rfdoc = "This function returns the default value if no argument is passed";

The .rfdoc property is read during InitializeExtension and passed to the Python side as the keyword's docstring.

6. Error Throwing

function crashKeyword() {
    throw Error("Crash");
}

Synchronous functions that throw are supported. The error propagates through gRPC and becomes a Robot Framework failure.

7. Context and Browser Access

async function contextAndBrowserDemo(message, context, browser, logger) {
    const pages = context.pages();
    return {
        message,
        browserType: browser?.browserType().name() ?? "NO BROWSER",
        pageCount: pages.length
    };
}

Reserved parameters context and browser provide access to the active BrowserContext and Browser objects, enabling advanced multi-page and multi-browser operations.

8. Export Block

exports.__esModule = true;
exports.myFunkyKeyword = myFunkyKeyword;
exports.createRemoteBrowser = createRemoteBrowser;
// ... additional exports

Every function that should be exposed as a keyword must be explicitly assigned to exports. Functions defined in the module but not exported are private and will not become keywords.

Keywords Generated

From funky.js, the following Robot Framework keywords are registered:

Function Name Keyword Name User Arguments Reserved Parameters
myFunkyKeyword My Funky Keyword *args page, logger
myNewStyleFunkyKeyword My New Style Funky Keyword selector page, logger
createRemoteBrowser Create Remote Browser (none) logger, playwright
closeRemoteBrowser Close Remote Browser (none) (none)
crashKeyword Crash Keyword (none) (none)
withDefaultValue With Default Value a="default" (none)
moreDefaults More Defaults bTrue=True, bFalse=False, integer=123, floater=1.3, text="hello", nothing=None, undefineder=None (none)
contextAndBrowserDemo Context And Browser Demo message context, browser, logger

Related

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment