ScreenshotNeo

BlogHow-to

Automated Browser Compatibility Testing with JUnit and Selenium

Build a JUnit 5 and Selenium WebDriver test matrix for the browsers and platforms your product supports, then run it locally or through Selenium Grid.

By the ScreenshotNeo team4 October 202611 min read

To test browser compatibility with JUnit and Selenium, define the browser and platform combinations your product promises to support, then run the same user journeys against each combination using a separate Selenium WebDriver session. JUnit Jupiter organizes and reports the test invocations; WebDriver controls the browsers. Start with local sessions for a small matrix, and use Selenium Grid when you need remote machines, more browser versions, or parallel execution.

A passing run only covers the browsers, versions, platforms, and workflows that actually ran. Selenium gives tests a common automation interface, but it does not make browser behavior identical.

1. Define the browser compatibility matrix

Choose environments from your product’s support commitments and audience rather than a universal browser-count rule. Record the combinations you intend to check before configuring the test harness.

Decision Questions to answer
Browsers Which browsers does the product claim to support? Selenium documents browser-specific functionality for Chrome, Edge, Firefox, Internet Explorer, and Safari.
Versions Does the support commitment include only current stable releases, or older supported versions too? Testing multiple versions requires environments that can provide them.
Operating systems Is support limited to the development OS, or does the product promise behavior on other desktop platforms?
Workflows Which high-value user journeys, layouts, forms, or browser-sensitive features need coverage in each environment?
Execution Can local machines supply the required browsers, or is remote execution through Grid more practical?
Repeatability Will you pin browser/environment versions for stable comparisons, or allow environment versions to advance? Pinning improves repeatability but requires updates.

A small matrix can be a useful starting point, but label it as the tested subset of your support promise. Do not treat a handful of browser runs as universal compatibility evidence.

2. Set up a JUnit 5 and Selenium project

The following Maven example uses JUnit Jupiter parameterized tests and Selenium Java. It starts a local Chrome, Firefox, or Edge session based on a system property, executes the same scenario for each parameter, and closes the driver after each invocation. Use browser and driver versions compatible with your installed browser; Selenium Manager can assist with driver management in current Selenium releases.

<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>browser-matrix</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <junit.version>5.13.1</junit.version>
    <selenium.version>4.35.0</selenium.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>${selenium.version}</version>
;    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.3</version>
        <configuration>
          <useModulePath>false</useModulePath>
;      </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Save as src/test/java/example/BrowserCompatibilityTest.java:

package example;

import java.time.Duration;
import java.util.Locale;
import java.util.stream.Stream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import static org.junit.jupiter.api.Assertions.assertEquals;

class BrowserCompatibilityTest {
    private WebDriver driver;

    static Stream<String> browsers() {
        String configured = System.getProperty("browsers", "chrome,firefox");
        return Stream.of(configured.split(","))
            .map(String::trim)
            .filter(name -> !name.isEmpty());
    }

    @ParameterizedTest(name = "{0}: home page exposes the expected title")
    @MethodSource("browsers")
    void homePageTitleMatches(String browser) {
        driver = createDriver(browser);
        driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
        driver.get(System.getProperty("baseUrl", "https://example.com"));

        new WebDriverWait(driver, Duration.ofSeconds(10))
            .until(ExpectedConditions.titleIs("Example Domain"));
        assertEquals("Example Domain", driver.getTitle());
    }

    private WebDriver createDriver(String browser) {
        return switch (browser.toLowerCase(Locale.ROOT)) {
            case "chrome" -> new ChromeDriver();
            case "firefox" -> new FirefoxDriver();
            case "edge" -> new EdgeDriver();
            default -> throw new IllegalArgumentException(
                "Unsupported browser: " + browser + "; use chrome, firefox, or edge");
        };
    }

    @AfterEach
    void closeDriver() {
        if (driver != null) {
            driver.quit();
            driver = null;
        }
    }
}

Remove the unused By and WebElement imports if your compiler flags warnings. Run one or more browsers with:

mvn test -Dbrowsers=chrome,firefox,edge -DbaseUrl=https://example.com

The property lists browser names supported by this example. The example tests one simple journey; replace its URL and assertion with a representative workflow from your application. For real user journeys, locate controls by stable accessible names or application-owned attributes, wait for observable conditions, and assert the result users depend on.

3. Write stable, reusable user journey tests

