Skip to main content
SeleniumDecoded

Mock an API Response

Return a canned JSON payload for one endpoint so the UI shows empty, error or edge-case states without changing the backend.

Medium Verified against Selenium 4.48.0

Problem

The “no orders yet” empty state and the “service unavailable” error state are impossible to reach on staging, where the test account always has orders and the API is always up.

Solution

Intercept the endpoint and provide the response yourself. The request never reaches the server.

Provide a response for one endpoint
Selenium 4 Medium
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.*;
public static void mockJson(WebDriver driver, String pathname, int status, String json) {
Network network = new Network(driver);
network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT)
.urlPatterns(List.of(new UrlPattern().pathname(pathname))));
network.onBeforeRequestSent(e -> {
if (!e.isBlocked()) return;
network.provideResponse(new ProvideResponseParameters(e.getRequest().getRequestId())
.statusCode(status)
.headers(List.of(new Header("Content-Type", new BytesValue(BytesValue.Type.STRING, "application/json"))))
.body(new BytesValue(BytesValue.Type.STRING, json)));
});
}
mockJson(driver, "/api/orders", 200, "{\"orders\":[],\"total\":0}");
driver.get("https://app.example.com/orders");
assertTrue(driver.findElement(By.cssSelector("[data-testid='empty-state']")).isDisplayed());
mockJson(driver, "/api/orders", 503, "{\"error\":\"maintenance\"}");
driver.navigate().refresh();
assertTrue(driver.findElement(By.cssSelector("[data-testid='error-banner']")).isDisplayed());
def mock_json(driver, pattern, status, body):
driver.network.add_request_handler(
[pattern],
lambda request: request.provide_response(
status_code=status, headers={"Content-Type": "application/json"}, body=body))
mock_json(driver, "**/api/orders", 200, '{"orders": [], "total": 0}')
driver.get("https://app.example.com/orders")
assert driver.find_element(By.CSS_SELECTOR, "[data-testid='empty-state']").is_displayed()
driver.network.clear_request_handlers()
mock_json(driver, "**/api/orders", 503, '{"error": "maintenance"}')
driver.refresh()
assert driver.find_element(By.CSS_SELECTOR, "[data-testid='error-banner']").is_displayed()
const { ProvideResponseParameters } = require('selenium-webdriver/bidi/provideResponseParameters');
const { Header, BytesValue } = require('selenium-webdriver/bidi/networkTypes');
async function mockJson(driver, urlPattern, status, json) {
const network = await Network(driver);
await network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT).urlStringPattern(urlPattern));
await network.beforeRequestSent(async (e) => {
if (!e.isBlocked) return;
await network.provideResponse(new ProvideResponseParameters(e.request.request)
.statusCode(status)
.headers([new Header('Content-Type', new BytesValue(BytesValue.Type.STRING, 'application/json'))])
.body(new BytesValue(BytesValue.Type.STRING, json)));
});
}
await mockJson(driver, 'https://app.example.com/api/orders', 200, '{"orders":[],"total":0}');
await driver.get('https://app.example.com/orders');
public static async Task MockJsonAsync(IWebDriver driver, string pathname, int status, string json)
{
var bidi = await driver.AsBiDiAsync();
await bidi.Network.InterceptRequestAsync(async e =>
await e.Request.ProvideResponseAsync(new()
{
StatusCode = (uint)status,
Headers = [new Header("Content-Type", "application/json")],
Body = json,
}),
new() { UrlPatterns = [new UrlPatternPattern { Pathname = pathname }] });
}
await MockJsonAsync(driver, "/api/orders", 200, "{\"orders\":[],\"total\":0}");
driver.Navigate().GoToUrl("https://app.example.com/orders");
Assert.That(driver.FindElement(By.CssSelector("[data-testid='empty-state']")).Displayed);

Why It Works

provideResponse short-circuits the request at the browser’s network layer with the status, headers and body you supply. The app’s fetch or XHR resolves exactly as if the server had answered, so every code path (loading, empty, error) is reachable deterministically.

Gotchas

  • Record a real response first and base the mock on it; a mock that drifts from the API contract tests nothing.
  • Include CORS headers if the app runs on a different origin than the API and checks them.
  • Remove or replace intercepts between scenarios (removeIntercept, clear_request_handlers) or the first mock wins forever.
  • Keep at least one test per endpoint unmocked against the real backend.

Learn the theory