Styrow.dev
Question 67 of 82
Selenium-Java Topic: Network Condition Simulation hard

Design a Selenium-Java Framework Component for Network Condition Simulation and Performance Impact Measurement.

Question

Design a Selenium-Java Framework Component for Network Condition Simulation and Performance Impact Measurement.

Answer

As a senior QA engineer, you’re tasked with enhancing the existing Selenium-Java test automation framework. The goal is to move beyond functional validation and incorporate performance and resilience testing under various network conditions.

The challenge is to design and implement a reusable framework component that allows test engineers to:

  1. Simulate diverse network conditions (e.g., slow 3G, fast 3G, offline) during test execution.
  2. Measure the impact of these conditions on critical user flows, specifically focusing on page load times and the loading of key resources.
  3. Integrate seamlessly into existing Selenium WebDriver tests without significant refactoring.
  4. Ensure proper cleanup of network conditions after each test scenario to prevent interference.

Describe your approach, including the key Selenium 4 features you would leverage, a conceptual code structure for the component, and how you would integrate it into a typical test.


This problem requires leveraging Selenium 4’s integration with the Chrome DevTools Protocol (CDP) to achieve network emulation, as standard Selenium WebDriver APIs do not directly expose network throttling capabilities.

Approach and Key Selenium 4 Features

The core of the solution lies in utilizing Selenium 4’s DevTools API, which provides a gateway to interact with browser-level functionalities exposed by CDP. Specifically, we’ll use the Network domain of CDP to emulateNetworkConditions.

Key Features:

  • HasDevTools Interface: The ChromeDriver class implements HasDevTools, allowing us to create and manage a DevTools session.
  • DevTools API: Provides methods like createSession() to establish a CDP session and send() to dispatch CDP commands.
  • CDP Network Domain: Offers commands such as emulateNetworkConditions to simulate network latency, download/upload throughput, and connection type. It also provides disable to reset conditions.
  • CDP Performance Domain (Optional but Recommended for “Hard”): Commands like enable() and getMetrics() can be used to collect detailed performance metrics like TaskDuration, LayoutDuration, ScriptingDuration, etc., for deeper analysis beyond simple page load times.

Component Design: NetworkEmulator Utility Class

We’ll design a NetworkEmulator utility class that encapsulates the CDP interactions, making it easy for test engineers to apply and reset network conditions.

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.devtools.vVERSION.network.Network;
import org.openqa.selenium.devtools.vVERSION.network.model.ConnectionType; // Adjust 'vVERSION' to your Selenium DevTools version
import org.openqa.selenium.devtools.vVERSION.network.model.EmulateNetworkConditions;
import org.openqa.selenium.WebDriver;

import java.util.Optional;
import java.util.function.Consumer;

public class NetworkEmulator {

    private final DevTools devTools;
    private final ThreadLocal<DevTools> devToolsSession = new ThreadLocal<>(); // To handle parallel execution

    // Predefined network conditions
    public enum NetworkPreset {
        OFFLINE(true, 0, 0, 0, ConnectionType.NONE),
        SLOW_3G(false, 2000, 50 * 1024, 20 * 1024, ConnectionType.CELLULAR3G), // Latency (ms), Download (bytes/s), Upload (bytes/s)
        FAST_3G(false, 750, 250 * 1024, 100 * 1024, ConnectionType.CELLULAR3G),
        BROADBAND(false, 50, 10 * 1024 * 1024, 5 * 1024 * 1024, ConnectionType.ETHERNET),
        NO_THROTTLING(false, 0, -1, -1, ConnectionType.NONE); // -1 means unlimited

        private final boolean offline;
        private final long latency;
        private final long downloadThroughput;
        private final long uploadThroughput;
        private final ConnectionType connectionType;

        NetworkPreset(boolean offline, long latency, long downloadThroughput, long uploadThroughput, ConnectionType connectionType) {
            this.offline = offline;
            this.latency = latency;
            this.downloadThroughput = downloadThroughput;
            this.uploadThroughput = uploadThroughput;
            this.connectionType = connectionType;
        }
    }

