Resource library

QA How-To

Selenium BiDi Emulate Network Conditions Tutorial

Use this selenium bidi emulate network conditions tutorial to test offline behavior, recovery, context scope, safe cleanup, and reliable UI assertions.

20 min read | 2,728 words

TL;DR

Enable WebDriver BiDi, obtain the current browsing context ID, call driver.emulation.set_network_conditions(offline=True, contexts=[context_id]), and verify the application's offline UI. Call it again with offline=False in cleanup, because the BiDi command supports offline state but does not define latency or bandwidth values.

Key Takeaways

  • WebDriver BiDi network conditions support offline mode in the current standard, not arbitrary latency or throughput profiles.
  • Enable BiDi before session creation with the webSocketUrl capability exposed by Selenium options.
  • Apply offline mode to a top-level browsing context so unrelated contexts can remain online.
  • Assert the product's visible offline state instead of merely asserting that a protocol command returned.
  • Clear the override in a finally block and verify recovery before releasing the driver.
  • Keep Selenium's emulation calls behind a small adapter so application tests express intent instead of protocol details.
  • Use request interception or a controlled proxy when the requirement is HTTP failure, latency, or bandwidth throttling.

A selenium bidi emulate network conditions tutorial should produce a meaningful resilience test, not just toggle a browser flag. In this guide, you will put one browsing context offline with WebDriver BiDi, verify a visible fallback, restore networking, and prove that Retry succeeds.

This tutorial is a focused companion to the Selenium BiDi Automation Complete Guide. It uses Selenium Python 4.43.0, the documented driver.emulation.set_network_conditions API, and a local deterministic test page so you can run the example without depending on a public site.

The important 2026 boundary is precise: the WebDriver BiDi command currently models an offline condition. It does not accept latency, download throughput, upload throughput, or a named 3G profile. Those controls still require a browser-specific CDP adapter, a proxy, or test-server behavior. This tutorial stays on the standards-based BiDi path and does not invent unsupported parameters.

What You Will Build

You will build a pytest test that:

  • starts a local HTTP application with a small status API;
  • creates a BiDi-enabled Firefox session;
  • identifies the active top-level browsing context;
  • applies offline mode only to that context;
  • verifies an accessible offline message after a user action;
  • clears the condition and verifies a successful retry;
  • guarantees cleanup even when an assertion fails.

The finished test exercises a customer-observable recovery flow. It does not treat elapsed time as proof of offline behavior, and it does not rely on a third-party endpoint whose availability could make CI flaky.

Prerequisites

Use Python 3.11 or newer, Selenium 4.43.0, pytest 8 or newer, and a current Firefox installation. Selenium Manager normally resolves the matching driver. The BiDi network conditions implementation varies by browser release, so verify the exact browser image used in CI instead of assuming every remote provider exposes the same command.

Create and activate an isolated environment:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install "selenium==4.43.0" "pytest>=8,<9"
python -c "import selenium; print(selenium.__version__)"

Windows PowerShell users can activate with .venv\Scripts\Activate.ps1. Pinning Selenium keeps an unplanned binding upgrade from changing your tested infrastructure.

Verification: The final command prints 4.43.0. Also run firefox --version or open Firefox's About dialog and record the browser version in CI diagnostics.

Step 1: Create a Deterministic Offline Demo Application

Create test_offline_bidi.py. Start with a tiny local server that serves both the page and /api/status. The browser page calls the API only after you press Check status, which gives the test a clean action to perform before and after network emulation.

import json
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

import pytest

PAGE = b"""<!doctype html>
<html lang='en'>
  <meta charset='utf-8'>
  <title>Connectivity demo</title>
  <body>
    <button id='check' type='button'>Check status</button>
    <p id='result' role='status'>Ready</p>
    <script>
      document.querySelector('#check').addEventListener('click', async () => {
        const result = document.querySelector('#result');
        result.textContent = 'Checking';
        try {
          const response = await fetch('/api/status', {cache: 'no-store'});
          if (!response.ok) throw new Error(`HTTP ${response.status}`);
          const payload = await response.json();
          result.textContent = payload.message;
        } catch (error) {
          result.textContent = 'You are offline. Check your connection and retry.';
        }
      });
    </script>
  </body>
</html>"""

class DemoHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/api/status':
            body = json.dumps({'message': 'Service available'}).encode()
            content_type = 'application/json'
        else:
            body = PAGE
            content_type = 'text/html; charset=utf-8'
        self.send_response(200)
        self.send_header('Content-Type', content_type)
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, format, *args):
        pass

