Skip to content

Document adoptContext for JavaScript extensions - #21

Open
d-biehl wants to merge 12 commits into
MarketSquare:mainfrom
d-biehl:adopt-context
Open

d-biehl wants to merge 12 commits into
MarketSquare:mainfrom
d-biehl:adopt-context

Conversation

@d-biehl

@d-biehl d-biehl commented Oct 9, 2026 •

Copy link
Copy Markdown

Documents adoptContext from MarketSquare/robotframework-browser#5318 on the JavaScript extensions page. The code is planned for Browser 20.7.0, so please merge this together with that release.

  • The names Browser fills in: the table gets adoptContext.
  • New section "Handing over a context you created": an Electron example, what adoptContext registers and returns, how the context is closed, and when onClose runs. A note says that Browser itself does not support Electron or Android.
  • "Which extension point?": the table gets a row for platforms that Browser does not launch.
  • Correction in "Custom selector strategies":
    • The warning said that every JavaScript extension keyword needs an open browser and fails with Browser has been closed.
    • Since 20.4.0, only keywords that take page, context or browser need one, and the message is No Browser is open but needed for this operation.
    • The new section relies on this, so the page now states it once below the table of names. The example's comment is removed.
    • I ran the selector example both ways against Browser 20.6.0, registering before and after New Browser. The selector worked in both.

The :since marker says 20.7.0, the milestone that is open now. pnpm verify passes.

The section "From a library written in Python" describes adopted_context_options, register_adopted_context and AdoptedContextOptions from MarketSquare/robotframework-browser#5326, so this PR should not be merged before that one. pnpm verify still passes.

AI / tooling disclosure
This contribution was prepared with assistance from Claude Code (Anthropic). I reviewed the result manually, understand the changes, verified them locally, and confirm that I have the right to submit this contribution under the project license.

Documents adoptContext from MarketSquare/robotframework-browser#5318, planned for Browser 20.7.0. The custom selector warning is corrected: since 20.4.0, only keywords that take page, context or browser need an open browser.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The documented return shape incorrectly guarantees pageId for contexts without an existing page.

1 open finding
What changed in this PR

Documents the upcoming adoptContext JavaScript extension API and corrects browser-state requirements.

Changes:

  • Adds adoptContext usage and lifecycle guidance.
  • Updates injected-argument and extension-point tables.
  • Corrects custom-selector prerequisites.
File Description
content/​docs/​extending/​javascript-extensions.md Documents context adoption and updates extension guidance.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread content/docs/extending/javascript-extensions.md Outdated
adoptContext takes the optional name, headless and onClose, and an adopted context gets Browser's timeout. The concepts page names adopted contexts among the browsers that close together with their context.
adoptContext no longer sets the library timeout. Extension functions can request defaultTimeout like the other injected names and set it on the context they adopt; the Electron example does so.
Browser passes the tracing option to Node as given, so a relative path would be relative to the Node process, not to the output directory.
A library written in Python gets the tracing and recording options of an adopted context from adopted_context_options and registers the context with register_adopted_context.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The Python integration example is not callable as shown, its JavaScript function is not exported, and several documentation inconsistencies remain.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread content/docs/extending/javascript-extensions.md Outdated
Comment thread content/docs/extending/javascript-extensions.md Outdated
The method of the example library is decorated with @Keyword, so that Robot Framework sees it, and the JavaScript module exports its function. The summary counts the seven names that Browser fills in.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The examples need type conversion and failure cleanup fixes to avoid invalid options and leaked Electron processes.

1 open finding
2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Clean up Electron process when launch or adoption fails

content/​docs/​extending/​javascript-extensions.md:156

If waiting for the first window or adopting the context fails, this launched Electron process is never handed to Browser and therefore cannot be cleaned up by Close Browser or automatic closing. Close the application on this error path (and kill it if graceful close fails) so a failed launch keyword does not leak a process.

This issue also appears on line 239 of the same file.

🧠 Review effort: Balanced

Comment thread content/docs/extending/javascript-extensions.md
…d launch

The Python example types its arguments with Browser's types, so that Robot Framework converts them as for New Context. A note says that a real module closes the application when anything after the launch fails.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The Python example does not initialize its JavaScript module, so its documented keyword call fails.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Register electron.js before invoking launchElectronApplication

content/​docs/​extending/​javascript-extensions.md:231

The subclass never registers electron.js, so call_js_keyword("launchElectronApplication", ...) has no Node-side function to invoke and this Python-library example fails as shown. Initialize the module in the subclass before calling it; using init_js_extension also avoids exposing a second Robot keyword with the same name as this wrapper.

🧠 Review effort: Balanced

@d-biehl

d-biehl commented Oct 11, 2026

Copy link
Copy Markdown
Author

About the note in the last review that the Python example never loads electron.js: the page shows two ways to do the same thing, a JavaScript extension loaded with jsextension and a library written in Python. Each example shows only its own part, so the Python one leaves out how the library loads its module, for example with init_js_extension. I left it as it is.

The example passes tracesDir from adopted_context_options to _electron.launch, so that Playwright's temporary trace files land in Browser's trace folder. The description of adopted_context_options names the empty recordVideo, which records with the defaults, the viewport argument that gives the video its size, and tracesDir.

@aaltat aaltat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this reads well. #5354 changed call_js_keyword: it now fills in defaultTimeout with the library timeout when the caller leaves it out, and a value the caller passes still wins. Three small changes to match, two as suggestions below.

3. python-plugins.md isn't in this PR, but its warning about call_js_keyword is now out of date. It lists five reserved names, and adoptContext and defaultTimeout are missing. Could you update it here, since this PR documents adoptContext? Something like:

