PRACTICAL GUIDE / Selenium BiDi console logs

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.

By The Testing AcademyUpdated July 11, 20268 min read
All field guides
In this guide11 sections
  1. Separate Console Policy from JavaScript Failure Policy
  2. Enable BiDi and Register Before Navigation
  3. Wait for Product Completion Before Judging Evidence
  4. Filter with an Explicit Product Policy
  5. Use Source and Stack Data for Diagnosis
  6. Handle Multiple Tabs and Frames Deliberately
  7. Control Volume and Memory
  8. Diagnose Missing or Duplicate Events
  9. Balance Assertion Value and Test Coupling
  10. Console Evidence Checklist
  11. Turn Browser Noise into a Defensible Signal

What you will learn

  • Separate Console Policy from JavaScript Failure Policy
  • Enable BiDi and Register Before Navigation
  • Wait for Product Completion Before Judging Evidence
  • Filter with an Explicit Product Policy

Browser console evidence is useful only when the test knows what it means. An uncaught JavaScript exception can explain a frozen checkout, while a deliberate console.error may be an application's handled diagnostic. Selenium BiDi exposes both as live events, but the test still needs scope, timing, classification, and a completion boundary.

The reliable pattern is simple: enable BiDi at session creation, register listeners before the event can occur, collect into thread-safe structures, wait for the product workflow to finish, then apply a documented policy. Do not read "whatever logs exist at teardown" and call every entry a product failure.

Separate Console Policy from JavaScript Failure Policy

Console entries carry level, text, source, timestamp, and console-specific fields. JavaScript exception entries represent script failures and may include stack information. A product can emit the same text through either path, but their semantics differ. Keep two collections and two decisions.

The Selenium BiDi logging documentation exposes console-message handlers and JavaScript-error handlers. Java's LogInspector offers typed ConsoleLogEntry and JavascriptLogEntry callbacks over the negotiated BiDi session.

Animated field map

BiDi Browser Error Evidence

Page scripts emit browser events; scoped BiDi listeners collect them, policy filters expected noise, and the test attaches only relevant assertion evidence.

  1. 01 / page script

    Page Script

    Application code logs a message or throws during the tested workflow.

  2. 02 / browser log event

    Browser Log Event

    The browser emits typed console or JavaScript exception data.

  3. 03 / bidi subscription

    BiDi Subscription

    A context-scoped LogInspector registers before navigation or interaction.

  4. 04 / evidence filter

    Severity and Context Filter

    Product policy classifies unexpected errors without hiding raw evidence.

  5. 05 / test assertion

    Test Evidence Assertion

    The completed workflow fails on relevant entries and attaches a concise report.

Enable BiDi and Register Before Navigation

Request webSocketUrl in the options before creating the driver. Capture the initial window handle, create a context-scoped inspector, and add both handlers before navigating to a page whose startup scripts may fail. CopyOnWriteArrayList is suitable for a modest diagnostic stream with many reads and few writes; a bounded queue is better for high-volume logging.

Java
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.log.ConsoleLogEntry;
import org.openqa.selenium.bidi.log.JavascriptLogEntry;
import org.openqa.selenium.bidi.module.LogInspector;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

ChromeOptions options = new ChromeOptions();
options.setCapability("webSocketUrl", true);
WebDriver driver = new ChromeDriver(options);

List<ConsoleLogEntry> consoleEntries = new CopyOnWriteArrayList<>();
List<JavascriptLogEntry> scriptErrors = new CopyOnWriteArrayList<>();

try (LogInspector logs = new LogInspector(driver.getWindowHandle(), driver)) {
  logs.onConsoleEntry(consoleEntries::add);
  logs.onJavaScriptException(scriptErrors::add);

  driver.get("https://app.example.test/checkout");
  runCheckoutScenario(driver);
  assertBrowserEvidence(consoleEntries, scriptErrors);
} finally {
  driver.quit();
}

Creating the inspector after get() would miss errors from parsing, module loading, and application bootstrap. Creating it globally for an entire shared browser would mix tests. The inspector's try-with-resources scope should match the workflow whose logs will be judged.

Wait for Product Completion Before Judging Evidence

Events arrive asynchronously, so an empty list immediately after click() proves little. Wait for a stable product boundary such as a submitted order state, rendered error result, or completed route transition. That boundary says the workflow had the opportunity to emit its events.

When the expected result is itself a specific console or exception event, wait on the thread-safe collection with WebDriverWait or poll the queue with a bound. Do not add a fixed sleep "for logs to settle." A sleep either delays every passing test or still races a slower event.

Java
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(12));
wait.until(ExpectedConditions.attributeToBe(
    By.cssSelector("[data-testid='checkout-state']"),
    "data-state",
    "complete"));

wait.until(ignored -> consoleEntries.stream()
    .anyMatch(entry -> entry.getText().contains("checkout-complete")));

The marker in this example is an application-owned diagnostic contract. Avoid asserting arbitrary third-party wording. If no log event is part of the expected behavior, wait only for product completion and then classify whatever evidence was captured.

Filter with an Explicit Product Policy

Start by separating JavaScript exceptions from console levels. Unexpected uncaught exceptions in the tested context are usually failures. Console ERROR entries may fail unless a named dependency and known scenario are allowed. WARNING, INFO, and DEBUG entries are generally diagnostic unless the test specifically validates them.

Java
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.stream.Collectors;
import org.openqa.selenium.bidi.log.LogLevel;

List<ConsoleLogEntry> unexpectedConsoleErrors = consoleEntries.stream()
    .filter(entry -> entry.getLevel() == LogLevel.ERROR)
    .filter(entry -> !knownConsolePolicy.allows(entry))
    .toList();

