SauceLabs Cross-Browser Testing: Setup, Configuration, and Best Practices

SauceLabs Cross-Browser Testing: Setup, Configuration, and Best Practices

Cross-browser testing on SauceLabs means running your Selenium tests against a matrix of browser/OS combinations — Chrome, Firefox, Safari, Edge, and legacy browsers like IE11 — without provisioning your own VMs. This post covers building the browser matrix, W3C capabilities configuration, legacy browser quirks, visual screenshot testing, and parallel execution to keep your test suite fast.

Why Cross-Browser Testing Still Matters

Modern browsers have converged on most web standards, but gaps remain:

  • Safari lags on features like CSS grid subgrid, certain Web APIs, and IndexedDB behavior
  • IE11 is gone from most targets but lingers in enterprise and government
  • Mobile browsers (Chrome for Android, Safari for iOS) have different scroll, touch, and viewport behavior
  • Older browser versions matter if your analytics show users still running them

The goal isn't to test every browser equally — it's to test the browsers your users actually use, weighted by their share in your analytics.

Building Your Browser Matrix

Start with your analytics data. A reasonable baseline matrix:

Priority Browser Version Platform % Users (example)
P1 Chrome latest Windows 11 45%
P1 Safari latest macOS 14 22%
P1 Chrome latest macOS 14 12%
P2 Firefox latest Windows 11 8%
P2 Edge latest Windows 11 7%
P2 Safari 16 macOS 13 3%
P3 Chrome latest-1 Windows 10 2%
P3 IE 11 11 Windows 10 1%

Test P1 on every commit, P2 on daily schedule, P3 on pre-release only.

W3C Capabilities Configuration

SauceLabs uses W3C WebDriver protocol. The capability structure:

{
  "browserName": "chrome",
  "browserVersion": "latest",
  "platformName": "Windows 11",
  "sauce:options": {
    "name": "Cross-browser smoke test",
    "build": "v2.4.0",
    "tags": ["smoke", "cross-browser"]
  }
}

Valid browserName values: chrome, firefox, safari, MicrosoftEdge, internet explorer

Building a Matrix in Python

BROWSER_MATRIX = [
    {
        "browserName": "chrome",
        "browserVersion": "latest",
        "platformName": "Windows 11",
    },
    {
        "browserName": "safari",
        "browserVersion": "latest",
        "platformName": "macOS 14",
    },
    {
        "browserName": "firefox",
        "browserVersion": "latest",
        "platformName": "Windows 11",
    },
    {
        "browserName": "MicrosoftEdge",
        "browserVersion": "latest",
        "platformName": "Windows 11",
    },
]

def create_driver(browser_caps: dict):
    from selenium import webdriver

    options_class = {
        "chrome": webdriver.ChromeOptions,
        "firefox": webdriver.FirefoxOptions,
        "safari": webdriver.SafariOptions,
        "MicrosoftEdge": webdriver.EdgeOptions,
        "internet explorer": webdriver.IeOptions,
    }[browser_caps["browserName"]]

    options = options_class()
    options.browser_version = browser_caps["browserVersion"]
    options.platform_name = browser_caps["platformName"]
    options.set_capability("sauce:options", {
        "username": os.environ["SAUCE_USERNAME"],
        "accessKey": os.environ["SAUCE_ACCESS_KEY"],
        "build": os.environ.get("BUILD_ID", "local"),
        "name": f"Test on {browser_caps['browserName']} {browser_caps['browserVersion']}",
    })

    return webdriver.Remote(
        command_executor="https://ondemand.us-west-1.saucelabs.com/wd/hub",
        options=options
    )

Legacy Browser Configuration

IE11

Internet Explorer 11 requires specific capability handling:

from selenium.webdriver.ie.options import Options as IeOptions

ie_options = IeOptions()
ie_options.platform_name = "Windows 10"
ie_options.browser_version = "11"
ie_options.set_capability("sauce:options", {
    "username": os.environ["SAUCE_USERNAME"],
    "accessKey": os.environ["SAUCE_ACCESS_KEY"],
    # IE-specific options
    "iedriverVersion": "3.150.1",
    "name": "IE11 Test",
})
# Required for IE: ensure Protected Mode is same for all zones
ie_options.set_capability("ignoreProtectedModeSettings", True)
ie_options.set_capability("ignoreZoomSetting", True)

IE11 quirks to handle:

  • No Promise support — transpile your JS or polyfill
  • No fetch API — use XMLHttpRequest or a polyfill
  • CSS Grid support is partial (old spec)
  • querySelector works but closest() doesn't