    /**
     * Initializes NetworkEmulator.
     * Assumes WebDriver instance is a ChromeDriver or other HasDevTools implementation.
     * @param driver The WebDriver instance (must be a ChromeDriver).
     * @throws IllegalArgumentException if driver is not an instance of HasDevTools.
     */
    public NetworkEmulator(WebDriver driver) {
        if (!(driver instanceof HasDevTools)) {
            throw new IllegalArgumentException("Driver must implement HasDevTools for CDP capabilities.");
        }
        this.devTools = ((HasDevTools) driver).getDevTools();
        devTools.createSession(); // Create a new DevTools session for this driver
        devToolsSession.set(devTools); // Store for thread safety
        devToolsSession.get().send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty())); // Enable network events
    }

    /**
     * Applies a predefined network condition preset.
     * @param preset The desired NetworkPreset.
     */
    public void emulateNetwork(NetworkPreset preset) {
        if (devToolsSession.get() == null) {
            throw new IllegalStateException("DevTools session not initialized. Call constructor first.");
        }
        devToolsSession.get().send(Network.emulateNetworkConditions(
                preset.offline,
                preset.latency,
                preset.downloadThroughput,
                preset.uploadThroughput,
                Optional.of(preset.connectionType)
        ));
        System.out.println("Network conditions set to: " + preset.name());
    }

    /**
     * Custom network condition emulation.
     * @param offline True if the network should be offline.
     * @param latencyMs The latency in milliseconds.
     * @param downloadKbps The download throughput in Kbps.
     * @param uploadKbps The upload throughput in Kbps.
     */
    public void emulateCustomNetwork(boolean offline, long latencyMs, long downloadKbps, long uploadKbps) {
        if (devToolsSession.get() == null) {
            throw new IllegalStateException("DevTools session not initialized. Call constructor first.");
        }
        // CDP expects bytes/second, convert Kbps to bytes/second
        long downloadBytesPerSecond = downloadKbps * 1024 / 8;
        long uploadBytesPerSecond = uploadKbps * 1024 / 8;

        devToolsSession.get().send(Network.emulateNetworkConditions(
                offline,
                latencyMs,
                downloadBytesPerSecond,
                uploadBytesPerSecond,
                Optional.of(ConnectionType.OTHER) // Using OTHER for custom conditions
        ));
        System.out.printf("Custom network conditions set: Offline=%b, Latency=%dms, Down=%dKbps, Up=%dKbps%n",
                offline, latencyMs, downloadKbps, uploadKbps);
    }

    /**
     * Resets network conditions to default (no throttling).
     */
    public void resetNetworkConditions() {
        if (devToolsSession.get() != null) {
            devToolsSession.get().send(Network.emulateNetworkConditions(
                    false, 0, -1, -1, Optional.of(ConnectionType.NONE)
            ));
            System.out.println("Network conditions reset to default.");
        }
    }

    /**
     * Closes the DevTools session.
     */
    public void closeDevToolsSession() {
        if (devToolsSession.get() != null) {
            devToolsSession.get().send(Network.disable()); // Disable network events
            devToolsSession.get().close();
            devToolsSession.remove();
            System.out.println("DevTools session closed.");
        }
    }

    /**
     * Attaches a listener to network requests for detailed analysis (e.g., failed requests).
     * @param consumer A consumer function that takes a RequestWillBeSent event.
     */
    public void addRequestListener(Consumer<Network.RequestWillBeSent> consumer) {
        if (devToolsSession.get() != null) {
            devToolsSession.get().addListener(Network.requestWillBeSent(), consumer);
        }
    }

    /**
     * Attaches a listener to network responses.
     * @param consumer A consumer function that takes a ResponseReceived event.
     */
    public void addResponseListener(Consumer<Network.ResponseReceived> consumer) {
        if (devToolsSession.get() != null) {
            devToolsSession.get().addListener(Network.responseReceived(), consumer);
        }
    }
}

Note: The vVERSION in org.openqa.selenium.devtools.vVERSION needs to be replaced with the actual Chrome DevTools Protocol version supported by your Selenium version (e.g., v120, v121). You can find this by checking your Selenium dependencies or your Chrome browser version.

Integration into a Typical Test Scenario

This NetworkEmulator class can be integrated into a test framework using JUnit’s @BeforeEach/@AfterEach or TestNG’s @BeforeMethod/@AfterMethod annotations for setup and teardown, or directly within test methods for specific scenarios.

Example Test Class:

import org.junit.jupiter.api.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.By;
import java.time.Duration;

public class PerformanceNetworkTest {

    private WebDriver driver;
    private NetworkEmulator networkEmulator;

