How to Test CSS and Visual Regressions With Python Selenium
Build reliable Python Selenium screenshot tests: capture stable UI states, compare baselines, review diffs, and avoid flaky CSS regressions.
A CSS visual regression test drives a page into a known state, captures a screenshot, compares it with an approved baseline, and makes a deliberate decision about the difference. With Python Selenium, the reliable sequence is:
- Use the same browser, viewport, test data, and application state for every run.
- Wait for an application-specific ready condition.
- Capture the current browser window or a specific element.
- Compare the image with a stored baseline and save a useful diff artifact.
- Review the change: fix an unintended regression or approve a new baseline for an intentional design change.
Selenium saves the current browsing context as a PNG and can also capture an individual element. Its ordinary screenshot call should not be described as a guaranteed full-page capture. Selenium screenshot documentation and the Python WebDriver API document these calls.
What a visual regression test actually checks
A functional test can pass while a CSS change moves a button, clips text, changes spacing, or breaks a responsive layout. A screenshot test records rendered pixels at a checkpoint. The baseline is the approved reference image for that checkpoint. A comparison rule decides whether the new image is close enough, and a review process decides whether a difference is expected.
The review loop should be explicit: exercise the UI state, capture a checkpoint, compare it with the baseline, inspect the difference, then accept a new baseline only when the product change is intentional. This checkpoint and baseline workflow is described in the Applitools visual testing overview.
Set up Selenium for a deterministic screenshot test
Install and choose a browser
Install Selenium in the environment that runs your tests and make the browser version part of the test environment. Run the same browser and operating system image for baseline creation and comparison. A changed browser, font set, device scale, or viewport can produce pixel differences unrelated to your CSS.
Capture a stable page state
Navigation returning does not mean that client-side rendering is finished. Wait for a meaningful condition such as the main component becoming visible, a loading marker disappearing, or a test-specific data attribute appearing. Selenium documents these race conditions and recommends explicit waits in its waiting strategies and expected conditions guides.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
output = Path('artifacts/homepage.png')
output.parent.mkdir(parents=True, exist_ok=True)
driver = webdriver.Chrome()
try:
driver.set_window_size(1280, 900)
driver.get('https://example.com')
WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, 'main'))
)
assert driver.save_screenshot(str(output))
finally:
driver.quit()
save_screenshot stores the current window as a PNG and returns false on an I/O error. For a component-level checkpoint, locate the element and call its screenshot method:
card = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, '[data-testid="pricing-card"]'))
)
assert card.screenshot('artifacts/pricing-card.png')
Build baselines and compare screenshots
Use stable checkpoint names
Name checkpoints by page, state, viewport, and relevant data, for example checkout-empty-1280x900 or dashboard-dark-1440x900. Keep approved images with the test suite or in CI artifacts. Never overwrite a baseline automatically after a failure.
Define the comparison rule
Your comparison process should produce:
- the newly captured image;
- the baseline image;
- a diff image or highlighted regions;
- a machine-readable pass/fail result;
- a review link or CI artifact path.
For a local workflow, choose and document an image comparison implementation, its tolerance, and how it treats anti-aliasing. The research sources do not establish a particular local diff package, so verify the package’s current API and maintenance before standardizing on one. Hosted services can provide managed checkpoint review; Percy documents Selenium snapshots through percy_snapshot(driver, name), custom CSS, responsive widths, full-page capture options, frozen animated images, and ignored regions in its Python Selenium integration. Applitools documents checkpoint, baseline, and review concepts in its overview.
Approve changes deliberately
- Open the baseline, current image, and diff.
- Classify the change as intentional, environmental noise, or a product regression.
- If intentional, update only the affected baseline and record why.
- If unintended, keep the baseline and fix the CSS or application state.
Make captures reproducible
Control the environment
- Pin the browser and operating system image used in CI.
- Set an explicit window size before navigation.
- Use a consistent device scale factor and font availability.
- Use deterministic fixtures instead of live timestamps, random IDs, or changing API data.
- Set the same authentication and feature-flag state for baseline and comparison runs.
- Run at the same timezone and locale when formatted dates or numbers appear.
Control dynamic regions
Animations, rotating ads, timestamps, chat launchers, and personalized recommendations can change pixels without a CSS regression. Prefer test data that does not change. Where your capture tool supports it, freeze animations, inject screenshot-only CSS, or ignore a known region. Percy documents these controls for its integration. Keep the rule narrow: ignoring a large area can hide a real regression.
Wait for the condition that matters
Waiting for document.readyState alone is often insufficient for a JavaScript application. Wait for the component or state that proves the page is ready, and use a bounded timeout so a broken page fails clearly.
Viewport, element, and full-page coverage
A normal Selenium WebDriver screenshot captures the current browsing context. Use it for a fixed viewport checkpoint. Element screenshots are useful for isolated components and reduce unrelated page noise.
Full-page capture is different. Selenium’s cited Python API does not establish a standard full-page method. Browser-specific or hosted implementations may scroll and stitch images; stitching can create anomalies around floating bars and other dynamic elements, as discussed in Applitools screenshotting guidance. If the page must be covered end to end, test the chosen implementation on sticky headers, lazy-loaded images, and long lists before making it a baseline.
For responsive CSS, create separate checkpoints for each supported viewport rather than stretching one image. Keep the viewport list small enough to review and broad enough to cover your layout breakpoints.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Intermittent missing component | The test captures before asynchronous rendering completes. | Wait for a visible, application-specific ready element or state. |
| Text wraps differently in CI | Different browser, fonts, viewport, or device scale. | Use a fixed image, install the same fonts, and set the same viewport and scale. |
| Only animated areas differ | CSS transitions, videos, carousels, or rotating content. | Disable or freeze motion for capture, or narrowly ignore the dynamic region. |
| Baseline changes on every run | Live data, timestamps, random values, or unstable ordering. | Seed fixtures, freeze time, and sort data in the test environment. |
| Sticky header appears twice in a long image | Scroll-and-stitch full-page capture interacted with a floating element. | Use viewport checkpoints, hide the sticky element for capture, or validate the full-page implementation separately. |
| Screenshot file is empty or missing | Output directory or file write failed. | Create the directory first, assert the return value, and preserve the driver cleanup in finally. |
| Large diff after a harmless browser update | Rendering engine or font rasterization changed. | Rebuild baselines in the new pinned environment only after review. |
| Test hangs during navigation | Page load or a network dependency never completes. | Use bounded page and explicit waits, isolate external dependencies, and save diagnostic logs. |
Performance, reliability, and cost
Browser startup and page rendering usually dominate a screenshot test. Reuse a driver when isolation permits, but reset cookies, storage, authentication, and application state between checkpoints. Parallel workers can shorten suites, yet each worker needs a consistent browser environment and enough resources to avoid timing changes.
Capture only the regions that answer the question. Element screenshots are smaller and easier to review; viewport screenshots cover layout context. Full-page screenshots cost more time and can introduce stitching behavior. Keep artifacts for failures and a short retention window for successful runs unless your review policy requires more.
For hosted visual review, compare current pricing, retention, browser coverage, data handling, and CI behavior directly in the provider documentation before committing. The cited Percy and Applitools pages establish workflow capabilities, not current plan limits or prices.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP tools let Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for the complete option list. Relevant controls include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, custom CSS and JavaScript, clicks before capture, selector waits and delays, network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Does Selenium screenshot the whole page?
The standard Python WebDriver screenshot captures the current browsing context. Treat full-page capture as a separate, implementation-specific capability.
Should every visual difference fail CI?
Unexpected differences should fail. Intentional design changes should go through review and receive a deliberate baseline update.
How do I stop flaky screenshots?
Pin the browser and viewport, use deterministic data, disable or control animation, and wait for an application-specific ready condition.
Should I compare complete pages or elements?
Use element checkpoints for focused component changes and viewport checkpoints for layout context. Use full-page coverage only after validating its scrolling and stitching behavior.
Can an API replace Selenium for visual regression?
An API can simplify URL-based capture and remove browser-driver maintenance. Selenium remains useful when the test must perform authenticated interactions or drive a complex state before capture.


