Resource library

QA How-To

Selenium BiDi Capture Network HAR in Java Tutorial (2026)

Learn selenium bidi capture network har java with a runnable JUnit recorder that correlates requests, writes HAR 1.2 JSON, redacts secrets, and verifies output.

24 min read | 2,582 words

TL;DR

Enable BiDi on ChromeOptions, open Selenium's Network module, store onBeforeRequestSent events by request ID, pair them with onResponseCompleted, and serialize the pairs as HAR 1.2 JSON. The resulting HAR contains URLs, methods, headers, status, protocol, MIME type, timing, and byte counts, but no response bodies because the public Selenium BiDi Network API does not retrieve them.

Key Takeaways

  • Enable BiDi before session creation and subscribe before the action whose traffic you need.
  • Correlate request and response events by request ID, never by arrival order or URL alone.
  • Selenium 4.39.0 supplies HAR metadata and sizes but not response bodies through the public BiDi Network module.
  • Write valid HAR 1.2 JSON with explicit defaults for fields that BiDi does not expose.
  • Redact authorization, cookie, and token-bearing headers before saving CI artifacts.
  • Use thread-safe maps because BiDi callbacks and the JUnit test execute asynchronously.
  • Validate the generated file structurally and semantically before attaching it to a failed test.

The selenium bidi capture network har java workflow is an event-correlation task, not a built-in saveHar() call. Enable WebDriver BiDi, listen for outgoing requests and completed responses, match both events by request ID, and serialize the matched metadata into the HAR 1.2 shape.

This tutorial builds a runnable JUnit 5 recorder with Selenium 4.39.0 and Jackson 2.18.2. It produces a portable .har artifact while clearly marking data that Selenium's public BiDi Network module cannot currently supply. For the wider protocol model, read the complete Selenium BiDi automation guide.

What You Will Build

You will create a small Maven project that can:

  • Start Chrome with a negotiated BiDi WebSocket.
  • Capture request and completed-response events during one browser journey.
  • Correlate redirects and parallel resources by WebDriver BiDi request ID.
  • Export HAR 1.2 JSON with request headers, response headers, status, MIME type, sizes, and elapsed time.
  • Redact sensitive header values and verify the artifact with Java and a shell command.

The recorder is intentionally metadata-first. Selenium 4.39.0's public org.openqa.selenium.bidi.module.Network exposes RequestData, ResponseData, headers, timings, and sizes. It does not expose a response-body retrieval command, so the HAR content.text member is omitted. That limitation is preferable to inventing an empty body that looks authoritative.

Prerequisites

Use Java 21, Maven 3.9.9, Selenium Java 4.39.0, JUnit Jupiter 5.11.4, Jackson Databind 2.18.2, and Chrome 142 or newer. Selenium Manager, included with Selenium, resolves the compatible driver when Chrome is installed and reachable.

Check the tools first:

java -version
mvn -version
google-chrome --version || chromium --version

On macOS, the browser version can be checked with:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version

The examples use the public Selenium test site so you can run them without creating a demo application. A corporate proxy may change the observed entries, but it should not prevent the navigation entry from appearing.

Verification: Confirm Java reports major version 21 and Maven reports 3.9.x. If the browser command is unavailable, open Chrome's About page and verify its installed version before continuing.

Step 1: Create the Maven Project

Create pom.xml with pinned dependencies and a compiler target. Jackson writes standards-compliant JSON; JUnit owns the test lifecycle.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>dev.qajobfit</groupId>
  <artifactId>bidi-har-recorder</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>21</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>4.39.0</version>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>5.11.4</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.18.2</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.2</version>
        <configuration><useModulePath>false</useModulePath></configuration>
      </plugin>
    </plugins>
  </build>
</project>

The dependency on selenium-java brings in the BiDi Network classes and Selenium Manager. Do not add a versioned CDP artifact. This implementation uses the standards-oriented BiDi API, not Chromium's DevTools protocol. The distinction is covered in Selenium DevTools in Java.

Verification: Run mvn -q dependency:tree. The output must contain selenium-java:4.39.0, junit-jupiter:5.11.4, and jackson-databind:2.18.2.

Step 2: Enable BiDi and Prove Events Arrive

Create src/test/java/dev/qajobfit/BidiSmokeTest.java. BiDi must be enabled in the options before ChromeDriver creates the session. Register the listener before navigation so the document request cannot outrun the subscription.

package dev.qajobfit;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.BeforeRequestSent;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