String consoleSummary = unexpectedConsoleErrors.stream()
    .map(entry -> entry.getLevel() + ": " + entry.getText())
    .collect(Collectors.joining(System.lineSeparator()));

assertTrue(scriptErrors.isEmpty(), () -> summarizeScriptErrors(scriptErrors));
assertTrue(unexpectedConsoleErrors.isEmpty(), () -> consoleSummary);

An allowlist needs an owner, reason, narrow fingerprint, and removal condition. Do not allow every message containing "failed" or every event from a broad hostname. Preserve the raw entry in restricted artifacts even when policy allows it, so dependency changes can be reviewed without weakening the test silently.

Use Source and Stack Data for Diagnosis

Text alone collapses unrelated failures. Include log level, entry type, source context, timestamp, and stack frames when available. Normalize volatile URL query values before grouping, but keep enough path and line information to locate the failing bundle. Attach the current route and test step separately rather than concatenating everything into one assertion message.

Console arguments can include structured remote values. Treat them as untrusted and potentially sensitive. Prefer the browser-provided text for routine reports, and serialize arguments only through a bounded redaction function. Never dump access tokens, personal data, or entire application state because an error callback made them convenient.

Handle Multiple Tabs and Frames Deliberately

A top-level browsing-context subscription keeps unrelated tabs out of the collection. When the workflow opens a popup, capture its handle and create a second inspector scoped to that handle before triggering popup-side behavior. Merge evidence only if the test's policy says both contexts belong to one scenario.

Frames have their own execution contexts even when they contribute to one top-level user flow. Record the source context carried by the event and avoid assuming every script error came from the main document. Third-party frame errors may be diagnostic, product failures, or excluded by contract; decide that in policy, not by deleting all frame-originated entries.

Control Volume and Memory

Applications can emit logs in loops. An unbounded list can consume memory and produce unusable artifacts. Set a maximum entry count or byte budget, retain the first relevant errors and a tail sample, and record how many events were omitted. Overflow should make the evidence status visible.

Keep the handler tiny. Redaction that needs parsing, source maps, or artifact upload should happen after collection. A slow callback delays processing of later events and can make the apparent order diverge from the browser's event stream.

Diagnose Missing or Duplicate Events

No events at all suggests that BiDi was not negotiated, the inspector was created too late, the browser or remote environment lacks the event support, or the wrong context was supplied. Events from a manual console call but not from the application point to trigger timing or execution context. Duplicate entries usually mean duplicate inspectors or a listener that survived a retry.

If the application visibly breaks but no JavaScript exception arrives, check whether the code caught the exception and logged it instead. Also inspect failed network requests and product error state. BiDi logging is one evidence channel, not a universal detector for every client-side defect.

Balance Assertion Value and Test Coupling

Failing every end-to-end test on every console message creates broad coupling to analytics, browser extensions, and third-party scripts. Limiting checks to a few critical workflows reduces noise but leaves coverage gaps. A practical design uses strict JavaScript-exception checks on product-owned contexts, targeted console policy on critical paths, and a separate observational sweep for ecosystem noise.

Keep functional assertions primary. A successful payment should be judged by product and backend state; a clean console is supporting evidence. Conversely, do not let a green UI assertion erase a captured uncaught exception that indicates hidden corruption after the visible step.

Console Evidence Checklist

  • Enable webSocketUrl before session creation.
  • Register console and JavaScript handlers before navigation or action.
  • Scope the inspector to the browsing context under test.
  • Store callbacks in thread-safe, bounded structures.
  • Wait for a product completion boundary before classification.
  • Keep exceptions and console messages as distinct evidence types.
  • Apply a narrow, owned allowlist rather than broad text suppression.
  • Include source and stack data without leaking sensitive values.
  • Close the inspector before reusing a browser or retrying a test.
  • Report missing, dropped, and allowed evidence explicitly.

Turn Browser Noise into a Defensible Signal

BiDi log capture is strongest when the test can explain every step: which context was observed, when handlers became active, what completed the workflow, and why each entry passed or failed policy. Capture console and exception streams separately, preserve their typed evidence, and keep the allowlist narrow. Then a browser error is not background text; it is a traceable product finding tied to the exact scenario that exposed it.

// 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.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 11, 2026 / Reviewed July 11, 2026

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.

  1. 01
    Selenium documentation

    Selenium Project

    Canonical WebDriver, Grid, waits, element, and browser automation guidance.

  2. 02
    WebDriver standard

    W3C

    The browser automation protocol specification behind WebDriver implementations.

FAQ / QUICK ANSWERS

Questions testers ask

Are console.error messages the same as uncaught JavaScript exceptions?

No. Application code can call console.error without throwing, while an uncaught exception is a JavaScript error event. Capture and classify both streams instead of treating them as interchangeable.

When should a Selenium test subscribe to BiDi log events?

Subscribe before navigation or before the interaction that may emit the event. BiDi does not replay earlier events to a handler registered after the fact.

Should every browser console warning fail the test?

Usually not. Define a product-owned policy by severity, source, context, and known fingerprint. Fail on unexpected high-risk entries while retaining lower-severity messages as diagnostics.

Why use a thread-safe collection for log entries?

BiDi handlers receive events independently of the test command thread. A concurrent collection or queue prevents races while the test waits for completion and evaluates captured evidence.

How do I avoid logs from an unrelated tab?

Construct the Selenium Java LogInspector with the intended browsing-context ID, register before the trigger, and create a separate scoped inspector for a popup or newly opened tab.