@pytest.fixture
def demo_url():
    server = ThreadingHTTPServer(('127.0.0.1', 0), DemoHandler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    try:
        yield f'http://127.0.0.1:{server.server_port}'
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=2)

The fixture asks the operating system for a free port. The server stays inside the test process and returns a fixed response, so DNS, public certificates, rate limits, and external outages cannot affect the result. cache: 'no-store' prevents a successful response from hiding the offline condition.

Verification: Temporarily add print(demo_url) in a small test, run pytest with -s, and open the printed URL while the fixture is active. The page should show a button and the initial text Ready. Remove the temporary print afterward.

Step 2: Start a BiDi-Enabled Selenium Session

Add imports and a function-scoped driver fixture below the server fixture. Selenium requires the WebSocket URL capability during session creation. The Python convenience property is options.enable_bidi = True.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

@pytest.fixture
def driver():
    options = webdriver.FirefoxOptions()
    options.enable_bidi = True
    browser = webdriver.Firefox(options=options)
    browser.set_window_size(1280, 900)
    try:
        yield browser
    finally:
        browser.quit()

Do not set enable_bidi after webdriver.Firefox() returns. Capabilities are negotiated when the session starts. A fixture also gives every test a clean browser, which prevents an offline override, open tab, cookie, or cache entry from leaking into another test.

On Grid, create the same options object and pass it to webdriver.Remote. Confirm that the returned session capabilities contain a WebSocket URL and that the Grid or cloud vendor supports the BiDi emulation command. A WebSocket URL alone proves transport negotiation, not support for every module command.

Verification: Add assert driver.capabilities.get('webSocketUrl') to a temporary smoke test. The assertion should pass and the value should begin with ws:// or wss://. Never print a remote WebSocket URL into a public CI log because it can contain session-specific connection data.

Step 3: Access the BiDi Emulation Module

Selenium's Python driver exposes the synchronous driver.emulation module after BiDi is enabled. That module sends emulation.setNetworkConditions over the negotiated WebSocket. Add a small adapter so business tests say what condition they need without importing protocol implementation classes.

class OfflineController:
    def __init__(self, driver):
        self._emulation = driver.emulation

    def set_offline(self, context_id: str, offline: bool) -> None:
        self._emulation.set_network_conditions(
            offline=offline,
            contexts=[context_id],
        )

Use driver.emulation, not driver.bidi_connection. The latter is an async context manager retained for a different lower-level connection workflow and is not the Emulation constructor argument in this synchronous test. Accessing driver.emulation starts Selenium's BiDi WebSocket connection lazily and returns the module object backed by that connection.

Pass either contexts or user_contexts, never both. This tutorial uses a browsing context because the test owns one tab and wants narrow scope. User-context scope is useful when all tabs in a browser profile must share the condition.

Verification: After creating the driver, run assert callable(driver.emulation.set_network_conditions). If access fails, confirm the pinned Selenium version, verify the webSocketUrl capability, and check that another package named selenium is not shadowing the official binding.

Step 4: Get the Active Browsing Context ID

The network condition needs a BiDi browsing context ID. Selenium WebDriver window handles identify top-level browsing contexts, and the current window handle is the correct context value for this single-tab flow. Navigate before applying offline mode so the page and its JavaScript are already loaded.

def open_demo(driver, demo_url: str) -> str:
    driver.get(demo_url)
    context_id = driver.current_window_handle
    assert context_id
    assert driver.find_element(By.ID, 'result').text == 'Ready'
    return context_id

Apply the override after navigation. If you set offline mode first, the document itself cannot load, and you end up testing the browser's navigation error page instead of your application's resilience UI. That is a valid but different requirement.

For a multi-tab test, capture the handle immediately after switching to the target tab. Do not assume that the first item in window_handles remains the active application tab. Frames are not top-level browsing contexts for this command's contexts list. Scope the condition to the top-level tab that owns the fetch.

Verification: The function returns a nonempty ID and confirms Ready. Print only the length or a redacted suffix if you need diagnostics. The raw identifier is usually harmless locally, but minimizing session identifiers in logs is a sound default.

Step 5: Selenium BiDi Emulate Network Conditions Tutorial Test

Now write the central test. Put the context offline, click the button, and wait for the status region to show the application's fallback. The assertion proves the feature requirement, while the successful protocol call alone proves only that the browser accepted a command.

