PRACTICAL GUIDE / Selenium BiDi script realms
Selenium BiDi Script Realms and Preload Scripts for Test Instrumentation
Use Selenium BiDi script realms and preload scripts for isolated instrumentation, test-side channels, typed evaluation, cleanup, and failure analysis.
In this guide11 sections
- Understand Contexts, Realms, and Preload State
- Register Before the Document Is Created
- Prove Which Realm Executed the Instrumentation
- Use Realm Events for Dynamic Documents
- Design Instrumentation as an Observer
- Distinguish Preload Removal from In-page Cleanup
- Bound and Correlate Channel Messages
- Handle Main-Realm and Sandbox Tradeoffs
- Diagnose Preload and Realm Failures
- Script Instrumentation Checklist
- Instrument Early Without Owning the Application
What you will learn
- Understand Contexts, Realms, and Preload State
- Register Before the Document Is Created
- Prove Which Realm Executed the Instrumentation
- Use Realm Events for Dynamic Documents
Preload scripts solve an instrumentation timing problem that executeScript cannot: code must exist before the application initializes. WebDriver BiDi can register a function for future documents, run it in a named sandbox realm, and deliver structured messages back to the test through a channel. That is enough to observe early events without rewriting the application bundle.
The mechanism is precise but stateful. Registration is session state, a realm belongs to a particular document or worker lifetime, and removing a preload script affects future execution only. Treat those lifecycles separately or instrumentation will vanish after navigation, duplicate after retries, or remain installed after the test that owns it.
Understand Contexts, Realms, and Preload State
A browsing context represents a tab or frame. A realm is a JavaScript execution environment associated with a window, worker, or named sandbox and has an opaque ID. Navigation replaces the active document and its realms. A preload script is a session registration that is applied when matching future documents are created.
Selenium's BiDi script documentation introduces the script module, while the WebDriver BiDi specification defines realms, sandbox targeting, channels, and script.addPreloadScript. Do not derive meaning from realm ID text or cache a realm ID across navigation; the identifier is intentionally opaque and lifetime-bound.
Animated field map
Preload Instrumentation Lifecycle
The test registers instrumentation before a document exists, the browser creates a sandbox realm, page events flow through a channel, and the test removes future execution.
01 / preload registration
Preload Registration
Register a function, context scope, channel argument, and sandbox before navigation.
02 / context creation
Browsing Context Creation
A navigation or new tab creates a document matching the registration.
03 / isolated realm
Isolated Realm
Run instrumentation in a named sandbox with its own global namespace.
04 / page event
Instrumented Page Event
Observe selected DOM or window activity and serialize bounded evidence.
05 / test callback
Test-side Callback
Receive script.message data, assert it, and remove the preload registration.
Register Before the Document Is Created
Enable BiDi in browser options, construct Selenium Java's Script module, add message handling, and register the preload function before navigating. The function declaration receives the supplied channel as its first argument. The example keeps instrumentation state in the qa-instrumentation sandbox and reports clicks on elements with a test ID.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Script;
import org.openqa.selenium.bidi.script.ChannelValue;
import org.openqa.selenium.bidi.script.Message;
BlockingQueue<Message> messages = new LinkedBlockingQueue<>();
try (Script script = new Script(driver.getWindowHandle(), driver)) {
script.onMessage(messages::offer);
String preloadId = script.addPreloadScript(
"(send) => {"
+ " globalThis.__qaInstallCount = (globalThis.__qaInstallCount || 0) + 1;"
+ " window.addEventListener('click', event => {"
+ " const node = event.target && event.target.closest"
+ " ? event.target.closest('[data-testid]') : null;"
+ " if (node) send({kind: 'click', testId: node.dataset.testid});"
+ " }, true);"
+ "}",
List.of(new ChannelValue("qa-events")),
"qa-instrumentation");
try {
driver.get("https://app.example.test/cart");
driver.findElement(By.cssSelector("[data-testid='checkout']")).click();
Message message = messages.poll(
Duration.ofSeconds(5).toMillis(), TimeUnit.MILLISECONDS);
assertNotNull(message, "Expected script.message from preload channel");
assertEquals("qa-events", message.getChannel());
} finally {
script.removePreloadScript(preloadId);
}
}The channel callback serializes values according to BiDi's remote-value model. Keep messages small and schema-versioned. Do not send DOM subtrees, application stores, tokens, or user records merely because they are reachable from script.
Prove Which Realm Executed the Instrumentation
Using a sandbox name creates a separate JavaScript global for that context. Evaluate a probe in the same named sandbox rather than reading the page's main window. Selenium returns an EvaluateResult; check that it is a success before reading the RemoteValue.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import java.util.Optional;
import org.openqa.selenium.bidi.script.EvaluateResult;
import org.openqa.selenium.bidi.script.EvaluateResultSuccess;
EvaluateResult result = script.evaluateFunctionInBrowsingContext(
driver.getWindowHandle(),
"qa-instrumentation",
"globalThis.__qaInstallCount",
false,
Optional.empty());
EvaluateResultSuccess success = assertInstanceOf(EvaluateResultSuccess.class, result);
assertEquals("number", success.getResult().getType());
Number count = (Number) success.getResult().getValue().orElseThrow();
assertEquals(1, count.intValue());The sandbox keeps __qaInstallCount out of the application's main global namespace. It can still interact with platform objects through the sandbox model, which is why DOM observation works. Isolation reduces naming collisions; it is not a security boundary for running untrusted test code.
Use Realm Events for Dynamic Documents
Single-page navigation can preserve a window realm, while full navigation replaces it. Frames and workers add more realms. Script.onRealmCreated and onRealmDestroyed let infrastructure record these changes, and getRealmsInBrowsingContext can inspect current realm metadata when a test needs to target one explicitly.
Store realm IDs only for the lifetime in which they were observed. If an evaluation returns a missing-realm style error after navigation, reacquire realm information instead of retrying the stale ID. Scope realm listeners with the same care as message listeners; a shared session can otherwise accumulate callbacks and duplicate records.
Design Instrumentation as an Observer
A preload script should observe a narrow contract: selected user interactions, an early error hook, a fetch marker, or timing around a product-owned event. Avoid monkey-patching broad browser APIs unless the test specifically validates that patch. Instrumentation that changes scheduling, return values, event propagation, or object identity can make the test pass in a browser state users never experience.
The click listener uses capture mode so it can observe before later bubbling handlers, but it does not prevent default or stop propagation. That is an intentional tradeoff. If the application removes the node during its own handler, the captured test ID remains evidence of the original target without modifying the event.
Distinguish Preload Removal from In-page Cleanup
removePreloadScript(preloadId) prevents the registration from running in future matching documents. It does not remove the listener or global already created in the current document. If the browser session will be reused, either navigate to a clean document after removal or design the preload function to expose a sandbox cleanup function and call it before reuse.
Make installation idempotent as a second defense. Store a sandbox-local installed flag and skip duplicate listener registration. Idempotency protects retries, but it does not excuse leaked registrations; still remove the exact preload ID owned by the test.
Bound and Correlate Channel Messages
Script.onMessage receives asynchronous events. Put them into a thread-safe bounded queue, include a small event type and correlation ID, and perform assertions on the test thread. A callback should not call WebDriver or wait for another message. If the queue overflows, mark evidence incomplete instead of blocking the WebSocket processing path.
Channel names are routing labels, not secrets. Use a unique name per instrumentation component or test attempt when streams share a module. Validate message schema and ignore unknown fields while rejecting an unknown event type; this supports additive evolution without accepting arbitrary payloads.
Handle Main-Realm and Sandbox Tradeoffs
Main-realm instrumentation can see application globals directly but risks collisions and can be detected or overwritten by application code. A named sandbox separates test globals and makes evaluation targeting explicit, but access to application-specific JavaScript objects is not the same as running in the main realm. Choose based on the observation required.
Prefer DOM, accessibility, network, and product APIs before reaching into framework internals. A test coupled to a private Redux store or module loader can fail on harmless implementation changes. Realm access is most valuable for evidence that the public UI cannot expose early enough, not as a replacement for user-facing assertions.
Diagnose Preload and Realm Failures
No message after navigation can mean the script was registered too late, the wrong context was scoped, the event never occurred, the channel was not passed, or the script threw during installation. Probe the sandbox install counter and capture JavaScript errors through BiDi logging. A count greater than one points to duplicate registration or non-idempotent installation.
An evaluation exception should be inspected as an EvaluateResultExceptionValue, not cast to success. A missing realm after navigation means the target lifetime ended. Messages from an unrelated tab indicate over-broad context scope. Messages continuing after removal are expected from the already instrumented document unless in-page cleanup ran; they do not prove removal failed.
Script Instrumentation Checklist
- Request and verify BiDi before creating the
Scriptmodule. - Register message listeners and preload functions before navigation.
- Scope registrations to the browsing contexts that own the scenario.
- Use a named sandbox to isolate instrumentation globals when appropriate.
- Keep the preload function observational, idempotent, and small.
- Pass bounded, schema-versioned evidence through a
ChannelValue. - Evaluate the named sandbox to prove installation and result type.
- Reacquire realm IDs after full navigation or context destruction.
- Remove the preload ID and separately clean the current document if reused.
- Close message and realm listeners before the next test owns the session.
Instrument Early Without Owning the Application
BiDi preload scripts are effective when they arrive before application code but remain modest in what they change. Register once, run in a named sandbox, send narrow evidence through a channel, target only live realms, and clean both future registration and current-document effects. That gives the test visibility into early browser behavior without turning instrumentation into a hidden second application runtime.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Selenium documentation
Selenium Project
Canonical WebDriver, Grid, waits, element, and browser automation guidance.
- 02WebDriver standard
W3C
The browser automation protocol specification behind WebDriver implementations.
FAQ / QUICK ANSWERS
Questions testers ask
What is a script realm in WebDriver BiDi?
A realm is a JavaScript execution environment with its own global object and opaque identifier. Window documents, workers, and named sandbox realms can each create distinct realms.
When does a BiDi preload script run?
It runs for new matching documents after registration. Adding it does not retroactively execute it in the document that is already loaded, so register before navigation or create a new document afterward.
Does removing a preload script undo code already installed in a page?
No. Removal prevents execution in future matching documents. Event listeners, globals, or DOM changes already created in the current document remain until code removes them or that document is discarded.
Why put instrumentation in a named sandbox?
A sandbox provides a separate realm and global namespace while retaining mediated access to platform objects. It reduces collisions with application globals and makes test evaluation target explicit.
How can a preload script send data back to a Selenium Java test?
Pass a BiDi ChannelValue argument to the preload function, call that channel from the script with serializable data, and collect script.message events through Script.onMessage in a thread-safe queue.
RELATED GUIDES
Continue the learning route
GUIDE 01
Enable WebDriver BiDi and Manage Event Subscriptions in Selenium
Enable Selenium WebDriver BiDi, verify WebSocket negotiation, scope event subscriptions, prevent races, and clean handlers up deterministically.
GUIDE 02
Capture Console Messages and JavaScript Errors with Selenium BiDi
Capture Selenium BiDi console messages and JavaScript exceptions with scoped listeners, thread-safe evidence, filtering policy, and failure diagnostics.
GUIDE 03
Intercept Requests and Supply Authentication with Selenium BiDi Network
Use Selenium BiDi network intercepts for scoped Basic or Digest authentication, request control, response evidence, cleanup, and safe diagnostics.
GUIDE 04
Instrument Selenium Commands with WebDriver Listeners
Instrument Selenium commands with WebDriverListener, structured timing events, failure screenshots, safe redaction, and useful execution timelines.