Resource library

QA How-To

How to Use Selenium browser console logs in Java (2026)

Capture selenium browser console logs java tests produce with Chromium logging or WebDriver BiDi, then filter, assert, and save useful CI evidence in 2026.

23 min read | 2,758 words

TL;DR

For Chromium, enable LogType.BROWSER with LoggingPreferences and read entries through driver.manage().logs().get(LogType.BROWSER). For live, standards-oriented events, enable BiDi and register driver.script().addConsoleMessageHandler before the tested action.

Key Takeaways

  • Use LoggingPreferences and LogType.BROWSER for the established Chromium pull-based log workflow.
  • Treat driver.manage().logs().get() as a drain because returned entries are removed from the buffer.
  • Use ChromeOptions.enableBiDi() and driver.script() for live high-level console and JavaScript error handlers.
  • Register handlers before the action that can emit a message and remove them in teardown.
  • Filter by level, source, and an application-owned signature instead of failing on every browser message.
  • Attach sanitized console evidence to the failing test, not to an undifferentiated suite-wide file.

A dependable selenium browser console logs Java workflow can expose JavaScript exceptions, failed resource messages, deprecation warnings, and application diagnostics that a DOM assertion cannot see. In 2026, Selenium Java offers two practical paths: the established Chromium browser-log buffer and the high-level WebDriver BiDi script handlers.

Choose the path deliberately. Buffered browser logs are simple and widely used in Chrome and Edge suites, but they are driver-specific and each read drains the available entries. BiDi handlers deliver live typed events and align with Selenium's cross-browser direction, but the exact browser support still belongs in your test matrix. This guide implements both and shows how to turn raw output into useful QA evidence without creating noisy false failures.

TL;DR

Need Recommended Java approach Important behavior
Read Chrome or Edge console output after an action LoggingPreferences plus LogType.BROWSER get() drains entries already returned
Receive console messages as they happen driver.script().addConsoleMessageHandler(...) Enable BiDi before driver creation
Receive uncaught JavaScript errors addJavaScriptErrorHandler(...) This is distinct from console.error
Fail a test on relevant errors Filter entries against an allowlist and app scope Do not fail on every third-party warning
Preserve failure evidence Format and attach per test Redact tokens, URLs, and personal data

1. Selenium browser console logs Java fundamentals

A browser console is a stream of messages produced by page scripts, the browser, extensions, and sometimes injected tooling. Common methods include console.log, console.info, console.warn, and console.error. Uncaught JavaScript exceptions may also appear in browser logging, but a typed BiDi JavaScript error event is not identical to a call to console.error. One is an exception raised by script execution, while the other is an application-selected console message.

Keep browser logs separate from Selenium client logs and driver service logs. Selenium client logs describe commands sent by your Java code. ChromeDriver service logs describe the driver process. Browser console logs describe the page and browser context. Enabling one does not automatically enable the others, and they answer different debugging questions.

A console assertion is usually secondary evidence. The primary assertion should still describe customer behavior: the order completed, the validation message appeared, or the report loaded. Console evidence can then explain a hidden front-end defect or enforce a targeted engineering rule, such as no uncaught exception from an application-owned bundle during checkout. If your suite often fails while locating the visible outcome, review Selenium NoSuchElementException causes and fixes before adding more logging.

2. Add a current Selenium Java dependency

A minimal Maven project needs Selenium Java. JUnit is optional for the first standalone examples, although most production suites will place the same setup in a test fixture or extension. Selenium Manager can resolve an appropriate local driver when the browser is installed, so hardcoded executable paths are normally unnecessary.

<dependencies>
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.46.0</version>
  </dependency>
</dependencies>

Pin the version through your normal dependency-management mechanism. A runnable example should be tested with the exact Selenium, browser, and driver versions used in CI. Selenium's public API is stable across many 4.x updates, but the browser's available log types and BiDi implementation can still vary.

Before writing an assertion, ask the driver which pull-based log types it exposes.

Set<String> available = driver.manage().logs().getAvailableLogTypes();
System.out.println("Available log types: " + available);

If browser is absent, calling for it will not make the remote end implement it. That capability check is valuable on Grid because a local Chrome session and a remote browser image may expose different logging behavior. Fail a dedicated environment preflight with a clear message rather than letting dozens of tests produce confusing empty collections.

3. Capture Chromium console output with LoggingPreferences