def status_is(expected: str):
    def condition(driver):
        element = driver.find_element(By.ID, 'result')
        return element if element.text == expected else False
    return condition

def test_offline_message_and_recovery(driver, demo_url):
    context_id = open_demo(driver, demo_url)
    network = OfflineController(driver)

    try:
        network.set_offline(context_id, True)
        driver.find_element(By.ID, 'check').click()

        offline = WebDriverWait(driver, 5).until(
            status_is('You are offline. Check your connection and retry.')
        )
        assert offline.get_attribute('role') == 'status'
    finally:
        network.set_offline(context_id, False)

    driver.find_element(By.ID, 'check').click()
    online = WebDriverWait(driver, 5).until(
        status_is('Service available')
    )
    assert online.is_displayed()

The click starts a new uncached fetch. Under offline conditions, fetch rejects and the application displays its fallback. WebDriverWait reacts to product state and avoids a fixed sleep. The finally block restores connectivity even if the offline assertion times out.

Verification: Run pytest -q test_offline_bidi.py. Expect one passing test. To prove the assertion has value, temporarily change the expected offline text by one word and confirm that pytest times out with a failure, then restore the original text.

Step 6: Make Cleanup Failure-Safe

The first test restores networking, but robust infrastructure should also handle a failure while clearing the condition. Track whether the override was applied, retain the original assertion failure, and always let the driver fixture quit the session. For most suites, the simple finally block is enough. Use a context manager when several tests need the same lifecycle.

from contextlib import contextmanager

@contextmanager
def offline_context(driver, context_id: str):
    controller = OfflineController(driver)
    controller.set_offline(context_id, True)
    try:
        yield
    finally:
        controller.set_offline(context_id, False)

def test_offline_with_context_manager(driver, demo_url):
    context_id = open_demo(driver, demo_url)

    with offline_context(driver, context_id):
        driver.find_element(By.ID, 'check').click()
        WebDriverWait(driver, 5).until(
            status_is('You are offline. Check your connection and retry.')
        )

    driver.find_element(By.ID, 'check').click()
    WebDriverWait(driver, 5).until(status_is('Service available'))

Do not rely only on driver.quit() as cleanup when the same session performs a recovery assertion. Clearing the condition is part of what the test verifies. Quitting remains the final containment boundary if the clear command itself fails.

If your framework attaches diagnostics in teardown, capture the visible status and browser version, not secrets or the entire page source by default. An offline test should not create a second failure that hides the original assertion. Configure teardown reporting to preserve both errors when possible.

Verification: Place raise AssertionError('intentional') inside the with block, run the test, and observe that the assertion is reported rather than a hung browser. Remove the line, then verify the online request succeeds after the block.

Step 7: Choose the Right Network Failure Model

Offline mode is one failure model, not a universal substitute for slow or erroneous services. Select the mechanism that matches the product requirement.

Requirement Best mechanism What to assert
Browser has no network BiDi set_network_conditions(offline=True) Offline UI and recovery
One request loses transport BiDi request interception plus fail request Endpoint-specific fallback
Server returns 500 or 429 Controlled test server or response interception Status-specific message or retry
Fixed server delay Controlled test server or proxy Loading state and eventual result
Bandwidth or latency profile Verified CDP adapter or network proxy Product behavior, not exact timing
Performance under load Load and performance tooling Service-level objectives and resource data

Current WebDriver BiDi network conditions accept the offline type. Passing invented keys such as latency, downloadThroughput, or connectionType to this BiDi command is incorrect. CDP's similarly named command has historically accepted richer Chromium-specific settings, which is why examples are often confused. Protocol names and capability boundaries matter more than the word BiDi in a blog title.

If you need one endpoint to fail while the rest of the application remains connected, follow the Selenium BiDi request interception tutorial for Python. It produces a more precise test than taking the entire tab offline.

Verification: Write the requirement in one sentence before selecting the mechanism. If the sentence says HTTP 500, latency, or one URL, an all-network offline override is not the correct implementation.

Step 8: Run the Selenium BiDi Emulate Network Conditions Tutorial in CI

Treat browser and binding support as an explicit test capability. Pin Selenium, record the browser version, run one BiDi emulation smoke test, and then run the application scenarios. A remote provider may negotiate BiDi while rejecting an emulation command that its browser or bridge does not yet implement.

A minimal CI command is:

python -m pip install "selenium==4.43.0" "pytest>=8,<9"
pytest -q --junitxml=artifacts/pytest.xml test_offline_bidi.py