Safari 12 (macOS Mojave)

safari_options = webdriver.SafariOptions()
safari_options.browser_version = "12"
safari_options.platform_name = "macOS 10.14"
safari_options.set_capability("sauce:options", {
    "username": os.environ["SAUCE_USERNAME"],
    "accessKey": os.environ["SAUCE_ACCESS_KEY"],
    "name": "Safari 12 Test",
})

Safari 12 gotchas:

  • WebDriver commands are slower — increase timeouts to 30s+
  • localStorage may not persist across navigation in private mode
  • CSS position: sticky has partial support

Screenshot Testing

SauceLabs captures full-page screenshots automatically. Access them via API:

JOB_ID="your-job-id"
curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \
  "https://api.us-west-1.saucelabs.com/rest/v1/$SAUCE_USERNAME/jobs/$JOB_ID/assets/0000screenshot.png" \
  --output screenshot.png

For visual regression testing, take manual screenshots at key assertions:

def take_screenshot(driver, name):
    """Save screenshot to SauceLabs and return the data."""
    driver.execute_script(f"sauce:context={name}")  # Label in SauceLabs UI
    return driver.get_screenshot_as_png()

def test_homepage_visual(driver):
    driver.get("https://example.com")

    # Wait for page load
    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.TAG_NAME, "main"))
    )

    take_screenshot(driver, "homepage-loaded")

    # Check above-the-fold elements
    hero = driver.find_element(By.CLASS_NAME, "hero")
    assert hero.is_displayed()

    take_screenshot(driver, "hero-verified")

Parallel Execution Strategy

Running a 4-browser matrix serially takes 4x as long. Parallelize at the test level:

pytest approach

# conftest.py
import pytest

def pytest_generate_tests(metafunc):
    if "browser_config" in metafunc.fixturenames:
        metafunc.parametrize(
            "browser_config",
            BROWSER_MATRIX,
            ids=[f"{b['browserName']}-{b['browserVersion']}" for b in BROWSER_MATRIX]
        )

@pytest.fixture
def driver(browser_config):
    d = create_driver(browser_config)
    yield d
    passed = not hasattr(d, "_test_failed")
    d.execute_script(f"sauce:job-result={'passed' if passed else 'failed'}")
    d.quit()
# Run all browsers in parallel
pytest tests/ -n auto --dist=loadscope

With 4 browsers and pytest-xdist, a 20-test suite runs in ~25% of the serial time.

Concurrency limits

SauceLabs plans have concurrency limits (simultaneous sessions). Match your -n value to your limit:

SauceLabs Plan Concurrency Recommended -n
Free 1 1
Team 5 4
Business 15 12
Enterprise 50+ 40+

Setting Browser Window Size

Browsers on SauceLabs default to 1024×768. Set a consistent size:

driver.set_window_size(1920, 1080)  # Full HD
# or
driver.maximize_window()

For responsive testing, test at multiple breakpoints:

VIEWPORTS = [
    (375, 812),   # iPhone X
    (768, 1024),  # iPad
    (1280, 800),  # Laptop
    (1920, 1080), # Desktop
]

@pytest.mark.parametrize("width,height", VIEWPORTS)
def test_responsive_nav(driver, width, height):
    driver.set_window_size(width, height)
    driver.get("https://example.com")
    # Assert navigation collapses/expands correctly

Test Naming and Tagging

Good naming makes the SauceLabs dashboard useful:

sauce_options = {
    "name": f"[{browser}] Checkout flow - payment step",
    "build": f"PR-{pr_number}",
    "tags": ["checkout", "payment", "cross-browser"],
    "customData": {
        "ticket": "JIRA-1234",
        "browser": browser,
        "viewport": "1920x1080",
    }
}

Filter by tag in the dashboard or API:

curl -u "$SAUCE_USERNAME:$SAUCE_ACCESS_KEY" \
  "https://api.us-west-1.saucelabs.com/rest/v1/$SAUCE_USERNAME/jobs?tag=cross-browser&limit=50"

When Tests Behave Differently Across Browsers

Common causes and fixes:

Symptom Likely cause Fix
Click does nothing on Safari Element not in viewport Scroll into view first
Timeout on IE11 Slow JS execution Increase implicitly_wait to 20s
Font rendering differs OS font rendering Don't pixel-compare fonts
Date input format varies Browser-native input Use send_keys with format matching browser locale
CSS transition not complete Animation still running Add driver.execute_script("document.getAnimations().forEach(a => a.finish()")

Read more

Start now free