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 Create Lib Component From Jsextension

From Leeroopedia
Revision as of 11:29, 16 February 2026 by Admin (talk | contribs) (Auto-imported from implementations/MarketSquare_Robotframework_browser_Create_Lib_Component_From_Jsextension.md)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Document Type

API Doc -- Documents the Python APIs responsible for loading JavaScript extension modules, introspecting their exports via gRPC, and dynamically generating Python wrapper methods that become Robot Framework keywords.

API Summary

Three methods in the Browser class collaborate to register JavaScript extension keywords:

  1. _create_lib_component_from_jsextension -- Orchestrates the full pipeline from file path to registered LibraryComponent
  2. init_js_extension -- Sends the module path to the Node.js gRPC server and receives keyword metadata
  3. _jskeyword_call -- Generates a Python wrapper method for a single JavaScript function and binds it to a component

Source References

File Lines Description
Browser/browser.py L888-893 jsextension handling in __init__
Browser/browser.py L997-1009 _create_lib_component_from_jsextension
Browser/browser.py L1011-1017 init_js_extension
Browser/browser.py L1019-1028 _js_value_to_python_value
Browser/browser.py L1030-1084 _jskeyword_call

Entry Point: jsextension in __init__

During library initialization, if the jsextension parameter is provided, it is split and each extension is processed:

if jsextension:
    jsextensions = (
        jsextension.split(",") if isinstance(jsextension, str) else jsextension
    )
    for js_ext in jsextensions:
        libraries.append(self._create_lib_component_from_jsextension(js_ext))

Parameters:

  • jsextension -- A string (single path or comma-separated paths) or a list of paths to JavaScript module files

Behavior:

  • Splits comma-separated strings into individual paths
  • Creates a LibraryComponent for each extension
  • Appends each component to the libraries list that is passed to DynamicCore.__init__

API: _create_lib_component_from_jsextension

def _create_lib_component_from_jsextension(
    self, jsextension: str
) -> LibraryComponent:
    component = LibraryComponent(self)
    response = self.init_js_extension(Path(jsextension))
    for name, args, doc in zip(
        response.keywords,
        response.keywordArguments,
        response.keywordDocumentations,
        strict=False,
    ):
        self._jskeyword_call(component, name, args, doc)
    return component

Parameters:

  • jsextension (str) -- Path to the JavaScript extension module file

Returns:

  • LibraryComponent -- A component instance with all JavaScript functions registered as bound methods

Behavior:

  1. Creates a new empty LibraryComponent instance with self (the Browser library) as its parent
  2. Calls init_js_extension to load the module on the Node.js side and receive metadata
  3. Iterates over the parallel arrays in the response (keywords, keywordArguments, keywordDocumentations)
  4. For each function, calls _jskeyword_call to generate and bind a Python wrapper method

API: init_js_extension

def init_js_extension(self, js_extension_path: Path | str) -> Response.Keywords:
    with self.playwright.grpc_channel() as stub:
        return stub.InitializeExtension(
            Request().FilePath(
                path=str(Path(js_extension_path).resolve().absolute())
            )
        )

Parameters:

  • js_extension_path (Path | str) -- Path to the JavaScript file, resolved to an absolute path before sending

Returns:

  • Response.Keywords -- A protobuf message containing:
    • keywords -- List of function names (strings)
    • keywordArguments -- List of argument specification strings (comma-separated name=default pairs)
    • keywordDocumentations -- List of documentation strings (from .rfdoc or empty)

Behavior:

  • Opens a gRPC channel to the Node.js Playwright wrapper
  • Sends an InitializeExtension request with the resolved absolute file path
  • The Node.js side calls require() on the file, introspects exports, and returns metadata

Helper: _js_value_to_python_value

def _js_value_to_python_value(self, value: str) -> str:
    return {
        "true": "True",
        "false": "False",
        "null": "None",
        "undefined": "None",
        "NaN": "float('nan')",
        "Infinity": "float('inf')",
        "-Infinity": "float('-inf')",
    }.get(value, value)

Parameters:

  • value (str) -- A JavaScript default value as a string