The established pull-based approach configures browser logging at session creation, runs browser actions, then fetches the available entries. This complete program emits an error from the page and verifies that Selenium captured it.

import java.util.List;
import java.util.logging.Level;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;

public class BrowserLogExample {
  public static void main(String[] args) {
    LoggingPreferences preferences = new LoggingPreferences();
    preferences.enable(LogType.BROWSER, Level.ALL);

    ChromeOptions options = new ChromeOptions();
    options.setCapability(ChromeOptions.LOGGING_PREFS, preferences);

    ChromeDriver driver = new ChromeDriver(options);
    try {
      driver.get("data:text/html,<title>Console test</title>");
      ((JavascriptExecutor) driver)
          .executeScript("console.error('checkout failed');");

      List<LogEntry> entries = driver.manage()
          .logs()
          .get(LogType.BROWSER)
          .getAll();

      entries.forEach(entry -> System.out.printf(
          "%s %s %s%n",
          entry.getLevel(),
          entry.getTimestamp(),
          entry.getMessage()));

      boolean found = entries.stream()
          .anyMatch(entry -> entry.getMessage().contains("checkout failed"));
      if (!found) {
        throw new AssertionError("Expected console error was not captured");
      }
    } finally {
      driver.quit();
    }
  }
}

Level.ALL requests all severities for the browser log type. You can request a stricter threshold, but collecting broadly and filtering against a test policy often gives better diagnostics. This route is most dependable for Chromium drivers. Do not describe it as universally cross-browser merely because WebDriver.Options.logs() exists in the Java API.

4. Understand buffer drain semantics and capture windows

The Java Logs.get(logType) contract resets the buffer for entries it returns. The next call yields messages generated after the previous read, not the full session history. This matters when a helper reads logs for debugging and a later assertion expects the same entries. The first helper has already consumed them.

Create one owner for each capture window. A useful pattern is to drain stale startup messages immediately before the action, perform the action, wait for the expected UI state, and then read once for assertion and attachment.

private static List<LogEntry> captureAfter(
    ChromeDriver driver, Runnable action) {
  driver.manage().logs().get(LogType.BROWSER);
  action.run();
  return driver.manage().logs().get(LogType.BROWSER).getAll();
}

This helper is intentionally small, but real asynchronous applications need a completion signal. Reading immediately after a click can race with a promise that logs later. Wait for a business-visible state, a specific element, or a bounded condition before draining the buffer. A fixed sleep makes the test slower and still does not prove completion.

Also decide what navigation boundaries mean. A single-page application can emit messages throughout one document lifetime. A full navigation can add browser and resource messages unrelated to the action under test. Label evidence with the current test phase, URL, and timestamp so a reviewer can tell whether an error occurred during login, setup, or the actual scenario.

5. Selenium browser console logs Java with WebDriver BiDi

Selenium's high-level Java BiDi path uses the script() namespace because console and JavaScript errors are script-related events. Enable BiDi on the options before creating the session, add a handler, run the action, wait for the event, and remove the handler by ID.

import java.time.Duration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.bidi.log.ConsoleLogEntry;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class BidiConsoleExample {
  public static void main(String[] args) {
    ChromeOptions options = new ChromeOptions();
    options.enableBiDi();

    ChromeDriver driver = new ChromeDriver(options);
    List<ConsoleLogEntry> entries = new CopyOnWriteArrayList<>();
    long handlerId = driver.script().addConsoleMessageHandler(entries::add);

    try {
      driver.get("data:text/html,<title>BiDi console test</title>");
      ((JavascriptExecutor) driver)
          .executeScript("console.error('payment widget failed');");

      new WebDriverWait(driver, Duration.ofSeconds(5))
          .until(_driver -> entries.stream()
              .anyMatch(entry -> entry.getText().contains("payment widget failed")));

      entries.forEach(entry -> System.out.printf(
          "%s %s %s%n",
          entry.getLevel(),
          entry.getMethod(),
          entry.getText()));
    } finally {
      driver.script().removeConsoleMessageHandler(handlerId);
      driver.quit();
    }
  }
}

A CopyOnWriteArrayList is appropriate for this small example because events can arrive on Selenium's listener path while the test thread reads the collection. For very high message volume, use a bounded concurrent structure and avoid expensive callback work. The handler should collect or enqueue, not perform screenshots, remote calls, or complex assertions.

6. Build a severity and ownership policy