JUnit Jupiter parameterized tests run a method for each supplied argument set, with the normal per-test lifecycle for each invocation. That makes a browser name or environment configuration a natural input. JUnit does not create browser sessions or provide cross-browser behavior; the test setup does that.

  1. Keep test intent constant. Assert the same user-visible outcome across browsers. Avoid browser-specific expectations unless the product intentionally has a documented difference.
  2. Start a fresh session per invocation. This prevents cookies, local storage, or open windows from one parameter from contaminating another.
  3. Wait for conditions, not arbitrary timing. Use explicit waits for elements, navigation, or application state. Fixed sleeps make suites slower and can still be too short under load.
  4. Use resilient selectors. Prefer labels, accessible roles/names, or stable test attributes over brittle positional CSS or generated IDs.
  5. Capture useful failure evidence. Include the browser, version, platform, test name, and relevant logs or screenshots in CI artifacts where available.
  6. Close sessions even when assertions fail. JUnit lifecycle callbacks such as @AfterEach are appropriate for cleanup.

When matrix inputs grow beyond a browser name, represent an environment as a small configuration object containing browser, version, platform, and any relevant options. Keep the test scenario independent from environment construction so that local and remote drivers can share the same checks.

4. Expand to remote execution with Selenium Grid

Selenium Grid routes WebDriver commands to remote browser instances. It is designed for running different browser versions and platforms and distributing work. Grid is useful when the local machine lacks the required environment or when parallel execution can reduce feedback time.

For a simple single-machine Grid, start a Selenium Server standalone instance, then configure the test to use its WebDriver endpoint. The endpoint path and container/browser setup depend on the Selenium Server version and deployment. The Java example can select a remote endpoint using a system property:

private WebDriver createDriver(String browser) {
    String gridUrl = System.getProperty("gridUrl");
    if (gridUrl != null && !gridUrl.isBlank()) {
        org.openqa.selenium.Capabilities capabilities = switch (
                browser.toLowerCase(Locale.ROOT)) {
            case "chrome" -> new org.openqa.selenium.chrome.ChromeOptions();
            case "firefox" -> new org.openqa.selenium.firefox.FirefoxOptions();
            case "edge" -> new org.openqa.selenium.edge.EdgeOptions();
            default -> throw new IllegalArgumentException("Unsupported browser: " + browser);
        };
        try {
            return new org.openqa.selenium.remote.RemoteWebDriver(
                java.net.URI.create(gridUrl).toURL(), capabilities);
        } catch (java.net.MalformedURLException e) {
            throw new IllegalArgumentException("Invalid gridUrl: " + gridUrl, e);
        }
    }
    return switch (browser.toLowerCase(Locale.ROOT)) {
        case "chrome" -> new ChromeDriver();
        case "firefox" -> new FirefoxDriver();
        case "edge" -> new EdgeDriver();
        default -> throw new IllegalArgumentException("Unsupported browser: " + browser);
    };
}

Set -DgridUrl to the endpoint your Grid exposes, and run the same matrix. For example, if the endpoint is http://localhost:4444:

mvn test -Dbrowsers=chrome,firefox -DgridUrl=http://localhost:4444 -DbaseUrl=https://example.com

Selenium Grid offers Standalone as a simple one-machine arrangement and Hub/Node or distributed arrangements for multiple machines. Scale concurrency according to available CPU, memory, browser workload, and machine limits. Selenium’s guide gives roughly 1 GB of RAM per browser session as a planning reference, while cautioning that actual needs vary; treat it as an estimate, not capacity guarantee.

5. Configure browser options and environments

Capabilities and browser options communicate environment needs to a local driver or Grid. Common configuration choices include:

  • Headless mode: useful in environments without a display, but ensure it represents the rendering behavior you intend to validate.
  • Window size: set a consistent viewport when checking responsive layout; browser chrome and device emulation can affect the resulting viewport.
  • Browser version and platform: request these when the Grid offers them, and record what was actually allocated.
  • Timeouts: set page-load and explicit-wait limits based on application behavior. Avoid relying on implicit waits alongside explicit waits, which can make timing harder to reason about.
  • Isolation: use a new profile/session per test invocation unless the scenario specifically tests persisted state.

For example, options can be configured before constructing a local Chrome session:

org.openqa.selenium.chrome.ChromeOptions options =
    new org.openqa.selenium.chrome.ChromeOptions();
options.addArguments("--headless=new");
options.addArguments("--window-size=1440,1000");
WebDriver driver = new ChromeDriver(options);

Do not assume every browser supports the same vendor-specific capability or flag. Check the browser-specific Selenium documentation and the target Grid’s configuration for the exact options you need. Safari and Internet Explorer setup differs from the three browser factories shown above; add them only when the platform and driver environment are available.