Do not silently catch unsupported operation and mark the scenario passed. Either fail a required browser lane, or skip it with a documented capability policy and run equivalent resilience coverage through a controlled server or proxy. A skip should state the browser name, version, Selenium version, and missing command without exposing the WebSocket URL.

Parallel workers need separate WebDriver sessions. Never share one OfflineController or driver across tests, because context IDs and protocol connections belong to their originating session. Keep the local server fixture function-scoped or session-scoped according to your runner, but keep browser state test-scoped.

Verification: Run the same command used by CI in a clean environment. Confirm that the JUnit report contains the test, a pass or policy-approved skip, and useful version diagnostics. Then run the test twice in sequence to prove that the first run leaves no network state behind.

Troubleshooting

AttributeError for emulation -> Confirm options.enable_bidi = True was set before creating the driver and that Selenium 4.43.0 is installed in the active environment. Verify webSocketUrl, then print selenium.__file__ if you suspect a shadowing module.

unsupported operation or unknown command -> The browser, driver, Grid, or vendor bridge does not implement emulation.setNetworkConditions. Record the versions, verify support in a local browser, and use a controlled alternative for that matrix lane. Do not turn a required assertion into an unconditional pass.

The page fails during initial navigation -> Apply offline mode after driver.get() and after the page's script is loaded. Going offline before navigation tests the browser error page, not the application's fetch failure UI.

The API still succeeds while offline -> Disable application caching for the selected call, trigger a new request after applying the override, and confirm you scoped the condition to the active top-level context. Service workers can also serve cached data, so use a clean profile and a deterministic route.

The next test is also offline -> Put the clear call in finally, use a function-scoped browser fixture, and quit the session even when cleanup fails. Avoid shared sessions for condition-changing tests.

The offline assertion is flaky -> Wait for an explicit DOM state with WebDriverWait, not a fixed sleep. Ensure the action triggers exactly one new request and that no third-party dependency controls the expected UI.

Where To Go Next

Return to the complete Selenium BiDi automation guide to place emulation alongside logging, authentication, interception, and browser-context workflows.

Then deepen the same test architecture with these focused guides:

Keep each helper narrow. Emulation changes browser conditions, interception changes selected traffic, and logging observes events. Separating those responsibilities makes failures readable and future migration easier.

Interview Questions and Answers

Q: What does WebDriver BiDi network emulation support in 2026?

The standard emulation.setNetworkConditions command models offline conditions and can scope them to browsing contexts or user contexts. It does not define arbitrary latency or throughput fields. I use CDP or a controlled proxy only when a browser-specific requirement needs those richer controls.

Q: Why enable BiDi before creating the driver?

Selenium requests the webSocketUrl capability during new-session negotiation. That WebSocket carries BiDi commands and events. Setting an option after session creation cannot retroactively create the negotiated connection.

Q: Why apply offline mode after navigation?

The application document and JavaScript must load before they can render their own offline fallback. Applying the condition first usually tests a browser navigation error instead. I choose the order based on whether the requirement concerns initial navigation or an in-app request.

Q: How do you prevent network state from leaking between tests?

I clear the override in a finally block, verify an online recovery action, and use a function-scoped driver. The fixture quits the browser as a final containment boundary. I never share the protocol controller across sessions.

Q: Should an offline test assert a request duration?

No. Offline fetch behavior and application scheduling vary, so an elapsed-time threshold is brittle. I wait for the visible fallback or a specific network event that directly represents the requirement.

Q: When is request interception better than offline emulation?

Interception is better when one endpoint should fail while the rest of the page stays connected, or when the test needs a selected transport failure. Offline emulation is appropriate when the whole browsing context must behave as disconnected.

Q: Why wrap Selenium's Emulation class in an adapter?

A small adapter limits dependency churn to one place, gives the suite domain-specific names, and supports a focused compatibility test without bypassing Selenium's public driver property.

Best Practices

  • Translate the requirement into a failure model before selecting a protocol command.
  • Load the application before going offline when testing in-page recovery.
  • Use one active top-level context ID and avoid overly broad user-context scope.
  • Trigger an uncached request after applying the condition.
  • Assert an accessible, visible product state rather than command completion.
  • Clear the override in finally and prove a subsequent online action succeeds.
  • Pin Selenium and keep the public driver.emulation call behind a focused adapter.
  • Record browser support explicitly in Grid and cloud-provider matrices.
  • Keep WebSocket URLs, credentials, headers, and response bodies out of routine logs.
  • Use interception, a test server, CDP, a proxy, or load tooling when offline mode does not match the requirement.

