From ce577c56e98f348211aafbf2ee657bcfdcba8847 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sat, 10 Oct 2026 00:07:16 +0200 Subject: [PATCH 1/9] docs(extending): hand over a context you created with adoptContext 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. --- .../docs/extending/javascript-extensions.md | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index cf62531..bcfc589 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -46,11 +46,11 @@ Three conventions carry the whole API: - **Exported names become keywords** — the *export key*, not the function's own name. `exports.getLinks = getLinks` gives `Get Links`; the `exports.myMouseWheel = mouseWheel` below gives `My Mouse Wheel`. -- **Five argument names are filled in for you**, by name — see below. +- **Six argument names are filled in for you**, by name — see below. - **`fn.rfdoc` becomes the keyword documentation**, so your keyword shows up in Libdoc and in editor tooltips like any other. -### The five names Browser fills in +### The six names Browser fills in Name a parameter one of these and the library passes the object in. Everything else in your signature becomes an ordinary keyword argument. @@ -62,6 +62,7 @@ else in your signature becomes an ordinary keyword argument. | `browser` | The active [Browser](https://playwright.dev/docs/api/class-browser) | | `logger` | A function that writes to the Robot Framework log | | `playwright` | The [`playwright` module](https://playwright.dev/docs/api/class-playwright) itself | +| `adoptContext` | A function that hands a context you created to Browser — see [Handing over a context you created](#handing-over-a-context-you-created) | **They are matched by name, not by position.** Browser reads your parameter names and fills in the ones it recognises, so `mouseWheel(x, y, logger, page)` @@ -72,6 +73,12 @@ best. These names are reserved: a keyword cannot take an argument called `page` from Robot Framework, because that name is spoken for. `self` is not usable either. +:since{version="20.4.0"} **Only `page`, `context` and `browser` need an open +browser.** A keyword that takes one of them fails with `No Browser is open but +needed for this operation.` when none is open. A keyword that takes none of them +runs without one, which is what lets a module +[create the first browser itself](#handing-over-a-context-you-created). + One further name is special without being filled in. A parameter called `args` makes the keyword variadic — it receives Robot Framework's `*args`, so it carries values *to* your function rather than from the library. It cannot have a @@ -119,7 +126,7 @@ exports.registerMySelector = registerMySelector; *** Test Cases *** Use A Custom Engine New Browser chromium - Register My Selector # a browser must already be open + Register My Selector New Page ${URL} Click myselector=Some Title ``` @@ -128,17 +135,67 @@ Use A Custom Engine --- kind: warning --- -Two things this example depends on. - -**A browser must already be open.** Every JavaScript-extension keyword resolves -the active browser before it runs, even one whose only argument is `playwright`, -so calling this first fails with `Browser has been closed.` - **Register once per run.** `selectors.register` rejects a name that is already registered, and because the registration is awaited that rejection fails the keyword — unawaited, it takes the whole Node process down instead. :: +## Handing over a context you created + +:since{version="20.7.0"} A module can create a browser context itself and hand +it to Browser with `adoptContext`. That is how a library supports a platform +that Browser does not launch, such as an Electron application, without Browser +knowing about it: + +```javascript +async function launchElectronApplication(executablePath, playwright, adoptContext) { + const app = await playwright._electron.launch({ executablePath }); + await app.firstWindow(); + return adoptContext(app.context()); +} +exports.__esModule = true; +exports.launchElectronApplication = launchElectronApplication; +``` + +```robot +*** Settings *** +Library Browser jsextension=${CURDIR}/electron.js + +*** Test Cases *** +Read The Title Of An Electron Application + Launch Electron Application /path/to/app + Get Title == My App + Close Browser +``` + +`adoptContext(context, onClose)` adds the context as a new browser and makes it +the active one, the same way Browser keeps a persistent context: + +- **Its pages become Browser's pages.** The pages the context already has are + indexed and the first one becomes the active page, so `Click`, `Get Text` and + the other keywords work on it right away. Pages it opens later can be selected + with `Switch Page`. +- **It returns the ids** of the new browser, context and page, as an object with + `browserId`, `contextId` and `pageId`. Return it from your function and the + keyword returns it to Robot Framework. +- **Closing the browser closes the context.** `Close Browser` and automatic + closing treat it like any other browser. +- **`onClose` releases the rest.** The optional async `onClose` runs once after + the context is closed, even when closing it failed. Pass it for what the + context does not own. An Electron application quits with its context and needs + none; a connection to an Android device does not close by itself: + `adoptContext(context, () => device.close())`. + +::doc-note +--- +kind: aside +--- +Browser does not support Electron or Android itself, and `_electron` and +`_android` are experimental in Playwright. The module, and keeping it working +with the platform, are yours. A module that does more than launch belongs in a +library of its own, built on Browser. +:: + ## Debugging You can attach a Node debugger to the Playwright process — to your own extension @@ -230,6 +287,7 @@ rows: - ['Assertion arguments like the built-in getters', 'Python plugin with AssertionEngine'] - ['To replace an existing keyword', 'Python plugin'] - ['A custom selector engine', 'JavaScript module'] + - ['A platform Browser does not launch, such as Electron', 'JavaScript module that [hands over its context](#handing-over-a-context-you-created), usually in a library of its own'] - ['Both Python and page-side logic in one keyword', 'Python plugin calling a JS module'] - ['Business logic in Python — `IF`, `TRY`, parsing — with Browser unchanged', '[Your own Python library](/docs/extending/python-libraries)'] - ['Your own keywords and your own failure handling, with Browser underneath', '[Browser as a base](/docs/extending/browser-as-a-base)'] From 7621eefb74745e52d50950b2dee46e651e712493 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sat, 10 Oct 2026 00:26:38 +0200 Subject: [PATCH 2/9] docs(extending): pageId only comes back when the context has a page --- content/docs/extending/javascript-extensions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index bcfc589..db0728e 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -175,9 +175,9 @@ the active one, the same way Browser keeps a persistent context: indexed and the first one becomes the active page, so `Click`, `Get Text` and the other keywords work on it right away. Pages it opens later can be selected with `Switch Page`. -- **It returns the ids** of the new browser, context and page, as an object with - `browserId`, `contextId` and `pageId`. Return it from your function and the - keyword returns it to Robot Framework. +- **It returns the ids** of the new browser and context, as an object with + `browserId` and `contextId`, plus `pageId` if the context has a page. Return + it from your function and the keyword returns it to Robot Framework. - **Closing the browser closes the context.** `Close Browser` and automatic closing treat it like any other browser. - **`onClose` releases the rest.** The optional async `onClose` runs once after From a78c633a37373b3cc34e923f08353538cf8052b0 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sat, 10 Oct 2026 12:24:55 +0200 Subject: [PATCH 3/9] docs(extending): describe the options and the timeout of adoptContext 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. --- content/docs/concepts/browser-context-page.md | 6 ++++-- .../docs/extending/javascript-extensions.md | 20 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/content/docs/concepts/browser-context-page.md b/content/docs/concepts/browser-context-page.md index fd2b839..d0def25 100644 --- a/content/docs/concepts/browser-context-page.md +++ b/content/docs/concepts/browser-context-page.md @@ -297,8 +297,10 @@ and `Switch Browser`, `Switch Context` and `Switch Page` move that pointer. By default, the **contexts and pages** a test opened are closed when the test ends. Browsers are not: no auto-closing level closes a browser per test or per suite, so a browser lives until execution ends or you call `Close Browser`. The -one exception is a browser opened by `New Persistent Context` — it is closed -together with its context. +exceptions are a browser opened by `New Persistent Context` and one that a +JavaScript extension +[handed over with `adoptContext`](/docs/extending/javascript-extensions#handing-over-a-context-you-created) +— each is closed together with its context. That setting is `auto_closing_level`, and it has four values: diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index db0728e..435df1c 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -151,7 +151,7 @@ knowing about it: async function launchElectronApplication(executablePath, playwright, adoptContext) { const app = await playwright._electron.launch({ executablePath }); await app.firstWindow(); - return adoptContext(app.context()); + return adoptContext(app.context(), { name: 'electron' }); } exports.__esModule = true; exports.launchElectronApplication = launchElectronApplication; @@ -168,23 +168,29 @@ Read The Title Of An Electron Application Close Browser ``` -`adoptContext(context, onClose)` adds the context as a new browser and makes it +`adoptContext(context, options)` adds the context as a new browser and makes it the active one, the same way Browser keeps a persistent context: - **Its pages become Browser's pages.** The pages the context already has are indexed and the first one becomes the active page, so `Click`, `Get Text` and the other keywords work on it right away. Pages it opens later can be selected with `Switch Page`. +- **It gets Browser's timeout.** Like the contexts Browser creates, its default + timeout is the library timeout at that moment. For another one, call + `context.setDefaultTimeout()` after adopting it. +- **`options` describe the browser.** All of them are optional. `name` is what + `Get Browser Catalog` reports as its type, `adopted` by default, and `headless` + says whether it runs headless, `false` by default. - **It returns the ids** of the new browser and context, as an object with `browserId` and `contextId`, plus `pageId` if the context has a page. Return it from your function and the keyword returns it to Robot Framework. - **Closing the browser closes the context.** `Close Browser` and automatic closing treat it like any other browser. -- **`onClose` releases the rest.** The optional async `onClose` runs once after - the context is closed, even when closing it failed. Pass it for what the - context does not own. An Electron application quits with its context and needs - none; a connection to an Android device does not close by itself: - `adoptContext(context, () => device.close())`. +- **`onClose` releases the rest.** The async option `onClose` runs once after the + context is closed, even when closing it failed. Pass it for what the context + does not own. An Electron application quits with its context and needs none; a + connection to an Android device does not close by itself: + `adoptContext(context, { onClose: () => device.close() })`. ::doc-note --- From 08955ffe2c9a792d741e211fd855b2df3fb8dd65 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sat, 10 Oct 2026 17:44:19 +0200 Subject: [PATCH 4/9] docs(extending): describe tracing and contextOptions of adoptContext --- content/docs/extending/javascript-extensions.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index 435df1c..41322c0 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -181,6 +181,14 @@ the active one, the same way Browser keeps a persistent context: - **`options` describe the browser.** All of them are optional. `name` is what `Get Browser Catalog` reports as its type, `adopted` by default, and `headless` says whether it runs headless, `false` by default. +- **`tracing` records a trace.** Pass the path of a trace file or a folder, and + Browser starts tracing the context and saves the trace before it closes the + context, as with `tracing` of `New Context`. +- **`contextOptions` say how the context was created.** Pass the options you + created it with, in the form of Playwright's browser context options. Browser + keywords that depend on them then work on it: `Download` needs + `acceptDownloads`, which an Electron application has by default, so pass + `{ acceptDownloads: true }` for one. - **It returns the ids** of the new browser and context, as an object with `browserId` and `contextId`, plus `pageId` if the context has a page. Return it from your function and the keyword returns it to Robot Framework. From 10ab80a2f79b2f9161426a394ddaca52f8e5dfad Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sun, 11 Oct 2026 01:52:40 +0200 Subject: [PATCH 5/9] docs(extending): take the library timeout as defaultTimeout 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. --- content/docs/extending/javascript-extensions.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index 41322c0..808d997 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -50,9 +50,9 @@ Three conventions carry the whole API: - **`fn.rfdoc` becomes the keyword documentation**, so your keyword shows up in Libdoc and in editor tooltips like any other. -### The six names Browser fills in +### The seven names Browser fills in -Name a parameter one of these and the library passes the object in. Everything +Name a parameter one of these and the library passes the value in. Everything else in your signature becomes an ordinary keyword argument. | Name | You get | @@ -63,6 +63,7 @@ else in your signature becomes an ordinary keyword argument. | `logger` | A function that writes to the Robot Framework log | | `playwright` | The [`playwright` module](https://playwright.dev/docs/api/class-playwright) itself | | `adoptContext` | A function that hands a context you created to Browser — see [Handing over a context you created](#handing-over-a-context-you-created) | +| `defaultTimeout` | The library timeout in milliseconds, as `Set Browser Timeout` sets it | **They are matched by name, not by position.** Browser reads your parameter names and fills in the ones it recognises, so `mouseWheel(x, y, logger, page)` @@ -148,9 +149,10 @@ that Browser does not launch, such as an Electron application, without Browser knowing about it: ```javascript -async function launchElectronApplication(executablePath, playwright, adoptContext) { +async function launchElectronApplication(executablePath, playwright, adoptContext, defaultTimeout) { const app = await playwright._electron.launch({ executablePath }); await app.firstWindow(); + app.context().setDefaultTimeout(defaultTimeout); return adoptContext(app.context(), { name: 'electron' }); } exports.__esModule = true; @@ -175,9 +177,10 @@ the active one, the same way Browser keeps a persistent context: indexed and the first one becomes the active page, so `Click`, `Get Text` and the other keywords work on it right away. Pages it opens later can be selected with `Switch Page`. -- **It gets Browser's timeout.** Like the contexts Browser creates, its default - timeout is the library timeout at that moment. For another one, call - `context.setDefaultTimeout()` after adopting it. +- **It keeps its own timeout.** Browser does not change the context's default + timeout. To give it the library timeout, as the contexts Browser creates have, + take `defaultTimeout` and call `context.setDefaultTimeout(defaultTimeout)`, as + the example does. - **`options` describe the browser.** All of them are optional. `name` is what `Get Browser Catalog` reports as its type, `adopted` by default, and `headless` says whether it runs headless, `false` by default. From a50fad34c6be7b5632973cfb8e70e3b6032ab9fc Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sun, 11 Oct 2026 02:06:56 +0200 Subject: [PATCH 6/9] docs(extending): adoptContext takes an absolute trace path 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. --- content/docs/extending/javascript-extensions.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index 808d997..4aa3d61 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -184,9 +184,11 @@ the active one, the same way Browser keeps a persistent context: - **`options` describe the browser.** All of them are optional. `name` is what `Get Browser Catalog` reports as its type, `adopted` by default, and `headless` says whether it runs headless, `false` by default. -- **`tracing` records a trace.** Pass the path of a trace file or a folder, and - Browser starts tracing the context and saves the trace before it closes the - context, as with `tracing` of `New Context`. +- **`tracing` records a trace.** Pass the absolute path of a `.zip` trace file + or of a folder, which gets `trace_.zip`, and Browser starts tracing + the context and saves the trace before it closes the context, as with `tracing` + of `New Context`. A relative path would be relative to Browser's Node process, + not to the output directory. - **`contextOptions` say how the context was created.** Pass the options you created it with, in the form of Playwright's browser context options. Browser keywords that depend on them then work on it: `Download` needs From dfaedd859570c1332d598d800e33ce7bb2b303f2 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sun, 11 Oct 2026 13:47:12 +0200 Subject: [PATCH 7/9] docs(extending): describe the Python methods for adopted contexts 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. --- .../docs/extending/javascript-extensions.md | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index 4aa3d61..ac49f47 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -188,7 +188,8 @@ the active one, the same way Browser keeps a persistent context: or of a folder, which gets `trace_.zip`, and Browser starts tracing the context and saves the trace before it closes the context, as with `tracing` of `New Context`. A relative path would be relative to Browser's Node process, - not to the output directory. + not to the output directory. A library written in Python gets the path from + Browser, see [From a library written in Python](#from-a-library-written-in-python). - **`contextOptions` say how the context was created.** Pass the options you created it with, in the form of Playwright's browser context options. Browser keywords that depend on them then work on it: `Download` needs @@ -205,6 +206,54 @@ the active one, the same way Browser keeps a persistent context: connection to an Android device does not close by itself: `adoptContext(context, { onClose: () => device.close() })`. +### From a library written in Python + +A library built on Browser in Python calls the module with `call_js_keyword` +and lets Browser decide where traces, videos and HAR files go, by the same rules +as for `New Context`: + +```python +from Browser import Browser + + +class ElectronLibrary(Browser): + def launch_electron_application(self, executable_path, tracing=None, record_video=None, record_har=None): + options = self.adopted_context_options(tracing=tracing, recordVideo=record_video, recordHar=record_har) + adopted = self.call_js_keyword( + "launchElectronApplication", + executablePath=executable_path, + defaultTimeout=int(self.timeout), + **options, + ) + self.register_adopted_context(adopted["contextId"], options, adopted.get("videoPath")) + return adopted +``` + +```javascript +async function launchElectronApplication(executablePath, tracing, recordVideo, recordHar, defaultTimeout, playwright, adoptContext) { + const contextOptions = { acceptDownloads: true }; + if (recordVideo) contextOptions.recordVideo = recordVideo; + if (recordHar) contextOptions.recordHar = recordHar; + const app = await playwright._electron.launch({ executablePath, ...contextOptions }); + const window = await app.firstWindow(); + app.context().setDefaultTimeout(defaultTimeout); + const adopted = await adoptContext(app.context(), { name: 'electron', tracing: tracing || undefined, contextOptions }); + return { ...adopted, videoPath: recordVideo ? await window.video()?.path() : null }; +} +``` + +- **`adopted_context_options`** returns an `AdoptedContextOptions`, a typed + dictionary with `tracing`, `recordVideo` and `recordHar` as `New Context` + would use them, `None` when off: `tracing=True` and + `ROBOT_FRAMEWORK_BROWSER_TRACING` give Browser's trace folder, a video goes to + Browser's video folder, 1280×720 by default, and relative paths are resolved + as for `New Context`. Name the function's parameters like its keys and + `**options` passes them on. Give `tracing` to `adoptContext` and the others to + Playwright when you create the context. +- **`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. + ::doc-note --- kind: aside From 76d54dc9b9d6483152199f632c1ee472faf3aa06 Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sun, 11 Oct 2026 15:02:02 +0200 Subject: [PATCH 8/9] docs(extending): make the Python example a keyword and count seven names 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. --- content/docs/extending/javascript-extensions.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index ac49f47..87756c7 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -46,7 +46,7 @@ Three conventions carry the whole API: - **Exported names become keywords** — the *export key*, not the function's own name. `exports.getLinks = getLinks` gives `Get Links`; the `exports.myMouseWheel = mouseWheel` below gives `My Mouse Wheel`. -- **Six argument names are filled in for you**, by name — see below. +- **Seven argument names are filled in for you**, by name — see below. - **`fn.rfdoc` becomes the keyword documentation**, so your keyword shows up in Libdoc and in editor tooltips like any other. @@ -214,9 +214,11 @@ as for `New Context`: ```python from Browser import Browser +from robotlibcore import keyword class ElectronLibrary(Browser): + @keyword def launch_electron_application(self, executable_path, tracing=None, record_video=None, record_har=None): options = self.adopted_context_options(tracing=tracing, recordVideo=record_video, recordHar=record_har) adopted = self.call_js_keyword( @@ -240,6 +242,8 @@ async function launchElectronApplication(executablePath, tracing, recordVideo, r const adopted = await adoptContext(app.context(), { name: 'electron', tracing: tracing || undefined, contextOptions }); return { ...adopted, videoPath: recordVideo ? await window.video()?.path() : null }; } +exports.__esModule = true; +exports.launchElectronApplication = launchElectronApplication; ``` - **`adopted_context_options`** returns an `AdoptedContextOptions`, a typed From e353a711d4e466ef9b4da71efb7958c10e3ade7c Mon Sep 17 00:00:00 2001 From: Daniel Biehl Date: Sun, 11 Oct 2026 15:23:02 +0200 Subject: [PATCH 9/9] docs(extending): type the example keyword and note cleanup on a failed 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. --- .../docs/extending/javascript-extensions.md | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/content/docs/extending/javascript-extensions.md b/content/docs/extending/javascript-extensions.md index 87756c7..410ef48 100644 --- a/content/docs/extending/javascript-extensions.md +++ b/content/docs/extending/javascript-extensions.md @@ -170,6 +170,11 @@ Read The Title Of An Electron Application Close Browser ``` +The examples on this page are kept short. A real module closes the application +when anything after the launch fails, for example with `try`/`catch` around +`firstWindow` and `adoptContext`, because Browser cannot close a process it has +not taken over. + `adoptContext(context, options)` adds the context as a new browser and makes it the active one, the same way Browser keeps a persistent context: @@ -210,16 +215,27 @@ the active one, the same way Browser keeps a persistent context: A library built on Browser in Python calls the module with `call_js_keyword` and lets Browser decide where traces, videos and HAR files go, by the same rules -as for `New Context`: +as for `New Context`. Type the keyword's arguments with Browser's types, as the +example does, so that Robot Framework converts `False` or a dictionary as it does +for `New Context`: ```python +from pathlib import Path + from Browser import Browser +from Browser.utils import RecordHar, RecordVideo from robotlibcore import keyword class ElectronLibrary(Browser): @keyword - def launch_electron_application(self, executable_path, tracing=None, record_video=None, record_har=None): + def launch_electron_application( + self, + executable_path: str, + tracing: bool | Path | None = None, + record_video: RecordVideo | None = None, + record_har: RecordHar | None = None, + ): options = self.adopted_context_options(tracing=tracing, recordVideo=record_video, recordHar=record_har) adopted = self.call_js_keyword( "launchElectronApplication",