    @BeforeEach
    public void setup() {
        // Assume ChromeDriver is configured (e.g., via WebDriverManager or system property)
        driver = new ChromeDriver();
        networkEmulator = new NetworkEmulator(driver);
        driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60)); // Set a reasonable page load timeout
    }

    @AfterEach
    public void teardown() {
        if (networkEmulator != null) {
            networkEmulator.resetNetworkConditions(); // Ensure conditions are reset
            networkEmulator.closeDevToolsSession();
        }
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    @DisplayName("Verify page load under slow 3G conditions")
    public void testPageLoadUnderSlow3G() {
        networkEmulator.emulateNetwork(NetworkEmulator.NetworkPreset.SLOW_3G);

        long startTime = System.currentTimeMillis();
        driver.get("https://www.example.com"); // Navigate to a target URL
        long endTime = System.currentTimeMillis();
        long pageLoadTime = endTime - startTime;

        System.out.println("Page loaded in " + pageLoadTime + " ms under Slow 3G.");

        // Assertions for page content or specific element visibility
        Assertions.assertTrue(driver.findElement(By.tagName("h1")).isDisplayed(), "Page title should be visible");
        // Add performance specific assertions, e.g., max pageLoadTime
        Assertions.assertTrue(pageLoadTime < 15000, "Page should load within 15 seconds under Slow 3G");
    }

    @Test
    @DisplayName("Verify application behavior when offline")
    public void testOfflineResilience() {
        networkEmulator.emulateNetwork(NetworkEmulator.NetworkPreset.OFFLINE);

        driver.get("https://www.example.com"); // Attempt to load a page while offline

        // Expect a browser error page or specific offline UI
        // This assertion will depend on the application's actual offline behavior
        // For a generic browser, it might show "This site can't be reached"
        String pageSource = driver.getPageSource();
        Assertions.assertTrue(pageSource.contains("ERR_INTERNET_DISCONNECTED") || pageSource.contains("no internet"),
                "Browser should indicate no internet connection.");

        // Try interacting with elements that might rely on cached data
        // Example: if there's a PWA with offline capabilities
    }

    @Test
    @DisplayName("Verify resource loading under custom network conditions (e.g., high latency)")
    public void testHighLatencyResourceLoading() {
        // Custom: 1000ms latency, 1Mbps download, 0.5Mbps upload
        networkEmulator.emulateCustomNetwork(false, 1000, 1024, 512);

        // Optional: Add listeners to catch failed requests due to high latency/throttling
        networkEmulator.addRequestListener(request -> {
            if (request.getRequest().getUrl().contains("api.example.com")) {
                System.out.println("API Request will be sent: " + request.getRequest().getUrl());
            }
        });
        networkEmulator.addResponseListener(response -> {
            if (response.getResponse().getStatus() >= 400) {
                System.err.println("Failed response: " + response.getResponse().getUrl() + " Status: " + response.getResponse().getStatus());
            }
        });

        long startTime = System.currentTimeMillis();
        driver.get("https://www.another-example.com/data-heavy-page");
        long endTime = System.currentTimeMillis();
        long pageLoadTime = endTime - startTime;

        System.out.println("Data-heavy page loaded in " + pageLoadTime + " ms under custom high latency.");
        Assertions.assertTrue(pageLoadTime < 25000, "Data-heavy page should load within 25 seconds under high latency.");
        // Further assertions about specific image/script loaded states, if applicable
    }
}

Architectural Considerations

  1. Reusability: The NetworkEmulator class is designed as a reusable component, making it easy to apply network conditions across different tests.
  2. Thread Safety: The ThreadLocal<DevTools> ensures that each WebDriver instance (and thus each test thread in parallel execution) gets its own DevTools session, preventing concurrency issues.
  3. Cross-Browser Compatibility: CDP is primarily a feature of Chromium-based browsers (Chrome, Edge). This solution will not work directly with Firefox or Safari. For cross-browser network simulation, external tools (like proxy servers such as Browsermob Proxy or integrated proxy features in frameworks like Playwright) would be required. The question specifically asked for Selenium-Java framework design, so CDP is the most direct solution.
  4. Reporting: Integrate measured page load times and any collected network metrics into test reports (e.g., Allure, ExtentReports) to provide comprehensive performance insights.
  5. Test Stability: Be mindful of increasing test execution times when simulating slow networks. Set appropriate timeouts and expectations to avoid flaky tests.
  6. Granularity: The emulateNetworkConditions command applies to the entire browser context. For more fine-grained control (e.g., throttling specific resources), more complex CDP listeners and conditional handling would be needed.
  7. Version Management: The CDP API (vVERSION) is tied to specific Chrome versions. Ensure your Selenium DevTools dependency version aligns with your Chrome browser version to avoid compatibility issues.

By implementing this NetworkEmulator component, the framework gains significant capabilities for validating application performance and resilience under realistic and challenging network environments, moving testing efforts closer to real-world user experiences.


📲 Practice Offline on Mobile: Download the free QA Automation & SDET Prep app on Google Play & App Store.