Failing on every SEVERE or BiDi error entry sounds strict, but real pages often include third-party widgets, browser warnings, extension noise, intentional negative-test errors, and blocked tracking calls. A good policy identifies application ownership and known exceptions. It also makes allowlist entries specific and temporary.

For buffered LogEntry values, normalize the message into a small record with level, timestamp, source URL when present, and text. ChromeDriver often embeds source information in the message string, so parsing should be defensive. For typed BiDi entries, prefer getLevel(), getText(), getMethod(), getSource(), and getStackTrace() rather than flattening everything immediately.

A useful decision order is:

  1. Ignore levels below the test's policy threshold.
  2. Ignore messages provably owned by an approved third party.
  3. Ignore an exact, reviewed signature with an expiry or issue link.
  4. Fail on an application-owned uncaught error or prohibited console call.
  5. Attach the complete sanitized capture for diagnosis.

Never allowlist a generic phrase such as Failed to load resource. That can hide the exact regression the gate should catch. Match the source host, stable message fragment, and test phase. Review the list as code, and delete entries when the underlying dependency or defect is removed.

7. Distinguish console.error from JavaScript exceptions

console.error('bad state') is an explicit logging call. throw new Error('bad state') creates a JavaScript exception. An application can call console.error without throwing, and it can catch an exception before it becomes uncaught. Treating both as one category loses useful semantics.

BiDi provides a dedicated high-level handler for JavaScript errors.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openqa.selenium.bidi.log.JavascriptLogEntry;

List<JavascriptLogEntry> errors = new CopyOnWriteArrayList<>();
long errorHandler = driver.script().addJavaScriptErrorHandler(errors::add);
try {
  ((JavascriptExecutor) driver)
      .executeScript("setTimeout(() => { throw new Error('render failed'); }, 0);");
  new WebDriverWait(driver, Duration.ofSeconds(5))
      .until(_driver -> !errors.isEmpty());
  assert errors.get(0).getText().contains("render failed");
} finally {
  driver.script().removeJavaScriptErrorHandler(errorHandler);
}

Use console handlers to enforce logging policies or diagnose page behavior. Use the JavaScript error handler when the contract is specifically no uncaught exceptions during a feature flow. If both are useful, keep separate collections so the failure explains which contract was violated. This separation also prevents a deliberate console.error in a negative test from being reported as a runtime crash.

8. Integrate capture into JUnit without masking the real failure

A JUnit integration should attach console evidence when a test fails and optionally enforce a targeted policy after the test. Teardown must preserve the original exception. If cleanup throws first, the report can blame log retrieval instead of the failed checkout assertion. Catch capture errors, add them as suppressed exceptions or diagnostic text, then always quit the driver.

A practical architecture has three parts: a per-test collector, a policy evaluator, and an artifact writer. The collector owns handler IDs or buffer reads. The evaluator returns violations as data. The writer sanitizes and stores JSON or text under a unique test ID. Keeping these responsibilities separate makes policy changes testable without launching a browser.

For BiDi, register listeners in @BeforeEach after driver creation and remove them in @AfterEach. For pull logs, clear startup entries in @BeforeEach, then read once during teardown. If a test intentionally verifies console output, let that test own the capture window so the generic extension does not drain it.

Do not automatically fail every existing test the day a console gate is introduced. First run in observation mode, classify recurring noise, fix application-owned errors, and define a narrow baseline. Then enable enforcement for high-value flows. This staged adoption is engineering discipline, not reduced quality, because it prevents a noisy gate from training the team to ignore failures.

9. Preserve useful evidence on Grid and in parallel runs

Parallel suites magnify logging mistakes. A static list shared across tests mixes messages from different sessions. A suite-wide browser.log lets workers overwrite each other. Use instance state tied to one driver and create artifact paths from a unique test execution ID. Include the browser name and version because console behavior can differ across browser releases.

On Grid, validate available log types for the pull route and the BiDi WebSocket capability for the event route. A remote ChromeOptions object still requests logging or BiDi at session creation, but the Grid node and browser must implement the request. Environment checks belong in a small launch test rather than hidden inside every page object.

Redact aggressively. Console messages can include signed URLs, access tokens, email addresses, query parameters, and serialized application state. Keep a reusable sanitizer that handles known token patterns and URL query strings. Store full artifacts only according to the organization's retention and access policy.

