PRACTICAL GUIDE / Selenium BiDi response header override continuation
Override Response Headers with Selenium BiDi
Learn Selenium BiDi response header override continuation with working code, failure cases, debugging steps, and CI evidence for reliable QA automation.
In this guide11 sections
- Set a Response-Metadata Contract
- Decide Whether Continuation Is the Right Tool
- Pin the Binding and Use responseStarted
- Preserve Headers Before Replacing One
- Run the Java Continuation Workflow
- Correlate Original, Override, and Completion Evidence
- Build Negative Cases Around Policy Boundaries
- Diagnose Phase and Header Failures
- Own Cleanup and CI Release Behavior
- Frequently Asked Questions
- When can Selenium BiDi override response headers?
- Does continueResponse replace the response body?
- Are supplied response headers appended or treated as a replacement list?
- How do I prove the browser consumed the overridden header?
- Why does a responseStarted intercept leave the page waiting?
- Which response headers should a generic test utility refuse to modify?
- Practice Response Continuation in QABattle
What you will learn
- Set a Response-Metadata Contract
- Decide Whether Continuation Is the Right Tool
- Pin the Binding and Use responseStarted
- Preserve Headers Before Replacing One
Selenium BiDi response header override continuation pauses a matched response after its headers arrive, replaces one allowlisted field, and resumes the original body through continueResponse. Preserve every unrelated header, correlate responseStarted and responseCompleted by request ID, prove the browser consumed the override, keep an untouched control response, and remove the intercept during owned teardown.
Set a Response-Metadata Contract
Response interception is justified when the behavior under test depends on metadata the browser receives, while the original server body should remain intact. A test might select a client-side compatibility branch from a custom response field, simulate a deprecated gateway marker, or verify that UI code reacts to a changed cache directive in a controlled fixture. State the exact invariant before intercepting: original body B plus overridden header H must produce browser state S.
That contract has three distinct truths. The server emitted an original response. The BiDi callback sent a valid override for the blocked response. The browser or application consumed the modified metadata while finishing the original body. A local list constructed in Java proves only the middle intention. The command and event correlation guide explains why protocol command success and browser-visible completion need separate evidence.
Choose a harmless custom field for the mechanism test, such as X-QA-Mode: override. Use a same-origin page that reads the fetch response header and renders it beside content derived from the response body. The pre-continuation event should show X-QA-Mode: baseline, while the page shows override. A responseCompleted event with the same request ID establishes that the body reached its terminal network phase.
The full lifecycle is: enable BiDi, create a context-owned Network, register a RESPONSE_STARTED intercept, attach listeners before the browser action, receive a blocked response event, issue one continueResponse, observe completion or fetch error, assert the rendered metadata and body, remove the intercept, close listeners, and quit the session. The callback and intercept are asynchronous owned resources. They cannot be treated as global test configuration.
Decide Whether Continuation Is the Right Tool
Response override is one of several ways to exercise metadata behavior. A server fixture often provides the strongest end-to-end check because the response travels through the real stack. BiDi is useful when changing the fixture would be expensive, when the browser-side reaction is the specific boundary, or when a test needs to vary one response without changing shared environment state.
| Testing need | Technique | Primary proof | Risk to control |
|---|---|---|---|
| Inspect what the server returned | Observe responseStarted or responseCompleted | Original status and headers | Observation does not change behavior |
| Change metadata but retain the body | RESPONSE_STARTED intercept plus continueResponse | Browser sees override and body completes | Replacement list drops required headers |
| Replace status, headers, and body | Controlled provideResponse workflow | Synthetic response marker and product state | Stub masks server integration |
| Test the real gateway policy | Configure a test gateway or server | Server and browser evidence | Shared fixture can affect other tests |
| Resolve HTTP authentication | AUTH_REQUIRED continuation | Challenge and authorized state | Credentials require dedicated controls |
Do not use response continuation to avoid preparing a proper server case when server ownership is the requirement. If the API must really emit a policy field, validate it without modification. If the browser must react safely when an intermediary changes that field, an intercept is a valid fault boundary. Label reports so readers know whether evidence came from an original or modified response.
Authentication challenges have their own phase and commands. The network authentication article should guide tests involving WWW-Authenticate, credentials, or authorization outcomes. Body substitution also deserves separate treatment; the response-body capture guide shows why payload retention and evidence size need controls beyond a metadata-only test.
Keep one mutation owner. A reverse proxy, service worker, browser extension, and BiDi intercept can all alter responses. If two are active, the page may display the expected field even when the BiDi callback never matched. The baseline and untouched control should run in the same environment and record enough metadata to identify an unexpected intermediary.
Pin the Binding and Use responseStarted
Examples here use Selenium Java 4.44.0 signatures verified against the tagged source on July 18, 2026. That release exposes Network.continueResponse(ContinueResponseParameters) and response header data through ResponseDetails.getResponseData().getHeaders(). The high-level docs do not demonstrate every Java continuation in equal detail, so verify the exact installed artifact whenever dependencies change.
Set the webSocketUrl capability before driver construction. The Selenium BiDi overview describes this session requirement. For remote Grid, the provider must return and proxy the BiDi endpoint; classic WebDriver support alone is insufficient. A compatibility smoke case should create the network module and observe one known response before the suite registers blocking intercepts.
Use InterceptPhase.RESPONSE_STARTED. The Selenium W3C network examples show Java response event subscriptions, while the W3C network specification defines responseStarted as the point after headers are received and before the body is complete. At that blocked phase, continueResponse can update headers or status and still use the network body.
Do not send continueRequest for this callback merely because the object still carries a request ID. The ID correlates a network exchange; it does not erase phase rules. A response continuation sent at beforeRequestSent is invalid, and a request continuation is not the documented response-metadata operation. Log phase beside every decision so errors can be assigned without guessing from a page timeout.
The Selenium network features page describes the intended handler use cases and links implementation tracking. Pin Selenium, browser, and driver or Grid image together. Prefer a compile-time version adapter over reflection, and let an unsupported browser combination fail a capability probe rather than silently running a test without its override.
Teams replacing a CDP response hook should compare semantics before sharing an adapter. The old hook may have exposed mutable maps, appended a field, or delivered a different event after redirects. Use the Selenium CDP to BiDi migration guide to inventory the original phase, scope, and proof. The BiDi replacement should explicitly preserve the full response list, target responseStarted, join completion by request ID, and retain the browser-facing assertion. Run old and new implementations in separate sessions against the same synthetic fixture; two interceptors in one session cannot provide trustworthy ownership evidence.
Preserve Headers Before Replacing One
Supplying headers to ContinueResponseParameters is a replacement operation. The protocol creates a new list from the supplied values and sets the response header list to it. A continuation containing only X-QA-Mode may remove Content-Type, caching controls, CORS fields, security policy, validators, and content encoding. That can change body interpretation or make a test fail for an unrelated reason.
Build from event.getResponseData().getHeaders(). Remove every case-insensitive occurrence of the target name, add one intended value, and preserve all other Header objects. Do not convert the list to a simple map unless the contract explicitly defines how repeated fields are combined. Response headers such as Set-Cookie can legitimately repeat, and flattening them is a behavioral change.
Java example 1: replace one response field without flattening the list
import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.bidi.network.BytesValue;
import org.openqa.selenium.bidi.network.Header;
final class ResponseHeaders {
private ResponseHeaders() {}
static List<Header> replace(List<Header> original, String name, String value) {
List<Header> updated = new ArrayList<>(original.size() + 1);
for (Header header : original) {
if (!header.getName().equalsIgnoreCase(name)) {
updated.add(header);
}
}
updated.add(new Header(name, new BytesValue(BytesValue.Type.STRING, value)));
return List.copyOf(updated);
}
static String firstValue(List<Header> headers, String name) {
return headers.stream()
.filter(header -> header.getName().equalsIgnoreCase(name))
.map(header -> header.getValue().getValue())
.findFirst()
.orElse(null);
}
}Test the helper as ordinary Java. Verify mixed-case replacement, duplicate removal, unrelated duplicate preservation, input immutability, and exactly one output target. Add an allowlist at a higher policy layer so a caller cannot accidentally modify Set-Cookie, Content-Length, Content-Encoding, strict transport policy, content security policy, or CORS authorization headers.
Changing a field that controls body decoding can make the original bytes unreadable. Changing Content-Type can alter parsing. Changing status may move application code into a different branch before body consumption. Those are legitimate dedicated scenarios, but do not mix them into a custom-header proof. One changed dimension gives the failure a clear owner.
Treat status and headers as separate mutation dimensions. Selenium Java 4.44.0 also lets ContinueResponseParameters carry a status code and reason phrase, but a header-only case should leave both untouched. Changing 200 to 404 can alter fetch branching, cache eligibility, body parsing, and telemetry, making the header assertion difficult to isolate. If status behavior is required, create a second test with its own baseline, expected body handling, and negative control. Record the original status from responseStarted and the final product state, but do not assume responseCompleted alone reveals which application branch consumed it. This separation also simplifies browser comparisons because a divergence can be assigned to header continuation rather than a combined metadata rewrite. Reject extra mutation requests before registering the intercept.
Run the Java Continuation Workflow
Use a numbered sequence that reviewers can compare with event evidence:
- Configure a same-origin fixture whose server sends
X-QA-Mode: baselineand a known JSON body. - Start a BiDi-enabled browser, open the fixture page, and create a context-scoped
Networkmodule. - Add an exact
RESPONSE_STARTEDintercept and retain the intercept ID for cleanup. - Register
responseStarted,responseCompleted, andfetchErrorlisteners before triggering fetch. - On the owned blocked response, record the original field and request ID, construct the full replacement list, and call
continueResponseonce. - Match the terminal event by request ID, then assert the page rendered the overridden field and body-derived content.
- Fetch an adjacent control route and confirm it retains its original response field.
- Remove the intercept in
finally, close the module, and quit the driver after both complete.
Java example 2: override a custom response header and retain the body
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.AddInterceptParameters;
import org.openqa.selenium.bidi.network.ContinueResponseParameters;
import org.openqa.selenium.bidi.network.Header;
import org.openqa.selenium.bidi.network.InterceptPhase;
import org.openqa.selenium.bidi.network.UrlPattern;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;
class ResponseModeTest {
@Test
void browserSeesOverrideAndOriginalBody() throws Exception {
ChromeOptions options = new ChromeOptions();
options.setCapability("webSocketUrl", true);
WebDriver driver = new ChromeDriver(options);
try {
driver.get("https://app.example.test/response-lab");
AtomicReference<String> requestId = new AtomicReference<>();
AtomicReference<String> originalMode = new AtomicReference<>();
CompletableFuture<String> completedId = new CompletableFuture<>();
try (Network network = new Network(driver.getWindowHandle(), driver)) {
String interceptId = network.addIntercept(
new AddInterceptParameters(InterceptPhase.RESPONSE_STARTED)
.urlPattern(new UrlPattern()
.protocol("https")
.hostname("app.example.test")
.pathname("/api/profile")));
try {
network.onResponseStarted(event -> {
if (!event.isBlocked() || !event.getIntercepts().contains(interceptId)) {
return;
}
String id = event.getRequest().getRequestId();
URI uri = URI.create(event.getRequest().getUrl());
if (!"app.example.test".equals(uri.getHost())
|| !"/api/profile".equals(uri.getPath())) {
network.continueResponse(new ContinueResponseParameters(id));
return;
}
originalMode.set(ResponseHeaders.firstValue(
event.getResponseData().getHeaders(), "X-QA-Mode"));
List<Header> headers = ResponseHeaders.replace(
event.getResponseData().getHeaders(), "X-QA-Mode", "override");
requestId.set(id);
network.continueResponse(
new ContinueResponseParameters(id).headers(headers));
});
network.onResponseCompleted(event -> {
if (event.getRequest().getRequestId().equals(requestId.get())) {
completedId.complete(event.getRequest().getRequestId());
}
});
driver.findElement(By.id("load-profile")).click();
assertEquals(requestId.get(), completedId.get(5, TimeUnit.SECONDS));
assertEquals("baseline", originalMode.get());
new WebDriverWait(driver, Duration.ofSeconds(5)).until(d ->
d.findElement(By.id("response-mode")).getText().equals("override"));
new WebDriverWait(driver, Duration.ofSeconds(5)).until(d ->
d.findElement(By.id("profile-name")).getText().equals("Ada"));
driver.findElement(By.id("load-control-response")).click();
new WebDriverWait(driver, Duration.ofSeconds(5)).until(d ->
d.findElement(By.id("control-response-mode")).getText().equals("control"));
} finally {
network.removeIntercept(interceptId);
}
}
} finally {
driver.quit();
}
}
}The page fixture must derive response-mode from Response.headers, not from a hard-coded client variable. It derives profile-name from the original JSON body. These two assertions show that the browser consumed changed metadata and retained server content. Keep the route same-origin so a missing Access-Control-Expose-Headers rule does not turn a continuation test into an accidental CORS test.
Correlate Original, Override, and Completion Evidence
Use the request ID as the join key for the response event, continuation decision, completion event, and UI assertion record. Add context ID, redirect count, sanitized URL, original status, original target field hash or allowlisted value, replacement count, terminal event, and cleanup result. The intercept ID proves which owner blocked the event; it should appear in the event's intercept list before the callback acts.
Keep pre-override and post-override evidence distinct. responseStarted carries the response data that reached the interception point. The page's rendered field demonstrates what browser code consumed after continuation. responseCompleted shows that the full body phase finished, but a completion event by itself does not prove the application read the changed field.
Redirects produce multiple response stages. Decide whether the intercept should affect a redirect response, the final response, or both. An exact final path and expected redirect count reduce ambiguity. Correlation by URL alone can join the wrong hop, especially when a service redirects back to the same path. Store request ID and redirect count together.
Frames and popups can initiate the same route from different owners. Restrict Network to the top-level context when that matches the requirement and retain the event context in evidence. The browsing-context mutation article is a useful companion when navigation replaces or creates contexts during the fetch.
Do not retain complete response header sets in CI by default. They may include cookies, correlation tokens, infrastructure details, and policy data. Store names, counts, selected allowlisted values, and hashes where possible. A concise lifecycle record is easier to compare across a passing and failing run than an unrestricted network dump.
Build Negative Cases Around Policy Boundaries
Run the same route without an intercept and require baseline. This proves the server fixture and page reader work before BiDi changes anything. Then run the override and require override. A static page displaying override will fail the baseline, catching one of the simplest false positives.
Add a near-match route such as /api/profile-summary and a same-origin control response. Both must retain their original fields. Require one blocked event and one continuation in the target case. If the page legitimately retries the target, define the expected count and distinguish each request ID; silently accepting several callbacks can hide a retry regression.
Assert body continuity with content that cannot be derived from the test's header value. A profile name, record version, or body checksum from a synthetic fixture works. Test a malformed body separately to prove the page is not merely rendering the header and ignoring payload failure. The header override should not convert a body error into success.
Security fields need deny-by-default controls. A generic helper should reject attempts to weaken content security policy, strict transport security, cross-origin authorization, cookie delivery, or body framing. Dedicated tests may intentionally vary those fields, but they require exact origin scope, a synthetic environment, security review, and assertions for forbidden side effects. The CDP and BiDi parity guide can help identify browser differences without normalizing away a security failure.
Also test callback failure. Make the replacement policy reject a forbidden field and verify the handler continues the owned response unchanged, records the policy error, and fails the test thread. A policy guard that throws and leaves the response blocked is not a safe guard. Terminal handling and verdict reporting are separate responsibilities.
Diagnose Phase and Header Failures
No response event suggests listener timing, unsupported BiDi, wrong context, a request that never started, or a provider transport issue. An unblocked responseStarted means event observation works but the intercept did not match. Compare exact scheme, normalized host, port, path, and expected context. Do not start by increasing the page timeout.
A blocked event followed by a hang points to a missing or rejected terminal command. Capture callback exceptions in a future or atomic holder. Check whether the callback returned on an unexpected URL without continuing, whether another handler already used the request ID, and whether continueResponse received the complete replacement list. no such request often indicates stale or duplicate ownership; invalid argument points toward phase or parameter errors.
If the body becomes unreadable, compare preserved Content-Type, Content-Encoding, Content-Length, and transfer-related metadata by name and count without dumping values. A list that contains only the custom field is the obvious culprit. If browser code cannot read the changed field, verify same-origin behavior or the original exposure policy instead of adding a permissive CORS field inside the same test.
Unexpected duplicate values indicate case-sensitive removal, multiple active interceptors, or repeated target requests. Record original count and replacement count. The WebSocket subscription lifecycle guide helps detect listeners that survived an earlier case. A clean run should show registration before trigger and no callbacks after module closure.
Compare the earliest divergence between pass and fail: capability established, listener active, intercept added, request sent, response blocked, continuation accepted, response completed, header rendered, body rendered, cleanup complete. This sequence makes a stalled body distinguishable from a page assertion, server issue, or transport disconnect.
Own Cleanup and CI Release Behavior
The intercept ID belongs to the case that created it. Remove it in finally while Network and the driver are alive. Then close Network to clear listeners, and quit the driver last. Removing an intercept does not release a response already blocked by it, so the callback must settle every owned request before teardown proceeds.
Use one browser session per parallel worker and a unique synthetic record per case. Do not share response collectors or mutable request IDs. Keep evidence bounded and publish the dependency tuple: Selenium Java, browser, driver or Grid image, operating system image, and provider configuration. A version change should trigger the compatibility lane even when application code is untouched.
The Selenium BiDi response header override continuation CI pipeline should classify capability, trigger, callback, protocol, product, and cleanup failures separately. Retry only a named transient infrastructure class. Preserve first-attempt events and never retry a wrong-origin match, security-field mutation, duplicate continuation, missing body assertion, or cleanup failure into a green release signal.
Use deadlines for blocked response, continuation decision, completion, and rendered state as separate measurements. That timing split reveals where progress stopped. Gate on absent request-ID correlation, dropped original headers, contaminated controls, unexpected match count, missing body continuity, or failed cleanup. Trend browser-specific unsupported behavior rather than silently excluding it.
Keep the artifact reviewable: case and session IDs, versions, context, sanitized route, intercept ID, request ID, redirect count, original target value or hash, replacement count, completion status, body assertion, control result, and cleanup result. Use the Selenium BiDi interview scenarios to challenge whether each item supports a real release decision.
Frequently Asked Questions
When can Selenium BiDi override response headers?
Add an intercept for responseStarted and handle the blocked responseStarted event. At that point the browser has received response headers, but the body is not complete. Continue the same request with ContinueResponseParameters. A beforeRequestSent callback is too early, and responseCompleted is too late for this operation.
Does continueResponse replace the response body?
No. continueResponse can update response metadata such as headers and status while allowing the original network response body to continue. That distinction is useful for header-policy tests. Supplying a synthetic body is a different provide-response workflow and carries a greater risk of hiding server or integration defects.
Are supplied response headers appended or treated as a replacement list?
The protocol update steps set the response header list to the headers supplied with the continuation. Preserve the original response headers, remove all case-insensitive occurrences of the one allowlisted field, then add its intended value. Passing only the changed field can discard content type, caching, CORS, or security metadata.
How do I prove the browser consumed the overridden header?
Record the original value from the blocked responseStarted event, continue by request ID, and have a controlled same-origin page read the response header and render it. Correlate responseCompleted with the same ID and assert body-derived UI too. This proves original metadata, override consumption, and body continuation.
Why does a responseStarted intercept leave the page waiting?
The response is blocked until a valid continuation resolves it. An exception, early return, duplicate handler, wrong request ID, or phase-invalid command can leave body processing paused. Keep callback work small, capture its failure, use a bounded decision ledger, and issue exactly one continuation for each owned blocked response.
Which response headers should a generic test utility refuse to modify?
Refuse security and transport fields by default, including Set-Cookie, Content-Length, Content-Encoding, strict transport policy, content security policy, and CORS authorization fields. Test those contracts only in dedicated cases with exact scope and security review. Prefer a harmless custom field for proving the continuation mechanism itself.
Practice Response Continuation in QABattle
Go to the QABattle battle catalog and select a browser workflow backed by a controlled response. Build a baseline that renders the server's original custom field, an override case that preserves body-derived content, and a near-match control. Submit the request-ID timeline and cleanup result with the functional assertions.
Use the event-correlation guide to review whether the response truly moved through blocked, continued, and completed phases. Then compare evidence retention with the response-body capture article. A publication-ready solution proves the metadata changed without pretending that a locally constructed list is browser consumption.
// 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.
- 01Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 02Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 03Official selenium.dev reference
selenium.dev
Primary documentation selected and verified for the claims in this guide.
- 04Official w3c.github.io reference
w3c.github.io
Primary documentation selected and verified for the claims in this guide.
FAQ / QUICK ANSWERS
Questions testers ask
When can Selenium BiDi override response headers?
Add an intercept for responseStarted and handle the blocked responseStarted event. At that point the browser has received response headers, but the body is not complete. Continue the same request with ContinueResponseParameters. A beforeRequestSent callback is too early, and responseCompleted is too late for this operation.
Does continueResponse replace the response body?
No. continueResponse can update response metadata such as headers and status while allowing the original network response body to continue. That distinction is useful for header-policy tests. Supplying a synthetic body is a different provide-response workflow and carries a greater risk of hiding server or integration defects.
Are supplied response headers appended or treated as a replacement list?
The protocol update steps set the response header list to the headers supplied with the continuation. Preserve the original response headers, remove all case-insensitive occurrences of the one allowlisted field, then add its intended value. Passing only the changed field can discard content type, caching, CORS, or security metadata.
How do I prove the browser consumed the overridden header?
Record the original value from the blocked responseStarted event, continue by request ID, and have a controlled same-origin page read the response header and render it. Correlate responseCompleted with the same ID and assert body-derived UI too. This proves original metadata, override consumption, and body continuation.
Why does a responseStarted intercept leave the page waiting?
The response is blocked until a valid continuation resolves it. An exception, early return, duplicate handler, wrong request ID, or phase-invalid command can leave body processing paused. Keep callback work small, capture its failure, use a bounded decision ledger, and issue exactly one continuation for each owned blocked response.
Which response headers should a generic test utility refuse to modify?
Refuse security and transport fields by default, including Set-Cookie, Content-Length, Content-Encoding, strict transport policy, content security policy, and CORS authorization fields. Test those contracts only in dedicated cases with exact scope and security review. Prefer a harmless custom field for proving the continuation mechanism itself.
RELATED GUIDES
Continue the learning route
GUIDE 01
Selenium WebDriver BiDi Interview Questions
Selenium BiDi interview questions advanced: practical design, implementation, debugging, CI, metrics, and interview guidance for QA, SDET, and automation engineers.
GUIDE 02
Capture Network Response Bodies with Selenium WebDriver BiDi
Master Selenium BiDi response body with practical examples, architecture decisions, failure analysis, CI guidance, metrics, and scenario-led interview answers.
GUIDE 03
Migrate Selenium CDP Hooks to WebDriver BiDi
Migrate Selenium CDP hooks to WebDriver BiDi with a capability inventory, event adapters, parity tests, cross-browser rollout, and failure diagnostics.
GUIDE 04
Compare CDP and WebDriver BiDi Event Parity
A practical guide to Selenium CDP BiDi event parity testing, with implementation examples, debugging workflows, CI evidence, security controls, and release gates.
GUIDE 05
Combine Classic WebDriver Commands with BiDi Events
Combine Classic WebDriver Commands with BiDi Events: practical implementation, debugging, evidence, security, CI, and release guidance for QA teams.