Conclusion

This selenium bidi emulate network conditions tutorial gives you a standards-based offline test with a complete lifecycle: negotiate BiDi, identify the target context, apply offline mode, assert the application's fallback, clear the condition, and verify recovery. The recovery assertion and failure-safe cleanup are as important as the emulation command itself.

Start with the deterministic local example, then replace its page and status text with your product flow. Keep the adapter pinned and isolated, run a support smoke test in every browser lane, and choose a different network-control mechanism whenever the requirement is latency, bandwidth, an HTTP response, or one failed endpoint.

Interview Questions and Answers

How would you test offline behavior with Selenium WebDriver BiDi?

I enable BiDi during session creation, load the application, and capture the active top-level browsing context ID. I apply offline conditions to that context, trigger a new uncached request, and wait for the visible offline state. In a finally block I clear the condition, retry, and assert the online result.

What network condition values does WebDriver BiDi support in 2026?

The current `emulation.setNetworkConditions` command supports an offline condition and clearing that override. It can scope the condition to browsing contexts or user contexts. Latency and throughput values belong to other mechanisms such as CDP or a proxy, not this BiDi command.

Why is a visible UI assertion necessary after setting offline mode?

A successful protocol command only proves that the browser accepted the setting. The product requirement usually concerns a fallback message, disabled control, retry action, or preserved data. I assert that observable state to connect infrastructure behavior to customer value.

How do you make a network emulation test safe for parallel execution?

Each worker gets its own driver, BiDi connection, context ID, and controller. I avoid global state and shared sessions, clear the condition in finally, and quit the browser through a fixture. The local test service can be shared only if it is stateless and concurrency-safe.

When would you use BiDi request interception instead of offline emulation?

I use interception when one selected request must fail while the rest of the application stays online. I use offline emulation when the entire context should have no network. The failure model should match the acceptance criterion, because an HTTP error, transport failure, delay, and offline state are not equivalent.

How would you handle a browser that rejects the BiDi emulation command?

I capture the browser, driver, Selenium, and Grid versions and reproduce with a focused smoke test. If the browser lane requires the feature, the lane fails. Otherwise I apply an explicit skip policy and cover the resilience requirement through a supported test server, proxy, or interception mechanism.

Why isolate Selenium's Python Emulation API behind an adapter?

An adapter contains the Selenium dependency, keeps protocol language out of business tests, and gives one place to update after a binding change. It can delegate directly to Selenium's public `driver.emulation` property, while a small compatibility test verifies the pinned Selenium and browser versions.

Frequently Asked Questions

Can Selenium BiDi emulate slow 3G network conditions?

The 2026 WebDriver BiDi network conditions command defines offline mode, not latency, throughput, or named connection profiles. Use a verified Chromium CDP adapter or a controlled network proxy for slow-network behavior, and keep that browser-specific choice isolated.

How do I enable WebDriver BiDi in Selenium Python?

Set `options.enable_bidi = True` before creating the WebDriver session. Selenium then requests a WebSocket URL during session negotiation and exposes the connection to supported BiDi features.

How do I set Selenium WebDriver BiDi to offline mode?

Call `driver.emulation.set_network_conditions(offline=True, contexts=[context_id])`. Use the current top-level window handle as the context ID for a single-tab test, then clear the condition in cleanup.

How do I restore network access after a Selenium offline test?

Call `set_network_conditions(offline=False, contexts=[context_id])` in a `finally` block. Then trigger a fresh request and assert the online result so the test proves both cleanup and product recovery.

Does Selenium BiDi network emulation work in every browser?

Support depends on the Selenium binding, browser, driver, Grid, and remote vendor versions. Negotiate BiDi, run a focused capability smoke test in each browser lane, and define whether missing support should fail or produce a documented skip.

Why does my offline Selenium test still receive a response?

The response may come from browser cache, a service worker, or a request started before the condition was applied. Use a clean profile, disable caching for the selected request, apply offline mode before the user action, and verify the active context ID.

Is Selenium BiDi the same as Chrome DevTools Protocol?

No. WebDriver BiDi is a standards-oriented cross-browser protocol, while CDP is Chromium's debugging protocol. Similar command names can have different parameters, so do not copy CDP latency fields into a BiDi command.

Related Guides