skip to content

In Selenium's Java client, how do you read a page's browser console output, and what comes back?

level: juniorimportance: should knowfreq 58%

answer

  1. It hangs off driver.manage()
  2. Two methods only on that interface
  3. A collection, not a String
  4. Level, timestamp, message per entry
  5. Reading it empties it

basics

~10 s

Call driver.manage().logs().get(LogType.BROWSER). It returns a LogEntries collection of LogEntry objects, each carrying a level, a timestamp in epoch milliseconds, and the message text. Each call drains the buffer it read.

solid answer

~40 s

`driver.manage()` returns a `WebDriver.Options`, and its `logs()` method gives you a `Logs` object with just two methods: `get(String logType)` and `getAvailableLogTypes()`. Calling `logs().get(LogType.BROWSER)` returns a `LogEntries`, which is iterable and also offers `getAll()` for a `List<LogEntry>`. Every `LogEntry` exposes `getLevel()` (a `java.util.logging.Level`), `getTimestamp()` (milliseconds since the UNIX epoch) and `getMessage()`. The important catch is that the remote end resets the buffer on each read, so a second consecutive call returns nothing new — read once, in the code path that handles the failure. On a bicycle-share station map you would use it to pull back the uncaught `TypeError` that `station-map.js` threw when a station arrived with no dock count.

code

java · 21 lines
java
import java.util.logging.Level;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;

public class StationMapConsole {
  public static void main(String[] args) {
    WebDriver driver = new ChromeDriver();
    try {
      driver.get("https://example.com/bike-share/stations");
      for (LogEntry entry : driver.manage().logs().get(LogType.BROWSER)) {
        if (entry.getLevel().intValue() >= Level.SEVERE.intValue()) {
          System.out.printf("%d %s %s%n", entry.getTimestamp(), entry.getLevel(), entry.getMessage());
        }
      }
    } finally {
      driver.quit();
    }
  }
}

go deeper

for a junior

Be ready to write the call from memory: driver.manage().logs().get(LogType.BROWSER), then iterate the LogEntries and print each entry's level, timestamp and message.

for a middle

Explain that the remote end resets the buffer on every get call, that the timestamp is epoch milliseconds, and that the level is a java.util.logging.Level rather than a browser-specific string.

for a senior

Show where the read belongs in a real suite: once, in the failure path, so no earlier peek has drained it, and with a level filter so routine console chatter never reaches the failure record.

for a principal

Own the question of how much the team should invest in a retrieval API that only some browsers implement, and what the harness does on the browsers where it returns nothing.

