PRACTICAL GUIDE / Selenium BiDi block request intercept testing
Block Browser Requests with Selenium BiDi Intercepts
Learn Selenium BiDi block request intercept testing with working code, failure cases, debugging steps, and CI evidence for reliable QA automation.
In this guide11 sections
- Define the Request-Blocking Contract
- Choose the Smallest Interception Strategy
- Pin BiDi Support Before the Test Runs
- Implement the Owned Blocking Workflow
- Correlate Request IDs Across the Event Lifecycle
- Build Negative Controls That Can Catch a False Pass
- Diagnose a Page That Never Resumes
- Run the Checks Safely in Parallel CI
- Protect Credentials, Data, and Shared Environments
- Frequently Asked Questions
- What does a Selenium BiDi request intercept actually block?
- How do I prove that failRequest blocked the intended request?
- Why can a blocked Selenium BiDi request make the test hang?
- Should a test block top-level navigation or a subresource?
- How should Selenium BiDi intercepts be cleaned up?
- How should request blocking run in parallel CI?
- Practice the Failure Boundary in QABattle
What you will learn
- Define the Request-Blocking Contract
- Choose the Smallest Interception Strategy
- Pin BiDi Support Before the Test Runs
- Implement the Owned Blocking Workflow
Selenium BiDi block request intercept testing pauses a narrowly matched browser request, fails it with the event's request ID, and proves both the resulting fetchError and the application's fallback. Register observation before the trigger, give every blocked event one terminal decision, preserve a nearby successful control request, and remove the intercept before closing its listeners.
Define the Request-Blocking Contract
A useful blocking test starts with a product requirement, not a network trick. A checkout page might tolerate a recommendation outage but must not continue when the payment quote fails. An analytics script may be optional, while the consent endpoint is not. Write the contract as one sentence: when resource X cannot load, the browser-visible workflow must enter state Y without corrupting state Z. That statement tells you which request to block and which user outcome to assert.
WebDriver BiDi provides two related mechanisms. An event subscription observes network activity. An intercept adds a blocking condition at a selected phase. The official W3C network examples are easier to reason about when the distinction is explicit: an ordinary beforeRequestSent event reports activity, while a matching BEFORE_REQUEST_SENT intercept makes the event blocked and requires a command. For the broader event model, the WebDriver BiDi event architecture guide shows why command completion and asynchronous browser evidence are separate timelines.
The lifecycle is registration, match, blocked event, terminal command, downstream event, product assertion, intercept removal, listener closure, and session quit. The first observable failure boundary is the blocked event with isBlocked() true and the expected intercept ID. The next proof is fetchError with the same request ID after failRequest. A spinner, timeout, or missing element alone cannot distinguish deliberate failure injection from a listener that never ran.
The Selenium W3C network documentation documents Java support for intercept registration, removal, request failure, and network events. The WebDriver BiDi specification adds the decisive protocol rule: while a request is blocked, no further processing occurs. Treat the callback and intercept as asynchronous owned resources, not ambient session configuration.
Choose the Smallest Interception Strategy
Blocking is not always the right instrument. If the test only needs to confirm that an endpoint was called, subscribe without an intercept. If the server can expose a deterministic fault fixture, that may provide better integration coverage because it preserves the full network route. Use BiDi blocking when the risk specifically concerns browser behavior after a resource-level failure and the server cannot or should not be changed for the case.
| Decision | Best mechanism | Evidence to require | Main false positive |
|---|---|---|---|
| Observe a request without changing it | onBeforeRequestSent listener | URL, context, request ID, product outcome | Mistaking activity for success |
| Simulate a browser-side network failure | BEFORE_REQUEST_SENT plus failRequest | Blocked event, correlated fetchError, fallback UI | Failing the wrong request |
| Return deterministic response content | Scoped intercept plus provideResponse where supported | Stub marker, response event, product state | Stub hiding an integration defect |
| Exercise a real service outage | Test environment or fault proxy | Server logs, browser events, product state | Shared outage affecting other suites |
| Test HTTP authentication | AUTH_REQUIRED flow | Challenge, redacted decision, authorized state | Sending credentials to an unintended host |
The network authentication intercept guide is the safer reference when a 401 challenge is involved. Do not convert an authentication test into generic request blocking, because the required terminal command and credential risks are different. Likewise, a CDP-to-BiDi migration should preserve the old test's assertion boundary rather than copying CDP method names into a new callback.
Prefer an exact scheme, host, path, and browsing context. Query values may be dynamic, so use a stable path plus an application-generated case header or payload marker when necessary. A broad pattern such as *api* can match preflight requests, redirects, health checks, or another worker's traffic. If several requests are intentionally eligible, keep a bounded set of expected IDs and state how many terminal decisions should occur.
Count decisions as well as matches. For a test expecting one target, the final ledger should show one blocked event, one request ID, one failRequest command, and one correlated terminal event. Zero decisions means the fault was never injected. Two decisions usually indicate duplicate listeners, concurrent matching calls, or an unsafe retry inside the callback. This accounting turns an intermittent timeout into a precise ownership failure and gives CI a stable threshold that does not depend on browser error text.
Pin BiDi Support Before the Test Runs
The Java examples below use Selenium 4.44.0 signatures, checked against the tagged Selenium source on July 18, 2026. In that release, Network exposes addIntercept, removeIntercept, failRequest, continueRequest, onBeforeRequestSent, and onFetchError. The network module remains version-sensitive, so pin the Selenium client, browser, and driver or Grid image as one tested combination. Compile failures after an upgrade are useful signals, not reasons to hide the API behind reflection.
Enable BiDi by setting the webSocketUrl capability before session creation. Do not infer support merely because classic WebDriver navigation works. Constructing Network requires a driver that implements HasBiDi; a remote provider must return and proxy the BiDi connection too. Run one capability smoke test per browser image before expensive cases and mark unsupported combinations explicitly rather than silently skipping assertions.
The Selenium BiDi overview explains the WebSocket capability and the difference from ordered classic commands. The high-level Selenium network features page also warns through its evolving examples that language bindings do not always expose features at the same time. Keep a dependency test that compiles the exact imports used by the suite and a runtime test that receives one known event.
Use a dedicated browser session for an intercepting test. In Selenium Java 4.44.0, Network.close() clears several network event types, but the tagged source does not clear listeners registered through onFetchError. Because this test uses that event, driver quit is the final listener-containment boundary. Broad session sharing makes ownership ambiguous, so keep Network, request IDs, futures, and intercept IDs out of static fixtures.
Implement the Owned Blocking Workflow
Follow one chronology so setup races are visible during review:
- Create a BiDi-enabled browser session and navigate to a stable test fixture.
- Construct a context-scoped
Networkmodule and bounded, thread-safe evidence holders. - Add an exact
BEFORE_REQUEST_SENTintercept and retain its returned ID. - Register
beforeRequestSentandfetchErrorlisteners before triggering the request. - In the callback, ignore unblocked events, verify the intercept ID and URL, store the request ID, then call
failRequestexactly once. - Trigger the browser action and wait for
fetchErrorwith that same request ID. - Assert the intended fallback plus an independent request that still succeeds.
- Remove the intercept in
finally, close the module, and quit the session in that order.
Java example 1: fail one matched subresource and own its cleanup
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI;
import java.time.Duration;
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.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 RecommendationFailureTest {
@Test
void showsFallbackWhenRecommendationRequestIsBlocked() throws Exception {
ChromeOptions options = new ChromeOptions();
options.setCapability("webSocketUrl", true);
WebDriver driver = new ChromeDriver(options);
try {
driver.get("https://app.example.test/network-lab");
AtomicReference<String> blockedId = new AtomicReference<>();
CompletableFuture<String> fetchErrorId = new CompletableFuture<>();
try (Network network = new Network(driver.getWindowHandle(), driver)) {
String interceptId = network.addIntercept(
new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT)
.urlPattern(new UrlPattern()
.protocol("https")
.hostname("api.example.test")
.pathname("/recommendations")));
try {
network.onBeforeRequestSent(event -> {
if (!event.isBlocked() || !event.getIntercepts().contains(interceptId)) {
return;
}
URI url = URI.create(event.getRequest().getUrl());
if (!"api.example.test".equals(url.getHost())
|| !"/recommendations".equals(url.getPath())) {
network.continueRequest(
new org.openqa.selenium.bidi.network.ContinueRequestParameters(
event.getRequest().getRequestId()));
return;
}
String requestId = event.getRequest().getRequestId();
blockedId.set(requestId);
network.failRequest(requestId);
});
network.onFetchError(event -> {
if (event.getRequest().getRequestId().equals(blockedId.get())) {
fetchErrorId.complete(event.getRequest().getRequestId());
}
});
driver.findElement(By.id("load-recommendations")).click();
assertEquals(blockedId.get(), fetchErrorId.get(5, TimeUnit.SECONDS));
new WebDriverWait(driver, Duration.ofSeconds(5)).until(d ->
d.findElement(By.id("recommendation-status")).getText()
.equals("Recommendations unavailable"));
assertTrue(driver.findElement(By.id("account-summary")).isDisplayed());
} finally {
network.removeIntercept(interceptId);
}
}
} finally {
driver.quit();
}
}
}The defensive continueRequest branch matters even with an exact pattern. A future test fixture, redirect, browser interpretation, or pattern edit can widen the match. Returning from a blocked callback is never a safe rejection policy. The branch sends a phase-valid continuation and lets the test report the unexpected URL separately in a production harness.
Do not call classic WebDriver from the BiDi callback. A WebDriver command can wait on a page whose request is paused by that same callback, creating a self-owned deadlock. Parse immutable event data, record a small decision, send the terminal network command, and return. Perform DOM assertions on the test thread after the correlated protocol event arrives.
Correlate Request IDs Across the Event Lifecycle
URL matching identifies eligibility; request ID proves identity. Record the ID from BeforeRequestSent.getRequest().getRequestId() and carry it through the terminal decision, fetchError, evidence record, and assertion message. Also retain browsing context, redirect count, method, sanitized URL, intercept ID, and timestamp. Together they answer whether the expected request was blocked in the expected tab at the expected phase.
Redirects require special care. The event model includes redirectCount, and a redirect chain can yield repeated URLs or related request activity. Never correlate solely on the last path observed. A compact key such as session ID, browsing context, request ID, and redirect count is more useful than a global queue of URLs. The browsing-context mutation guide helps when popups, frames, or tab replacement changes the owner during a workflow.
Java example 2: keep bounded, redacted lifecycle evidence
import java.net.URI;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openqa.selenium.bidi.network.BeforeRequestSent;
import org.openqa.selenium.bidi.network.FetchError;
record RequestEvidence(
String phase,
String requestId,
String contextId,
long redirectCount,
String originAndPath) {}
final class NetworkEvidence {
private final List<RequestEvidence> entries = new CopyOnWriteArrayList<>();
void blocked(BeforeRequestSent event) {
if (event.isBlocked() && entries.size() < 20) {
entries.add(new RequestEvidence(
"beforeRequestSent:blocked",
event.getRequest().getRequestId(),
event.getBrowsingContextId(),
event.getRedirectCount(),
safeLocation(event.getRequest().getUrl())));
}
}
void failed(FetchError event) {
if (entries.size() < 20) {
entries.add(new RequestEvidence(
"fetchError",
event.getRequest().getRequestId(),
event.getBrowsingContextId(),
event.getRedirectCount(),
safeLocation(event.getRequest().getUrl())));
}
}
List<RequestEvidence> snapshot() {
return List.copyOf(entries);
}
private static String safeLocation(String rawUrl) {
URI uri = URI.create(rawUrl);
return uri.getScheme() + "://" + uri.getAuthority() + uri.getPath();
}
}This collector strips query strings because they often carry tokens, search terms, or customer identifiers. It also caps retained entries so a malformed page cannot flood CI memory. If request bodies or headers are needed, preserve only an allowlisted field, length, or digest. The response-body capture guide covers the stronger data-retention controls needed when payload evidence is unavoidable.
Build Negative Controls That Can Catch a False Pass
One blocked request and one fallback assertion are not enough. A broken test page could show the fallback before the action, the pattern could miss while the endpoint fails naturally, or a broad intercept could disable every API call. Negative controls make those false passes expensive. Start by asserting the normal page state before the trigger and running the same fixture once without the intercept to prove that recommendations normally load.
Add a nearby control request under the same host that must succeed while the target fails. For example, /account-summary should complete while /recommendations reaches fetchError. Record both request IDs and require exactly one blocked target. This catches a hostname-only pattern and validates that the browser, DNS, certificate, and application session are otherwise usable.
Test the inverse policy too. Trigger a request whose path resembles the target, such as /recommendations-health, and prove it is not blocked. Add a redirect case when the target can redirect, a second browsing context when the application uses popups, and concurrent identical requests when de-duplication matters. Decide whether one or all matching requests should fail before writing the callback.
The most important product negative control is absence of forbidden side effects. Blocking a save request should leave the server record unchanged, not merely display an error toast. Blocking an optional resource should not log the user out, erase form data, or submit a duplicate request. Query a test-only server probe or inspect a deterministic application state after the UI assertion. A network event proves transport behavior, while the product assertion proves the requirement.
For migration coverage, compare the normalized lifecycle rather than raw event payloads. The CDP and BiDi parity testing guide is useful when a mature CDP test already has known controls. Do not run CDP and BiDi interceptors against the same request in one session; two controllers can make ownership and terminal decisions impossible to interpret.
Diagnose a Page That Never Resumes
A deliberate failRequest ends in an error event. An accidental stall remains blocked. When the test times out, inspect the last lifecycle marker. No beforeRequestSent event means BiDi was not enabled, the listener was late, the wrong context was scoped, or the browser-provider path does not support the event. An unblocked event means the intercept pattern or phase did not match. A blocked event without a recorded decision means the callback returned, threw, or waited before resolving it.
A terminal command error has its own signature. no such request usually means the wrong ID, a duplicate decision, or a decision sent after another handler already resumed it. invalid argument often means a phase mismatch, such as trying to continue a response at beforeRequestSent. Preserve callback exceptions in an AtomicReference<Throwable> or CompletableFuture, then fail the test thread with that cause. Exceptions on an event thread should never disappear into driver logs.
Removing an intercept is not an emergency release command. The specification states that removal affects future requests or future phases, not a request already blocked by that intercept. Resolve every known blocked ID first, then remove the intercept. If the browser session is still responsive, a safe cleanup path may continue an eligible request unchanged. If policy requires the request to fail, fail it and wait only for a bounded terminal signal.
Check event ordering with the WebSocket subscription lifecycle guide. The expected chain for this case is listener active, intercept active, browser trigger, blocked event, failRequest command completion, correlated fetchError, UI fallback, intercept removal, listener closure. A generic page-load timeout should be a secondary symptom, not the only stored artifact.
Run the Checks Safely in Parallel CI
A Selenium BiDi block request intercept testing CI pipeline should begin with a small browser compatibility lane. Pin the Selenium Java artifact, browser build, driver or Grid image, operating system image, and remote provider capability. Fail setup quickly if the BiDi WebSocket cannot be established. Report that as capability or infrastructure failure, not as a product fallback failure.
Give each worker a unique case marker and server fixture. The marker can be a non-secret path segment or test data ID that appears in server logs and sanitized event evidence. Never share a driver or Network module across parallel tests. Intercept IDs and request IDs belong to one session; a static map can accidentally correlate another worker's event and produce a convincing false pass.
Use bounded waits for blocked event, terminal event, and product state separately. Their timings reveal whether delay sits in browser triggering, protocol handling, or application recovery. Do not solve a missing terminal command by increasing page-load timeout. Record first-attempt evidence even when policy allows one retry for a classified browser startup or Grid transport error. Product assertion failures and owned callback failures should not be retried into green.
Store a concise artifact: dependency versions, session and case IDs, sanitized target, context ID, intercept ID, request ID, redirect count, phase, decision, terminal event, control-request result, and cleanup result. Redact headers by default. The Selenium BiDi interview scenarios can help reviewers test whether an engineer can explain why each field changes the release decision rather than simply listing protocol terms.
Gate on missing correlation, unexpected extra matches, wrong-context events, absent negative controls, or cleanup failure in a reused environment. Track infrastructure instability separately, but do not discard it. A browser-provider combination that cannot reliably complete owned intercept lifecycles is not ready for a release gate, even if the application itself is healthy.
Protect Credentials, Data, and Shared Environments
Request blocking sounds harmless because it does not add data, yet the events can expose full URLs, cookies, headers, initiator details, and browsing contexts. Log an allowlist of fields. Strip query strings and fragments, hash non-public IDs when correlation is enough, and never attach Authorization, Cookie, Set-Cookie, or complete request bodies to routine CI artifacts.
Scope failure injection to a test environment or an explicitly approved synthetic route. A broad intercept against production can disrupt real user actions from a shared browser account. Keep test accounts minimally privileged and make any server-side side effect idempotent. The callback should never choose a target from untrusted page text or a loosely matched suffix.
Owned cleanup has three layers. removeIntercept(interceptId) removes the remote intercept for future matching. Closing Network clears the before-request listener used here, but Selenium Java 4.44.0 does not clear its onFetchError registration. Driver quit therefore ends the disposable session after both steps. Preserve the original assertion or callback failure and attach cleanup as a secondary error rather than replacing the root cause.
Review the pattern whenever routes change. A missing block can quietly convert a fault-injection case into an ordinary happy path. A widened block can turn unrelated failures into expected behavior. Treat the URL pattern, expected match count, and control request as one reviewed policy. That small discipline keeps network manipulation visible to security, product, and test-platform owners.
Frequently Asked Questions
What does a Selenium BiDi request intercept actually block?
A matching intercept pauses the request at its configured phase before normal browser processing continues. At beforeRequestSent, the handler must continue, modify, fail, or provide a response for that request. Removing the intercept affects future matches, but it does not release a request that is already blocked.
How do I prove that failRequest blocked the intended request?
Record the request ID from the blocked beforeRequestSent event, call failRequest with that ID, and wait for fetchError carrying the same ID. Then assert the application fallback and confirm a nearby control request completed. This chain proves targeting, protocol action, browser outcome, and product behavior.
Why can a blocked Selenium BiDi request make the test hang?
A blocked event stops request processing until one valid terminal command resolves it. A callback that throws, returns early, waits on WebDriver, or sends a command for the wrong phase leaves the browser paused. Bound every wait, capture callback failures, and resolve each owned blocked request exactly once.
Should a test block top-level navigation or a subresource?
Prefer a fetch, image, script, or other subresource when testing application fallback behavior because the page remains available for assertions. Block top-level navigation only when navigation failure is the requirement. In that case, expect driver.get to fail or time out and use protocol evidence as the primary assertion.
How should Selenium BiDi intercepts be cleaned up?
Keep the intercept ID in test scope, remove it in finally, and quit the driver after closing Network. In Selenium Java 4.44.0, Network.close does not clear listeners registered with onFetchError, so fetch-error cases should own a disposable session. Preserve cleanup errors without hiding the original failure, and never use a shared static intercept.
How should request blocking run in parallel CI?
Give each worker its own browser session, test route, request marker, evidence buffer, and intercept lifecycle. Pin Selenium, browser, and driver versions, then classify unsupported BiDi, callback, product, and infrastructure failures separately. A retry may confirm a transient platform issue, but it must retain the first attempt's evidence.
Practice the Failure Boundary in QABattle
Turn the design into a reviewable exercise: open the QABattle battle catalog, choose a browser workflow with an optional network dependency, and write one ordinary run, one exact blocked-request run, and one near-match control. Your submission should name the request ID correlation, the first product assertion, and the cleanup owner.
After that, use the event-correlation article to review the timeline as if it failed on a remote Grid. The strongest solution does not merely force an error. It proves that only the intended request failed, the application reached its specified fallback, unrelated traffic remained healthy, and no intercept survived the test.
// 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
What does a Selenium BiDi request intercept actually block?
A matching intercept pauses the request at its configured phase before normal browser processing continues. At beforeRequestSent, the handler must continue, modify, fail, or provide a response for that request. Removing the intercept affects future matches, but it does not release a request that is already blocked.
How do I prove that failRequest blocked the intended request?
Record the request ID from the blocked beforeRequestSent event, call failRequest with that ID, and wait for fetchError carrying the same ID. Then assert the application fallback and confirm a nearby control request completed. This chain proves targeting, protocol action, browser outcome, and product behavior.
Why can a blocked Selenium BiDi request make the test hang?
A blocked event stops request processing until one valid terminal command resolves it. A callback that throws, returns early, waits on WebDriver, or sends a command for the wrong phase leaves the browser paused. Bound every wait, capture callback failures, and resolve each owned blocked request exactly once.
Should a test block top-level navigation or a subresource?
Prefer a fetch, image, script, or other subresource when testing application fallback behavior because the page remains available for assertions. Block top-level navigation only when navigation failure is the requirement. In that case, expect driver.get to fail or time out and use protocol evidence as the primary assertion.
How should Selenium BiDi intercepts be cleaned up?
Keep the intercept ID in test scope, remove it in finally, and quit the driver after closing Network. In Selenium Java 4.44.0, Network.close does not clear listeners registered with onFetchError, so fetch-error cases should own a disposable session. Preserve cleanup errors without hiding the original failure, and never use a shared static intercept.
How should request blocking run in parallel CI?
Give each worker its own browser session, test route, request marker, evidence buffer, and intercept lifecycle. Pin Selenium, browser, and driver versions, then classify unsupported BiDi, callback, product, and infrastructure failures separately. A retry may confirm a transient platform issue, but it must retain the first attempt's evidence.
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
Intercept Requests and Supply Authentication with Selenium BiDi Network
Use Selenium BiDi network intercepts for scoped Basic or Digest authentication, request control, response evidence, cleanup, and safe diagnostics.
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
Enable WebDriver BiDi and Manage Event Subscriptions in Selenium
Enable Selenium WebDriver BiDi, verify WebSocket negotiation, scope event subscriptions, prevent races, and clean handlers up deterministically.
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.