</>

Technology

Selenium

Difficulty

Advanced

Interview Question

How do you design a Selenium framework to run tests on multiple browsers and environments (staging/QA/prod)?

Answer

Multi-Browser, Multi-Environment Selenium Framework

Architecture Overview

CODE
                     testng.xml / Maven CLI
                           │
                    ┌──────┴──────┐
                    │  BaseTest   │
                    │ @BeforeMethod│
                    └──────┬──────┘
                           │
               ┌───────────┴────────────┐
               │      DriverFactory      │
               │  browser=chrome/ff/edge │
               │  mode=local/grid/sauce  │
               └───────────┬────────────┘
               │           │           │
         ChromeDriver  FirefoxDriver  RemoteWebDriver (Grid)
                           │
               ┌───────────┴────────────┐
               │     ConfigReader       │
               │  env=staging/qa/prod   │
               └────────────────────────┘

Environment Config Files

PROPERTIES
# config/staging.properties
base.url=https://staging.automateqa.online
db.url=jdbc:postgresql://staging-db:5432/automateqa
api.base=https://api.staging.automateqa.online
test.user=staging_test@automateqa.com
test.pass=StagingPass@123

# config/qa.properties
base.url=https://qa.automateqa.online
api.base=https://api.qa.automateqa.online

# config/prod.properties  (read-only smoke tests only!)
base.url=https://automateqa.online
test.user=prod_smoke@automateqa.com

DriverFactory — Supports Local + Grid

Java
public class DriverFactory {

    public static WebDriver createDriver(String browser, boolean headless)
            throws MalformedURLException {

        String gridUrl = ConfigReader.get("grid.url");  // "" or actual Grid URL

        if (gridUrl != null && !gridUrl.isEmpty()) {
            return createRemoteDriver(browser, headless, gridUrl);
        } else {
            return createLocalDriver(browser, headless);
        }
    }

    private static WebDriver createLocalDriver(String browser, boolean headless) {
        switch (browser.toLowerCase()) {
            case "firefox":
                FirefoxOptions ffOpts = new FirefoxOptions();
                if (headless) ffOpts.addArguments("--headless");
                return new FirefoxDriver(ffOpts);

            case "edge":
                EdgeOptions edgeOpts = new EdgeOptions();
                if (headless) edgeOpts.addArguments("--headless");
                return new EdgeDriver(edgeOpts);

            default: // chrome
                ChromeOptions opts = new ChromeOptions();
                if (headless) opts.addArguments("--headless=new",
                    "--no-sandbox", "--disable-dev-shm-usage");
                return new ChromeDriver(opts);
        }
    }

    private static WebDriver createRemoteDriver(String browser, boolean headless, String gridUrl)
            throws MalformedURLException {
        MutableCapabilities options;
        switch (browser.toLowerCase()) {
            case "firefox": options = new FirefoxOptions(); break;
            case "edge":    options = new EdgeOptions();    break;
            default:        options = new ChromeOptions();  break;
        }
        if (headless) ((ChromeOptions) options).addArguments("--headless=new");
        return new RemoteWebDriver(new URL(gridUrl), options);
    }
}

BaseTest — Reads From Config

Java
public class BaseTest {

    @Parameters({"browser", "env"})
    @BeforeMethod(alwaysRun = true)
    public void setUp(@Optional("chrome") String browser,
                      @Optional("staging") String env)
            throws MalformedURLException {

        // Set env so ConfigReader loads the right .properties file
        System.setProperty("env", env);
        System.setProperty("browser", browser);

        boolean headless = ConfigReader.isHeadless();
        WebDriver driver = DriverFactory.createDriver(browser, headless);
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        DriverManager.setDriver(driver);

        String baseUrl = ConfigReader.getBaseUrl();
        driver.get(baseUrl);
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown(ITestResult result) {
        if (result.getStatus() == ITestResult.FAILURE) {
            ScreenshotUtil.capture(result.getName());
        }
        DriverManager.quitDriver();
    }
}

testng.xml — Cross-Browser on Staging

XML
<suite name="Cross-Browser Staging" parallel="tests" thread-count="3">
    <parameter name="env" value="staging"/>

    <test name="Chrome-Staging">
        <parameter name="browser" value="chrome"/>
        <classes><class name="com.automateqa.tests.SmokeTest"/></classes>
    </test>

    <test name="Firefox-Staging">
        <parameter name="browser" value="firefox"/>
        <classes><class name="com.automateqa.tests.SmokeTest"/></classes>
    </test>

    <test name="Edge-Staging">
        <parameter name="browser" value="edge"/>
        <classes><class name="com.automateqa.tests.SmokeTest"/></classes>
    </test>
</suite>

GitHub Actions — Multi-Environment Matrix

YAML
name: Multi-Environment Tests

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 */6 * * *'   # Every 6 hours

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        env:     [staging, qa]
        browser: [chrome, firefox]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'

      - name: Start Selenium Grid
        run: |
          docker network create grid
          docker run -d -p 4444:4444 --network grid selenium/standalone-chrome:latest
          sleep 10

      - name: Run Tests  ${{ matrix.env }} / ${{ matrix.browser }}
        run: |
          mvn test \
            -Denv=${{ matrix.env }} \
            -Dbrowser=${{ matrix.browser }} \
            -Dheadless=true \
            -Dgrid.url=http://localhost:4444 \
            -Dbase.url=${{ secrets[format('{0}_BASE_URL', matrix.env)] }}

      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: report-${{ matrix.env }}-${{ matrix.browser }}
          path: test-output/

Follow AutomateQA

Related Topics