## What `driver.manage().logs()` actually is In Selenium 4's Java client, `driver.manage()` returns a `WebDriver.Options` object, and that object exposes a `logs()` method returning a `Logs` instance. **`Logs` is a two-method interface**: `get(String logType)` and `getAvailableLogTypes()`. Both it and `LogEntries` are annotated `@Beta` in Selenium 4, which is a fair signal of how unsettled this corner of the API is. `logs().get(LogType.BROWSER)` asks the **remote end** — the driver executable sitting between your test and the browser — for whatever it has recorded from the page's own console: `console.log`, `console.warn` and `console.error` calls, uncaught exceptions, and the browser's own complaints about blocked requests or malformed CSS. On a bicycle-share station map, that is exactly where `Uncaught TypeError: Cannot read properties of null (reading 'docksAvailable')` from `station-map.js` surfaces when the station feed returns a dock with no count and the marker renderer trips over it. ## The shape of what comes back `get()` returns a **`LogEntries`**, which implements `Iterable<LogEntry>` and adds `getAll()`, returning an unmodifiable `List<LogEntry>`. Each `LogEntry` carries three values and nothing else: | Accessor | Type | What it holds | |---|---|---| | `getLevel()` | `java.util.logging.Level` | severity, normalised by Selenium to one of `ALL`, `FINE`, `INFO`, `WARNING`, `SEVERE`, `OFF` | | `getTimestamp()` | `long` | milliseconds since the UNIX epoch — wall-clock time, not an offset from session start | | `getMessage()` | `String` | the recorded text, usually carrying the source URL and line number inline | `LogEntry.toString()` formats those three as `[<ISO instant>] [<level>] <message>`, which is why dumping entries straight into a report reads sensibly without extra formatting. Note the level type: it is the JDK's `java.util.logging.Level`, so the browser's own `warning` and `severe` strings are mapped onto JDK levels by Selenium's `LogLevelMapping`, and the wire name `DEBUG` becomes `Level.FINE`. ## The buffer drains on every read This is the single behaviour that bites people. The `Logs.get()` contract states that **log buffers are reset after each call**: what you get back is the entries not yet returned for that log type — everything since your last `get()`, or since the session started if you have not called it before. Selenium's own `GetLogsTest` asserts precisely this, checking that a second consecutive call comes back empty and that consecutive calls never overlap. Practical consequences: 1. **Read once per failure, not repeatedly.** A helper that peeks at the console mid-test silently steals the entries your teardown was going to attach. 2. **A "why is my log empty?" report is usually a double read**, not a browser that logged nothing. 3. **Different log types hold different entries.** Draining `browser` does not drain `driver`; the two never contain the same records. 4. **Buffering is bounded by the remote end**, so a page that logs continuously can push earlier entries out before you ever read them. ## The log types Selenium 4 declares Selenium 4's `LogType` class declares exactly three string constants: - `LogType.BROWSER` — the string `"browser"`, the page's console. - `LogType.DRIVER` — `"driver"`, records the remote end itself surfaces through the same endpoint. - `LogType.PERFORMANCE` — `"performance"`, off unless you ask for it. Rather than hard-coding one of those, `getAvailableLogTypes()` returns the `Set<String>` the remote end says it will actually serve. Against ChromeDriver, `browser` and `driver` are present without any configuration, and `performance` is absent until logging preferences turn it on — Selenium's `AvailableLogsTest` and `PerformanceLogTypeTest` assert both facts. ## What the browser log does and does not hold The name invites over-reading. `LogType.BROWSER` is the **page's console stream and nothing more**, so it is worth being explicit about the edges: - **In scope:** `console.log`, `console.info`, `console.warn` and `console.error` calls made by the page's own scripts. - **In scope:** uncaught exceptions and unhandled promise rejections, which the browser writes to the console itself. - **In scope:** the browser's own console complaints — a blocked mixed-content request, a rejected cookie, a resource that failed to load. - **Out of scope:** request and response bodies. A station-feed call that returned a 500 leaves a one-line console complaint, not the payload that caused it. - **Out of scope:** anything logged before the session's first navigation, and anything a previous `get()` already drained. `LogType.DRIVER` is a different stream entirely, served by the same endpoint, holding records the remote end emits rather than records the page emits. Calling `logs().get(LogType.DRIVER)` never returns console entries and never drains the `browser` buffer — Selenium's `GetLogsTest` asserts that two different log types never share the same entries. ## Working it into a failing station-map test - **Collect in the failure path.** Put the read in the hook that runs when a test fails, so the buffer still holds the errors from the run that just broke. - **Filter by level, not by grepping the message.** `entry.getLevel().intValue() >= Level.SEVERE.intValue()` picks out real errors and leaves the routine `INFO` chatter behind. - **Expect wall-clock timestamps.** Because `getTimestamp()` is epoch milliseconds, entries line up directly with the server-side timestamps of the station-feed requests that produced them. - **Do not assume every browser serves it.** The call compiles against any `WebDriver`, but not every remote end implements the endpoint behind it.

  • Your teardown prints nothing even though the station map clearly logged an error. What is the first thing you check?
    Whether something earlier already read the same buffer. `Logs.get()` resets the buffer for that log type on every call, so a debug helper or an assertion that peeked at `logs().get(LogType.BROWSER)` mid-test will have consumed the entries. Read once, in the failure path, and pass the resulting `LogEntries` around rather than calling `get()` again.
  • How do you keep routine console noise out of the entries you attach to a failure?
    Filter on `LogEntry.getLevel()`. Selenium normalises severities onto `java.util.logging.Level`, so comparing `entry.getLevel().intValue()` against `Level.SEVERE.intValue()` keeps errors and drops `INFO` chatter. You can also raise the floor at the source by enabling the browser log at `Level.WARNING` through logging preferences, so the remote end never buffers the noise.
  • Does getTimestamp() let you correlate a console error with a server-side request?
    Yes. `LogEntry.getTimestamp()` is milliseconds since the UNIX epoch — wall-clock time, not an offset from when the session started — so the value lines up directly with the timestamps your station-feed service writes. `LogEntry.toString()` already renders it as an ISO instant if you just want it readable in a report.

The browser log behaves like a pneumatic tube tray rather than a ledger: when you open it you take everything that is in there, and the tray is empty until the browser drops something new in.

saying these in an interview costs you the question

  • Says the call returns a single String of console text
  • Believes the buffer is only cleared when the session ends
  • Thinks getTimestamp is milliseconds since the session started
  • Reads the log mid-test then wonders why teardown sees nothing
  • Confuses the browser log with the driver executable's own log file