Every argument to call_js_keyword must be named and JSON serialisable — passing one positionally raises TypeError. The Node side fills in page, context, browser, logger, playwright and adoptContext itself, so a value you pass for them is ignored. defaultTimeout is the library timeout in milliseconds unless you pass your own.

Comment on lines +240 to +245
adopted = self.call_js_keyword(
"launchElectronApplication",
executablePath=executable_path,
defaultTimeout=int(self.timeout),
**options,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. The Python library no longer needs to pass the timeout:

Suggested change
adopted = self.call_js_keyword(
"launchElectronApplication",
executablePath=executable_path,
defaultTimeout=int(self.timeout),
**options,
)
adopted = self.call_js_keyword(
"launchElectronApplication",
executablePath=executable_path,
**options,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +277 to +279
- **`register_adopted_context`** registers the context after the call, so that
the keyword calls become groups in the trace and the log embeds the video.
It returns the video path, or an empty string without a video.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. One more bullet after register_adopted_context:

Suggested change
- **`register_adopted_context`** registers the context after the call, so that
the keyword calls become groups in the trace and the log embeds the video.
It returns the video path, or an empty string without a video.
- **`register_adopted_context`** registers the context after the call, so that
the keyword calls become groups in the trace and the log embeds the video.
It returns the video path, or an empty string without a video.
- **`defaultTimeout`** is filled in by `call_js_keyword` too, with the library
timeout. Pass your own only when your library keeps a timeout of its own; it
is the one filled-in name you can override.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

MarketSquare/robotframework-browser#5354 makes call_js_keyword fill in defaultTimeout with the library timeout when the caller leaves it out. The Python example no longer passes it, a new bullet says when to pass your own, and the warning in the Python plugins guide names adoptContext and defaultTimeout as well.
@d-biehl

d-biehl commented Oct 17, 2026

Copy link
Copy Markdown
Author

Thanks! Point 3 is in as well: the warning in python-plugins.md now names adoptContext and defaultTimeout, with your wording.

@aaltat

aaltat commented Oct 22, 2026

Copy link
Copy Markdown
Member

Thanks for waiting. The implementation changed after this PR was written, in MarketSquare/robotframework-browser#5370 and #5372 (both merged, shipping in 20.7.0, decision in ADR 0010). Browser now does more of the work itself, so the page gets shorter. In javascript-extensions.md:

Handing over a context you created, first example: drop defaultTimeout and setDefaultTimeout. adoptContext now gives the context the library timeout of the call:

async function launchElectronApplication(executablePath, playwright, adoptContext) {
  const app = await playwright._electron.launch({ executablePath });
  await app.firstWindow();
  return adoptContext(app.context(), { name: 'electron' });
}

Bullets under adoptContext(context, options):

  • Its pages become Browser's pages: add the new option activePage. By default the page Playwright saw first becomes active. activePage names the page to make active instead, and it must be a page of that context. An Electron app that shows a WebContentsView or <webview> first needs it to get the window, and so does a WebView page on Android (device.webView(...).page()).
  • It keeps its own timeout: replace it with "It gets the library timeout, as the contexts Browser creates do; set another one after adoptContext if you need it." defaultTimeout stays in the table of filled-in names.
  • tracing records a trace: add that the keyword calls become groups in the trace, as with New Context. Browser registers every adopted context itself, also for a plain jsextension keyword, not only for a library in Python.
  • contextOptions: add that Browser also takes the video size from recordVideo, then viewport, then 1280×720. Without contextOptions the video is still embedded, just in that fallback size.
  • It returns the ids: it now also returns videoPath, the video of the active page, or null without a page or a video.

From a library written in Python: register_adopted_context is gone. It was never released, and Browser now registers the context from what Node reports while call_js_keyword runs. So:

        options = self.adopted_context_options(tracing=tracing, recordVideo=record_video, recordHar=record_har)
        return self.call_js_keyword("launchElectronApplication", executablePath=executable_path, **options)
async function launchElectronApplication(executablePath, tracing, recordVideo, recordHar, tracesDir, playwright, adoptContext) {
  const contextOptions = { acceptDownloads: true };
  if (recordVideo) contextOptions.recordVideo = recordVideo;
  if (recordHar) contextOptions.recordHar = recordHar;
  const app = await playwright._electron.launch({ executablePath, tracesDir, ...contextOptions });
  await app.firstWindow();
  return adoptContext(app.context(), { name: 'electron', tracing: tracing || undefined, contextOptions });
}

and replace the register_adopted_context bullet with something like: "Browser registers the context itself when adoptContext resolves: the keyword calls become groups in the trace, and the log embeds the video in the size of contextOptions. A context adopted before your function fails is registered too." The defaultTimeout bullet can stay.

One part will change again: "A real module closes the application when anything after the launch fails". That stays right until adoption succeeds. Next we will let Browser close a context adopted by a call that then fails, after run-on-failure has taken its screenshot. I will comment here again when that lands. It only changes the try/catch advice.

robotcodedev/robotframework-electron-vscode#2 shows the same changes applied to the Electron library.

…itself

Browser now registers an adopted context from the report of the extension call (MarketSquare/robotframework-browser#5372), gives it the library timeout and returns the video of its active page (MarketSquare/robotframework-browser#5370), and adoptContext takes an activePage. The examples no longer set the timeout, read the video path or call register_adopted_context, and the page describes activePage, the registration and videoPath.
@d-biehl

d-biehl commented Oct 22, 2026

Copy link
Copy Markdown
Author

Updated to the API on main: the examples no longer set the timeout, read the video path or call register_adopted_context, and the page now describes activePage, the registration by call_js_keyword and videoPath.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants