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
Promisesupport — transpile your JS or polyfill - No
fetchAPI — use XMLHttpRequest or a polyfill - CSS Grid support is partial (old spec)
querySelectorworks butclosest()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+
localStoragemay not persist across navigation in private mode- CSS
position: stickyhas 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.pngFor 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=loadscopeWith 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 correctlyTest 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()") |