class BidiSmokeTest {
  @Test
  void receivesNavigationRequest() throws Exception {
    String page = "https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html";
    ChromeOptions options = new ChromeOptions().enableBiDi();
    WebDriver driver = new ChromeDriver(options);

    try (Network network = new Network(driver)) {
      CompletableFuture<BeforeRequestSent> navigation = new CompletableFuture<>();
      network.onBeforeRequestSent(event -> {
        if (page.equals(event.getRequest().getUrl())) navigation.complete(event);
      });

      driver.get(page);
      BeforeRequestSent event = navigation.get(10, TimeUnit.SECONDS);
      assertEquals("GET", event.getRequest().getMethod());
    } finally {
      driver.quit();
    }
  }
}

A CompletableFuture transfers the asynchronous callback result to the test thread. Filtering by exact URL prevents a stylesheet, favicon, or analytics call from satisfying the assertion. Avoid assertions inside the callback because a failure on the event thread may not be reported by JUnit as the test failure you expect.

Verification: Run mvn -q -Dtest=BidiSmokeTest test. A successful run exits with status zero. A timeout means BiDi was not negotiated, the WebSocket is blocked, or the listener was registered too late.

Step 3: Model a HAR 1.2 Entry

HAR uses a top-level log object containing version, creator, pages, and entries. Each entry connects one request to one response. Java records keep the model compact while Jackson preserves the expected property names. Create src/test/java/dev/qajobfit/HarModel.java:

package dev.qajobfit;

import java.util.List;
import java.util.Map;

record HarLog(HarRoot log) {}
record HarRoot(String version, HarCreator creator, List<HarPage> pages,
               List<HarEntry> entries) {}
record HarCreator(String name, String version) {}
record HarPage(String startedDateTime, String id, String title,
               Map<String, Long> pageTimings) {}
record HarEntry(String pageref, String startedDateTime, long time,
                HarRequest request, HarResponse response,
                HarCache cache, HarTimings timings, String serverIPAddress,
                String connection, String comment) {}
record HarRequest(String method, String url, String httpVersion,
                  List<HarHeader> headers, List<HarQuery> queryString,
                  List<HarCookie> cookies, long headersSize, long bodySize) {}
record HarResponse(int status, String statusText, String httpVersion,
                   List<HarHeader> headers, List<HarCookie> cookies,
                   HarContent content, String redirectURL,
                   long headersSize, long bodySize) {}
record HarHeader(String name, String value) {}
record HarQuery(String name, String value) {}
record HarCookie(String name, String value) {}
record HarContent(long size, String mimeType) {}
record HarCache() {}
record HarTimings(long blocked, long dns, long connect, long send,
                  long wait, long receive, long ssl) {}

HAR readers expect several fields even when the browser does not expose their values. HAR uses -1 for unavailable timings and sizes. Empty arrays represent known absence or intentionally uncaptured details. The HarContent record has no text field because this recorder cannot obtain response bodies through the public API.

Request post data is also absent from the current RequestData surface. This makes the artifact suitable for navigation diagnosis, endpoint discovery, status analysis, caching evidence, and size inspection, but not request-body or response-schema assertions. Use a direct API layer for those, following the API testing versus UI testing guide.

Verification: Run mvn -q -DskipTests test-compile. The command should compile HarModel.java without generated getters or Lombok.

Step 4: Selenium BiDi Capture Network HAR Java Event Correlation

Create src/test/java/dev/qajobfit/BidiHarRecorder.java. The request ID is the stable join key. A URL is not sufficient because retries, redirects, polling, and parallel calls can reuse it.

package dev.qajobfit;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.BeforeRequestSent;
import org.openqa.selenium.bidi.network.Header;
import org.openqa.selenium.bidi.network.ResponseData;
import org.openqa.selenium.bidi.network.ResponseDetails;

final class BidiHarRecorder implements AutoCloseable {
  private static final Set<String> SECRET_HEADERS = Set.of(
      "authorization", "proxy-authorization", "cookie", "set-cookie",
      "x-api-key", "x-auth-token");

  private final Network network;
  private final Map<String, BeforeRequestSent> requests = new ConcurrentHashMap<>();
  private final Map<String, ResponseDetails> responses = new ConcurrentHashMap<>();

  BidiHarRecorder(org.openqa.selenium.WebDriver driver) {
    network = new Network(driver);
    network.onBeforeRequestSent(event ->
        requests.put(event.getRequest().getRequestId(), event));
    network.onResponseCompleted(event ->
        responses.put(event.getRequest().getRequestId(), event));
  }

  int completedCount() {
    return responses.size();
  }

  void write(Path output, String pageTitle) throws IOException {
    List<HarEntry> entries = requests.entrySet().stream()
        .filter(entry -> responses.containsKey(entry.getKey()))
        .map(entry -> toEntry(entry.getValue(), responses.get(entry.getKey())))
        .sorted(Comparator.comparing(HarEntry::startedDateTime))
        .toList();

    String started = entries.isEmpty()
        ? Instant.now().toString() : entries.getFirst().startedDateTime();
    HarPage page = new HarPage(started, "page_1", pageTitle,
        Map.of("onContentLoad", -1L, "onLoad", -1L));
    HarRoot root = new HarRoot("1.2",
        new HarCreator("QAJobFit Selenium BiDi recorder", "1.0"),
        List.of(page), entries);

    new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT)
        .writeValue(output.toFile(), new HarLog(root));
  }

  private HarEntry toEntry(BeforeRequestSent before, ResponseDetails completed) {
    ResponseData response = completed.getResponseData();
    long elapsed = Math.max(0, completed.getTimestamp() - before.getTimestamp());
    String protocol = response.getProtocol() == null ? "" : response.getProtocol();
    HarRequest request = new HarRequest(
        before.getRequest().getMethod(), before.getRequest().getUrl(), protocol,
        headers(before.getRequest().getHeaders()), queries(before.getRequest().getUrl()),
        List.of(), before.getRequest().getHeadersSize(), -1);
    HarResponse harResponse = new HarResponse(
        response.getStatus(), response.getStatusText(), protocol,
        headers(response.getHeaders()), List.of(),
        new HarContent(response.getBodySize(), response.getMimeType()),
        redirect(response.getHeaders()), response.getHeadersSize(), response.getBodySize());
    HarTimings timings = new HarTimings(-1, -1, -1, -1, elapsed, 0, -1);

    return new HarEntry("page_1", Instant.ofEpochMilli(before.getTimestamp()).toString(),
        elapsed, request, harResponse, new HarCache(), timings, "", "",
        response.isFromCache() ? "Served from browser cache" : "");
  }

  private static List<HarHeader> headers(List<Header> source) {
    return source.stream().map(header -> {
      String name = header.getName();
      String value = SECRET_HEADERS.contains(name.toLowerCase(Locale.ROOT))
          ? "[REDACTED]" : header.getValue().getValue();
      return new HarHeader(name, value);
    }).toList();
  }

  private static String redirect(List<Header> headers) {
    return headers.stream().filter(h -> h.getName().equalsIgnoreCase("location"))
        .map(h -> h.getValue().getValue()).findFirst().orElse("");
  }

  private static List<HarQuery> queries(String rawUrl) {
    String query = URI.create(rawUrl).getRawQuery();
    if (query == null || query.isBlank()) return List.of();
    List<HarQuery> values = new ArrayList<>();
    for (String pair : query.split("&")) {
      String[] parts = pair.split("=", 2);
      values.add(new HarQuery(decode(parts[0]), parts.length == 2 ? decode(parts[1]) : ""));
    }
    return List.copyOf(values);
  }

  private static String decode(String value) {
    return URLDecoder.decode(value, StandardCharsets.UTF_8);
  }

  @Override
  public void close() {
    network.close();
  }
}

ConcurrentHashMap is required because listener callbacks can execute separately from the test thread. The export takes a point-in-time view and includes only completed pairs. Failed or still-active requests need a separate policy, discussed later.

The conversion uses event timestamps for startedDateTime and total elapsed time. Detailed HAR phases remain -1 because translating BiDi's fetch timing values into HAR phases correctly requires careful redirect and connection reuse handling. A truthful unknown value is better than misleading precision.

Verification: Run mvn -q -DskipTests test-compile. If getFirst() fails, confirm the compiler release is 21. If your project must remain on Java 17, replace it with entries.get(0).

Step 5: Run the Recorder and Write the HAR

Create src/test/java/dev/qajobfit/BidiHarCaptureTest.java. Wait for completed traffic before writing. document.readyState alone does not guarantee that every asynchronous request has finished, so the example waits for the known document response and then uses a short stability check.

package dev.qajobfit;

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

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;

class BidiHarCaptureTest {
  @Test
  void capturesHar() throws Exception {
    Path output = Path.of("target", "artifacts", "selenium-page.har");
    Files.createDirectories(output.getParent());
    WebDriver driver = new ChromeDriver(new ChromeOptions().enableBiDi());

    try (BidiHarRecorder recorder = new BidiHarRecorder(driver)) {
      driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html?run=har");
      new WebDriverWait(driver, Duration.ofSeconds(10))
          .until(ignored -> recorder.completedCount() > 0);

      int previous;
      do {
        previous = recorder.completedCount();
        Thread.sleep(250);
      } while (recorder.completedCount() != previous);

      recorder.write(output, driver.getTitle());
      assertTrue(Files.size(output) > 100);
    } finally {
      driver.quit();
    }
  }
}

The 250 ms stability window is bounded observation, not a substitute for application knowledge. In a real checkout test, wait for the specific order endpoint or a UI outcome and then write. Long-lived WebSocket, server-sent event, and polling connections may never make the global network quiet.

Keep the recorder scope inside one test journey. A suite-wide capture can mix users, retain excessive metadata, and make failures difficult to attribute. If you need a HAR only on failure, write into a temporary path during teardown when JUnit reports a failed outcome, but preserve the same redaction policy.

Verification: Run mvn -q -Dtest=BidiHarCaptureTest test, then test -s target/artifacts/selenium-page.har. Both commands should exit zero. Open the file and confirm its first object contains "log".

Step 6: Validate the HAR Artifact

A nonempty file can still be useless. Add src/test/java/dev/qajobfit/HarValidationTest.java to verify the version, entry count, navigation URL, status, and redaction.

package dev.qajobfit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.file.Path;
import java.util.stream.StreamSupport;
import org.junit.jupiter.api.Test;

class HarValidationTest {
  @Test
  void validatesCapturedHar() throws Exception {
    Path file = Path.of("target", "artifacts", "selenium-page.har");
    JsonNode log = new ObjectMapper().readTree(file.toFile()).path("log");
    assertEquals("1.2", log.path("version").asText());
    assertFalse(log.path("entries").isEmpty());

    boolean navigationFound = StreamSupport.stream(
        log.path("entries").spliterator(), false).anyMatch(entry ->
          entry.path("request").path("url").asText().contains("logEntryAdded.html")
              && entry.path("response").path("status").asInt() == 200);
    assertTrue(navigationFound);

    String json = log.toString().toLowerCase();
    assertFalse(json.contains("bearer ey"));
    assertFalse(json.contains("sessionid="));
  }
}

Run capture and validation in order because the second test consumes the first test's artifact. In a production suite, combine artifact creation and semantic assertions in the same test or use a Maven integration-test phase with explicit ordering. Never depend on alphabetical JUnit execution.

You can inspect top-level values without another dependency:

python3 -m json.tool target/artifacts/selenium-page.har >/dev/null
rg '"version"|"entries"|logEntryAdded' target/artifacts/selenium-page.har

Verification: Run mvn -q -Dtest=BidiHarCaptureTest,HarValidationTest test. Expect both tests to pass and the navigation entry to report HTTP 200.

Step 7: Handle Redirects, Failures, and Incomplete Requests

Redirects can reuse a logical navigation while producing distinct request events and redirect counts. The recorder keys by request ID, so it does not confuse two simultaneous calls to the same URL. Preserve each completed pair as its own HAR entry and use the response Location header for redirectURL.

A fetch failure does not trigger onResponseCompleted. Subscribe to network.onFetchError if failure evidence matters, store the request ID and error text, and emit a synthetic response with status 0 plus a clear comment. Do not claim a server status when the browser never received one. The Selenium BiDi network Java guide explains fetch-error events and interception in more depth.

Requests still in flight when write runs are currently excluded. Three defensible alternatives exist:

Policy Best use Trade-off
Export completed pairs only Stable page journeys Hides pending requests
Emit status 0 for pending requests Timeout diagnosis HAR readers may display unusual entries
Wait for named business endpoints Deterministic UI tests Requires scenario-specific filters

Choose the policy per test purpose. A blanket wait for zero active requests is unreliable on applications with polling or streaming. For a Save flow, create a future for POST /api/orders, wait for its completed response, assert the status, and then export. This ties the artifact boundary to the behavior under test.

Verification: Navigate to a controlled redirect URL and confirm the HAR contains separate entries with the first response's redirectURL. Then navigate to a deliberately unreachable test host with a short page-load timeout and confirm your chosen failure policy produces either a documented status-0 entry or an explicit omission.

Step 8: Make Selenium BiDi Capture Network HAR Java Safe for CI

HAR files can contain authentication headers, cookies, query tokens, internal hostnames, and personal data. The sample redacts common secret-bearing headers before serialization, but a production policy should also sanitize query parameter names such as token, code, key, and signature. Prefer an allowlist of safe headers when the diagnostic need is narrow.

Do not attach every passing test's HAR indefinitely. Capture focused journeys, write failures or sampled runs, compress large artifacts, and set a retention period in CI. Record browser and Selenium versions beside the file so another engineer can reproduce protocol differences. Never upload a HAR from a production account to a public issue.

Parallel execution needs one driver and one recorder per test. Include a unique test ID in the output filename to prevent workers from overwriting each other. Close Network before quitting the driver, as the try-with-resources order in the example does. A static recorder would mix request IDs and callbacks across sessions.

For remote Grid execution, the server must return and proxy a usable BiDi WebSocket. Prove one smoke event before enabling capture across the fleet. Learn the infrastructure boundary in the Selenium Grid Docker guide.

A HAR is diagnostic evidence, not an API contract suite. Assert the small number of browser-boundary facts the UI journey owns, then keep schema, authorization, and broad status coverage in direct API tests. This separation reduces flaky UI traffic assertions and keeps the artifact useful.

Verification: Add an Authorization: Bearer test-secret header in a controlled local application, capture it, and confirm the serialized value is [REDACTED]. Search the artifact for test-secret; the command must return no matches.

Troubleshooting

Network says the driver does not support BiDi -> Call new ChromeOptions().enableBiDi() before constructing ChromeDriver. On Grid, verify the returned capabilities contain a WebSocket URL and that intermediaries permit the WebSocket connection.

The HAR has zero entries -> Construct the recorder and register both listeners before driver.get or the click that causes traffic. Wait for a known completed response instead of writing immediately after the UI action.

The same URL appears several times -> This is normal for redirects, retries, polling, and repeated resources. Correlate with request ID and inspect method, redirect count, timestamps, and status rather than deduplicating by URL.

Response bodies are missing -> The public Selenium 4.39.0 BiDi Network module exposes body size but no response-body retrieval method. Do not invent getResponseBody(). Use metadata-only HAR, a controlled proxy, or a direct API client when content is required.

A HAR viewer rejects the file -> Confirm the root is log, version is 1.2, arrays are present, dates use ISO 8601, and unknown numeric timings use -1. Run python3 -m json.tool to separate malformed JSON from HAR-schema issues.

CI artifacts expose credentials -> Redact before writing, not after upload. Cover authorization, cookies, API keys, signed query parameters, and application-specific headers, then add automated searches for known test-secret patterns.

Where To Go Next

You now have a focused recorder built on the same typed events used throughout the complete Selenium BiDi automation guide. Extend it only where the diagnostic requirement justifies the additional data.

Interview Questions and Answers

Q: How do you create a HAR with Selenium BiDi in Java?

Enable BiDi during session creation, subscribe to before-request and completed-response events, and join them by request ID. Convert each pair into a HAR 1.2 entry and serialize the top-level log object. Selenium does not provide a single HAR export method in the public Java Network module.

Q: Why is request ID a better correlation key than URL?

One page can request the same URL several times because of retries, polling, redirects, or caching. Concurrent requests can also complete in a different order from their start order. The protocol request ID identifies the specific event sequence without relying on timing assumptions.

Q: Why are some HAR timings set to -1?

HAR defines -1 for an unavailable phase. BiDi exposes fetch timing data, but accurate conversion must account for redirects, reused connections, and the exact units and origin of each value. Reporting unknown is safer than publishing misleading DNS, TLS, or connect durations.

Q: Can this recorder capture response bodies?

Not through Selenium 4.39.0's public BiDi Network module. ResponseData exposes MIME type, byte counts, headers, cache status, protocol, and status, but no body retrieval call. Use a direct API client or a controlled proxy when body content is an explicit requirement.

Q: How would you make the recorder thread-safe?

Store events in concurrent collections because callbacks are asynchronous. Keep one recorder per WebDriver session, pair data by request ID, take a stable snapshot during export, and perform JUnit assertions on the test thread rather than inside listeners.

Q: What data should be removed from a HAR before CI upload?

Redact authorization and proxy-authorization values, cookies, set-cookie headers, API keys, session tokens, signed query parameters, and application-specific secrets. Consider personal data in URLs and response headers as well. Sanitization must happen before the artifact leaves the test process.

Best Practices

  • Start the BiDi-enabled session before constructing Network, then register listeners before the triggering action.
  • Pair events by request ID and retain redirects as separate entries.
  • Wait for a named business response instead of assuming the entire browser becomes idle.
  • Represent unavailable HAR fields honestly with empty values or -1.
  • Keep bodies absent when the API cannot retrieve them.
  • Redact secrets before writing and test the redaction with known canary values.
  • Use one recorder and one output path per test worker.
  • Close the recorder before quitting WebDriver.
  • Validate JSON syntax, HAR version, entry count, target URL, and expected status.
  • Treat HAR as focused browser evidence, not a replacement for API tests.

Conclusion

A reliable selenium bidi capture network har java implementation combines typed event listeners, request-ID correlation, thread-safe storage, explicit HAR 1.2 mapping, and aggressive redaction. The runnable project records the metadata Selenium actually exposes and avoids fabricating response bodies or detailed phases.

Run the capture and validation tests together, inspect the artifact in your preferred HAR viewer, and then replace the demo navigation with one business journey. Keep the capture boundary narrow and the retention policy short so the resulting file remains useful, safe evidence rather than a noisy archive.

Interview Questions and Answers

Describe the architecture of a Selenium BiDi HAR recorder.

Enable BiDi during browser session creation and open one Network module per driver. Store before-request and completed-response events in thread-safe maps keyed by request ID. At the capture boundary, convert completed pairs to HAR 1.2 entries, redact sensitive values, serialize JSON, and validate the artifact.

Why should a network listener be registered before navigation?

The document request begins as soon as navigation starts. Registering afterward creates a race in which the key event can be emitted before the subscription exists. Set up listeners first, trigger the action second, and wait for the intended event with a timeout.

How do redirects affect HAR correlation?

Redirects create multiple request and response stages and may reuse the same navigation context. Keep each completed request ID as a separate HAR entry, retain the redirect target from the Location header, and do not deduplicate entries by URL.

How would you represent a network fetch failure in HAR?

Subscribe to the fetch-error event and correlate it with its request ID. If the team needs failures in the artifact, emit a documented entry with status 0 and the error in a comment rather than inventing a server response. Alternatively, omit it under a completed-pairs-only policy and document that choice.

What concurrency risks exist in BiDi event capture?

Callbacks can arrive asynchronously while the test reads or exports state, and responses can finish out of order. Use concurrent collections, stable request IDs, one recorder per driver, bounded waits, and assertions on the JUnit thread. Avoid a static recorder shared by parallel sessions.

When would you choose a proxy instead of Selenium BiDi for HAR capture?

Choose a controlled proxy when complete payload bodies, TLS-level evidence, or browser-independent capture is mandatory and the infrastructure cost is acceptable. Prefer BiDi when typed browser events, minimal setup, cross-browser protocol direction, and focused metadata are sufficient.

Frequently Asked Questions

Can Selenium BiDi export a HAR file directly in Java?

The public Selenium Java BiDi Network module does not offer a one-call HAR exporter. Listen to request and response events, correlate them by request ID, map the available fields to HAR 1.2, and serialize the result.

Which Selenium version does this HAR recorder use?

The tutorial pins Selenium Java 4.39.0 with Java 21 and JUnit Jupiter 5.11.4. Pinning versions makes the API signatures reproducible and avoids accidental differences during dependency upgrades.

Why does the generated HAR not contain response text?

Selenium 4.39.0 ResponseData exposes response metadata and body size but the public BiDi Network module has no response-body retrieval method. Omitting content.text accurately communicates that the body was not captured.

How do I match Selenium request and response events?

Use event.getRequest().getRequestId() as the map key for both the before-request and response-completed callbacks. Do not match on URL or arrival order because both are ambiguous under redirects and concurrency.

Is a HAR file safe to upload as a CI artifact?

Only after sanitization. Redact authorization values, cookies, API keys, session tokens, signed query parameters, and any application-specific sensitive headers before writing or uploading the file.

Why are HAR timing values sometimes -1?

The HAR 1.2 convention uses -1 when a timing phase is unavailable. Preserve unknown values rather than deriving inaccurate DNS, TLS, connection, or blocked durations from insufficient data.

How should I decide when network capture is complete?

Wait for the specific business response or UI result that defines the journey. A global network-idle rule is unreliable for pages that poll, stream events, or keep WebSockets open.

Related Guides