6. Interpret failures and report coverage

A failure isolated to one browser or version is a compatibility signal, but first separate application behavior from a broken test environment. WebDriver uses browser automation APIs through browser-specific implementations, so browser/driver compatibility and Grid availability are part of the test system.

For every run, report the exact browser and version, operating system or Grid node, test scenarios, and pass/fail outcome. A green suite means those cases passed in those environments. It says nothing directly about omitted versions, devices, operating systems, or workflows.

7. Troubleshoot common failures

Symptom Likely cause Fix
Driver cannot start or browser session creation fails Browser/driver mismatch, missing browser installation, or unavailable driver management Confirm the browser is installed, update Selenium, check browser and driver compatibility, and verify the machine can download or locate the required driver.
Remote session reports no matching capability Grid has no node matching requested browser, version, or platform Inspect registered node capabilities and request only combinations that exist. Confirm the Grid endpoint and server configuration.
Connection refused to Grid Grid is not running, endpoint/port is wrong, or network access is blocked Check Grid health and listening address from the test runner host; use the endpoint exposed to that host rather than a node-only address.
Element not found or click intercepted Page is still rendering, selector is unstable, an overlay is present, or the layout differs Wait for a meaningful condition, prefer stable accessible selectors, and inspect a screenshot/DOM from the failing environment.
Test passes locally but fails in CI Different browser version, viewport, fonts, network, timezone, or resource pressure Record environment details, align viewport and browser versions where practical, and investigate the first differing condition rather than adding a longer sleep.
Tests interfere with one another Shared browser session or shared application state Create and close one session per invocation; isolate test data, cookies, and accounts where needed.
Suite hangs during navigation Page-load wait never completes or navigation timeout is too high Set a bounded page-load timeout, use a wait for the specific app state needed, and inspect network or browser logs.
Parallel runs become slower or flaky Too many browser sessions for available machine resources Reduce concurrency or add Grid capacity; measure the workload on the actual machines before increasing session count.

8. Performance, reliability, and cost

  • Feedback time: Serial execution is simpler and predictable. Parallel Grid sessions can shorten elapsed time, but startup overhead, contention, and resource limits can erase the gain.
  • Reliability: Favor isolated sessions, explicit state setup, bounded waits, and pinned or recorded environments. Retry only understood transient infrastructure failures; retries can conceal real intermittent application defects.
  • Capacity: Browser sessions consume meaningful CPU and memory. Use Selenium’s roughly 1 GB RAM per session reference only as an initial planning estimate, then size against measured workload and machine limits.
  • Cost: Local execution uses existing machines but requires browser installation and maintenance. Self-hosted Grid adds machine and operations costs. Hosted browser testing can reduce infrastructure work, but compare current browser coverage, pricing, artifacts, and service terms directly before choosing.
  • Maintenance: Browser releases and support commitments change. Review the matrix when product support changes, and validate dependency and browser versions at publication and upgrade time.

9. ScreenshotNeo for visual checks without browser setup

Selenium remains the right fit for interactive journeys and assertions that require clicking, typing, or inspecting application state. For screenshot capture, [ScreenshotNeo](https://screenshotneo.com) offers a one-request website screenshot API and MCP server. It can complement a Selenium suite when you need visual snapshots without maintaining a browser capture harness.

Or skip the browser setup

Use the API when the task is to capture a page, rather than exercise a multi-step browser workflow. See the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/) for request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners are accepted or removed before the capture, along with known newsletter popups and chat widgets; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card.

10. Frequently asked questions

Does JUnit run a test in multiple browsers automatically?

No. JUnit runs test invocations; your test configuration must create the browser sessions, or request them from Grid.

Does a passing Chrome test prove cross-browser compatibility?

No. It provides evidence only for that tested Chrome environment and scenario.

Should every test run on every browser?

That is a product and resource decision. Run critical workflows across the environments your support commitments require, and report any narrower coverage accurately.

Can Selenium Grid test different platforms?

Yes, when the Grid has nodes with the requested browser and platform capabilities. The available matrix depends on its configured machines.

Is Selenium-Jupiter part of JUnit or Selenium?

No. It is a third-party integration option described as a JUnit 5 extension; check its current maintenance and compatibility before adopting it.

Can screenshots replace interaction tests?

No. A screenshot captures rendered output, while Selenium can exercise interactions and assert application behavior. Use the method that matches the check.

Sources