When console capture is part of CI release evidence, connect it to the same test result and screenshot. The Jenkins pipeline for Selenium guide covers the surrounding execution pattern. The result should answer three questions quickly: which user flow failed, which page state was visible, and which relevant browser message occurred in that phase.

10. Choose between pull logs and BiDi handlers

Both APIs are legitimate, but they have different operating models.

Criterion Chromium browser log buffer WebDriver BiDi script handler
Setup LoggingPreferences capability enableBiDi() capability
Consumption Pull with manage().logs().get() Push into a callback
Timing Read available entries after an action Receive events while subscribed
Data model Generic LogEntry Typed ConsoleLogEntry or JavascriptLogEntry
Browser scope Most practical on Chrome and Edge Designed for cross-browser standardization
Cleanup Reads drain returned entries Remove handler by ID
Best fit Existing Chromium suites and simple diagnostics New event-driven code and typed error policies

If an established Chromium framework already has stable pull-log capture, there is no need for a rushed rewrite. Add tests around its drain behavior and sanitation. For new portable infrastructure, evaluate the BiDi high-level API first and run it across the supported browser matrix.

Avoid older CDP console-event examples as the default design for new Java code. Selenium still has CDP integrations for Chromium-specific needs, but WebDriver BiDi is the forward-looking standard. The rule is simple: use a public high-level Selenium feature when it meets the requirement, and isolate browser-specific fallback code behind a small adapter when it does not.

Interview Questions and Answers

Q: How do you get browser console logs from Selenium in Java?

For Chromium pull logs, enable LogType.BROWSER through LoggingPreferences on ChromeOptions, then call driver.manage().logs().get(LogType.BROWSER). For live BiDi events, enable BiDi and use driver.script().addConsoleMessageHandler. The right answer should mention browser support and lifecycle, not only one method call. I would also say who owns the capture window, because a pull-buffer reader can consume evidence and a live handler can leak into later tests. A complete implementation includes capability setup, collection timing, sanitization, and cleanup.

Q: Why can a second call to manage().logs().get() return no entries?

The Logs.get contract resets the buffer for entries it returns. A helper or teardown hook may have consumed the evidence before the assertion read it. Give one component ownership of the capture window and pass the collected entries to other consumers.

Q: What is the difference between a console error and a JavaScript exception?

A console error is an explicit call such as console.error. A JavaScript exception is a thrown runtime error, which may be caught or remain uncaught. BiDi exposes separate handlers, so a framework can enforce different policies for diagnostics and actual script failures.

Q: Would you fail every Selenium test on every console error?

No. I would define a policy based on severity, application ownership, scenario intent, and a reviewed allowlist. Third-party noise and intentional negative-test messages should not hide application-owned uncaught errors. The primary feature assertion remains essential. I would introduce the rule in observation mode, classify existing messages, and unit test the policy against representative entries before making it a release gate. That produces a trusted signal and a reviewed exception process instead of a broad severity check that teams learn to ignore.

Q: How do you make browser-log collection thread safe?

Keep each collector scoped to one driver and test. For callbacks, use a thread-safe collection such as CopyOnWriteArrayList for small volumes or a bounded concurrent queue for higher volumes. Never share a static collector across parallel sessions.

Q: What information belongs in a console failure artifact?

Include the test ID, browser version, current page or sanitized origin, phase, timestamp, level, message, and relevant stack details. Redact secrets and personal data. Attach it to the failing test along with the primary assertion evidence.

Q: Why is BiDi preferable to CDP for new portable logging code?

WebDriver BiDi is intended as a cross-browser automation standard, while CDP belongs to Chromium and changes with Chromium versions. Selenium's high-level BiDi handlers reduce direct protocol coupling. Actual support must still be proven in the browsers the team runs.

Common Mistakes

  • Enabling LoggingPreferences after the driver is created. Logging capabilities are session-creation inputs.
  • Assuming LogType.BROWSER behaves identically in every browser and remote environment. Check available log types.
  • Reading the buffer in a debug helper and then reading it again for an assertion. The first read drains those entries.
  • Registering a BiDi handler after the action that emitted the message. Event listeners cannot recover past events.
  • Leaving handler IDs active across tests. Remove console and JavaScript error handlers in teardown.
  • Using a normal ArrayList while a listener thread writes and the test thread iterates. Use an appropriate concurrent structure.
  • Failing on all messages that contain error. Filter structured level and ownership, and keep allowlists precise.
  • Dumping console output that contains tokens or user data into public CI artifacts. Sanitize first.
  • Replacing a business assertion with a no-console-errors assertion. A quiet console does not prove the feature worked.
  • Confusing Selenium command logging, driver service logging, performance logging, and page console logging. Name each source clearly.