Returns:

  • The Python equivalent string representation, or the original value if no mapping exists

API: _jskeyword_call

This is the most complex method, responsible for dynamic code generation:

def _jskeyword_call(
    self,
    component: LibraryComponent,
    name: str,
    argument_names_and_default_values: str,
    doc: str,
):
    argument_names_and_vals = [
        [a.strip() for a in arg.split("=")]
        for arg in (argument_names_and_default_values or "").split(",")
        if arg
    ]
    argument_names_and_default_values_texts = []
    arg_set_texts = []
    for item in argument_names_and_vals:
        arg_name = item[0]
        if arg_name in ["logger", "playwright", "page", "context", "browser"]:
            arg_set_texts.append(f'("{arg_name}", "RESERVED")')
        else:
            arg_set_texts.append(f'("{arg_name}", {arg_name})')
            if arg_name == "args":
                argument_names_and_default_values_texts.append("*args")
            elif len(item) > 1:
                argument_names_and_default_values_texts.append(
                    f"{arg_name}={self._js_value_to_python_value(item[1])}"
                )
            else:
                argument_names_and_default_values_texts.append(f"{arg_name}")
    text = f"""
@keyword
def {name}(self, {", ".join(argument_names_and_default_values_texts)}):
    \"\"\"{doc}\"\"\"
    _args_browser_internal = dict()
    _args_browser_internal["arguments"] = [{", ".join(arg_set_texts)}]
    with self.playwright.grpc_channel() as stub:
        responses = stub.CallExtensionKeyword(
            Request().KeywordCall(name="{name}", arguments=json.dumps(_args_browser_internal))
        )
        for response in responses:
            logger.info(response.log)
        if response.json == "":
            return
        return json.loads(response.json)
"""
    try:
        exec(
            text,
            {**globals(), "keyword": keyword, "json": json},
            component.__dict__,
        )
        setattr(
            component, name, types.MethodType(component.__dict__[name], component)
        )
    except SyntaxError as e:
        raise DataError(f"{e.msg} in {name}")

Parameters:

  • component (LibraryComponent) -- The component to bind the generated method to
  • name (str) -- The JavaScript function name (becomes the Python method name)
  • argument_names_and_default_values (str) -- Comma-separated argument specifications (e.g., "selector,page,logger" or "a=default")
  • doc (str) -- Documentation string from the .rfdoc property

Behavior:

The method performs these steps:

  1. Parse arguments: Splits the argument specification string by commas, then each argument by = to separate name from default value
  1. Classify arguments: For each argument:
    • If the name is a reserved name (logger, playwright, page, context, browser): adds ("name", "RESERVED") to the argument tuple list but does not add it to the Python function's parameter list
    • If the name is args: maps it to *args (variadic positional arguments)
    • If there is a default value: maps JavaScript defaults to Python equivalents and adds name=default
    • Otherwise: adds the name as a required positional argument
  1. Generate Python code: Constructs a Python function definition string decorated with @keyword that:
    • Accepts self plus the user-facing arguments
    • Builds an arguments dictionary with reserved names set to "RESERVED"
    • Calls stub.CallExtensionKeyword via gRPC
    • Logs messages from the response stream
    • Returns the JSON-parsed result or None
  1. Execute and bind: Uses exec() to compile the function definition, then binds it to the component using types.MethodType
  1. Error handling: Catches SyntaxError (e.g., if a parameter named self was used) and raises DataError

Generated Code Example

For a JavaScript function:

async function withDefaultValue(a = "default") { ... }
withDefaultValue.rfdoc = "Returns the uppercased value";

The generated Python method would be equivalent to:

@keyword
def withDefaultValue(self, a="default"):
    """Returns the uppercased value"""
    _args_browser_internal = dict()
    _args_browser_internal["arguments"] = [("a", a)]
    with self.playwright.grpc_channel() as stub:
        responses = stub.CallExtensionKeyword(
            Request().KeywordCall(name="withDefaultValue", arguments=json.dumps(_args_browser_internal))
        )
        for response in responses:
            logger.info(response.log)
        if response.json == "":
            return
        return json.loads(response.json)

Related

Page Connections

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