If you are preparing to explain the design in a senior interview, use Selenium interview questions for three years of experience and compare the Java workflow with Selenium browser console logs in Python.

Conclusion

A production-quality selenium browser console logs Java solution is more than calling a log method. Configure the capability during session creation, understand whether evidence is pulled or pushed, scope collection to one test, and apply a policy that separates application defects from background noise. Unit test the policy and sanitizer with representative entries so a browser upgrade or message-format change produces a focused framework failure instead of silently weakening the gate.

Use the Chromium log buffer for a straightforward existing Chrome or Edge suite, and use high-level WebDriver BiDi handlers for live typed events and new portable designs. Whichever route you choose, preserve the original test failure, sanitize the output, and attach concise evidence that helps an engineer reproduce the customer-visible problem.

Interview Questions and Answers

Explain two ways to capture browser console logs in Selenium Java.

The pull-based Chromium route enables LogType.BROWSER with LoggingPreferences and reads LogEntry values from driver.manage().logs(). The event-driven route enables WebDriver BiDi and registers a console handler through driver.script(). The first is simple for Chromium suites, while the second provides typed live events and a standards-oriented design.

What does it mean that Selenium log reads drain the buffer?

Entries returned by Logs.get are removed from the set available to the next call. If two framework components read independently, the second can miss evidence. I assign ownership of the read and share the collected result with assertions and reporters.

How would you prevent console-log assertions from becoming noisy?

I filter by structured severity, application origin, scenario phase, and precise known signatures. Allowlist entries have an issue reference and are reviewed for removal. The policy reports relevant errors while retaining sanitized context for diagnosis.

How do you capture console logs safely in parallel Java tests?

Each driver owns a separate collector and artifact path, with no static shared list. BiDi callbacks write to an appropriate concurrent collection. Handler IDs and buffer reads are cleaned up within the same test lifecycle.

Why separate console errors from JavaScript exceptions?

A page can deliberately call console.error without throwing, and code can throw an exception that follows a different error path. BiDi supplies distinct typed handlers for these signals. Separate policies produce more accurate failures and clearer diagnostics.

What should happen if console capture fails during teardown?

The framework should preserve the original test failure, record the capture problem as secondary evidence, and still quit the driver. A teardown exception should not replace the business assertion that first failed. Cleanup errors can be attached or suppressed for investigation.

When would you select WebDriver BiDi over LoggingPreferences?

I would prefer BiDi for new event-driven code, typed console and JavaScript error events, and a design intended to span supported browsers. I would keep LoggingPreferences for a stable Chromium suite when buffered diagnostics meet the requirement. In both cases I would verify behavior on the actual CI matrix.

Frequently Asked Questions

How do I enable browser console logs in Selenium Java?

For Chromium, create LoggingPreferences, enable LogType.BROWSER at the desired java.util.logging level, and place the preferences on ChromeOptions before creating ChromeDriver. Then read entries through driver.manage().logs().get(LogType.BROWSER).

Why is Selenium returning an empty browser log in Java?

The driver may not expose the browser log type, logging may not have been enabled at session creation, or another component may already have drained the buffer. Check getAvailableLogTypes and make one component own each read window.

Does reading Selenium browser logs clear them?

Yes. The Java Logs.get contract states that available entries returned by a read are reset from the buffer. Later reads contain only entries generated since the previous read.

Can Selenium Java listen to console logs in real time?

Yes. Enable WebDriver BiDi with the browser options and register driver.script().addConsoleMessageHandler. Save the returned ID and remove the handler when the capture window ends.

How can Selenium Java capture uncaught JavaScript errors?

With BiDi enabled, use driver.script().addJavaScriptErrorHandler and collect JavascriptLogEntry values. Keep these separate from explicit console.error messages because they represent a different failure category.

Should a Selenium test fail on every SEVERE console entry?

Usually not. Filter by application ownership, test phase, severity, and precise reviewed exceptions. Continue to assert the customer-visible outcome because console cleanliness alone does not prove feature success.

Do Selenium browser console logs work on Grid?

They can, but the Grid node, browser, and driver must expose the requested feature. Validate available log types or the BiDi WebSocket capability in a small environment preflight and keep versions compatible.

Related Guides