How to Use Atomic Tests in Selenium
Learn how to make Selenium tests independent, focused, and easier to debug with isolated setup, browser actions, assertions, and cleanup.
Atomic Selenium tests are independent, focused tests: each one prepares the state it needs, performs a short set of relevant browser actions, checks one coherent outcome, and cleans up after itself. A test should not depend on another test’s success, execution order, or data. Selenium does not prescribe exactly one assertion per test; the goal is a clear purpose and self-contained state.
This guide uses Python with pytest for runnable examples. The same principles apply with other languages and test frameworks. Selenium WebDriver controls the browser; pytest runs the tests and evaluates assertions. See the Selenium guidance on test independency and avoiding shared state.
1. What makes a Selenium test atomic?
“Atomic” is a useful shorthand for a test with one clear reason to exist and no dependency on other tests. It does not mean that a test must contain one browser command or one assertion. Several actions and checks can belong together when they establish one outcome, such as confirming that a sign-in form displays a validation message for an invalid password.
Selenium recommends writing each test as its own unit. For example, a test that checks a published content module should arrange the content it needs itself, or use a controlled stub; it should not wait for another test to create and publish that content.
| Quality | Practical meaning |
|---|---|
| Independent | It runs successfully without another test running first. |
| Isolated | It uses its own browser session and data, or data that cannot collide with other tests. |
| Focused | Its browser actions and checks answer one test question. |
| Repeatable | It can run again after success or failure without stale state changing the result. |
2. Set up a runnable Python example
Install Selenium and pytest in your project environment:
python -m pip install selenium pytest
Recent Selenium versions can use Selenium Manager to obtain a compatible browser driver when one is not already available. You still need a supported browser installed. In managed CI environments, install and configure the browser and driver according to that environment’s policy.
Save the following as test_login.py. This example assumes the application provides a test page at http://localhost:8000/login with the indicated IDs and a predictable validation message. Adjust the URL and selectors to match your application.
import pytest
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
@pytest.fixture
def driver():
browser = webdriver.Chrome()
browser.set_window_size(1280, 900)
yield browser
browser.quit()
def test_invalid_password_shows_validation_message(driver):
driver.get("http://localhost:8000/login")
driver.find_element(By.ID, "email").send_keys("atomic-test@example.test")
driver.find_element(By.ID, "password").send_keys("wrong-password")
driver.find_element(By.ID, "submit").click()
message = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "login-error"))
)
assert message.text == "Email or password is incorrect"
Run it with:
python -m pytest -q
The fixture creates a fresh WebDriver for each test and quits it after the test, including when the test fails. The test itself arranges the invalid credentials, performs the submit action, and checks the resulting message. It does not rely on a separate test to create an account or establish browser state.
3. Structure tests with arrange, act, assert, and cleanup
- Arrange: create or select the specific data and preconditions this behavior needs.
- Act: perform the short browser interaction that is the subject of the test.
- Assert: check the visible or otherwise relevant outcome with the test framework.
- Clean up: remove created records when appropriate and close the browser session.
Cleanup should be reliable even when an assertion fails. A pytest fixture with yield is one way to guarantee browser shutdown. For application data, use a fixture finalizer or a separate cleanup fixture where needed. Prefer unique data per test so cleanup failures do not make later runs ambiguous.
Keep assertions aligned to the purpose. A test may check both that an error is visible and that the form remains available if those checks together describe the same invalid-login behavior. If it starts checking unrelated navigation, profile editing, and purchase behavior, split those into separate tests.
4. Keep setup fast without sharing test dependencies
Isolation does not require driving the browser through every prerequisite. When possible, create test data through an application API, fixture, or database setup process before opening the browser. Then use Selenium for the behavior that needs a real browser, such as form validation, navigation, responsive rendering, or browser-specific interaction.
- Give each test distinct records or a unique namespace.
- Remove stale test records that could be selected by a later run.
- Do not assume a previous test created the user, order, or content being checked.
- Use stubs or controlled fixtures when a test needs a stable dependency.
- Keep browser-driven setup limited to interactions that are themselves under test.
For example, an order-confirmation test can arrange an eligible order through an API and then use Selenium to verify the confirmation page. A separate test should cover the browser checkout flow. This keeps the confirmation test focused without weakening its independence.
5. Run tests in any order and prepare for parallel runs
A useful independence check is to run a test by itself, run the suite in a different order, and repeat a failed test after the suite finishes. If its result changes because another test ran first, it is sharing state or relying on execution order.
Selenium recommends a new WebDriver instance per test for isolation and simpler parallelization. Parallel execution still requires a data strategy that avoids collisions. Two independent browser sessions can interfere if they edit the same account, consume the same one-time token, or delete each other’s records. Use per-test data, separate accounts, or another explicit isolation boundary.
Selenium Grid can distribute tests across machines and browser/platform combinations, but distribution does not solve shared application data. Keep the same independence and collision protections when using Grid.
6. Decide what belongs in a browser test
Functional browser tests can be expensive to execute and maintain. Before adding a Selenium test, ask whether a lighter-weight test can verify the behavior. A unit or service-level test may be enough for business logic; Selenium is appropriate when browser behavior is part of the question.
Keep one long business journey from becoming a single fragile script that creates an account, configures a product, adds it to a cart, pays, and submits feedback. Give those behaviors separate tests with their own preconditions. This makes failures easier to locate and lets each test prepare only what it needs.
Selenium provides browser control, not the test runner, pass/fail assertions, or reporting. Use pytest, JUnit, NUnit, or another suitable testing framework for those responsibilities. Selenium’s own practices are recommendations to adapt to the application and team, not a single mandatory test design.
7. Common mistakes and fixes
| Problem | Why it happens | Fix |
|---|---|---|
| A test fails when run alone | Another test created data or state it expects. | Move prerequisite setup into the test or a fixture that runs for it. |
| Results depend on test order | Tests share mutable records, browser state, or external state. | Use separate data, reset state, and create a new driver per test. |
| Intermittent failures with old records | Stale data is being selected or conflicts with current test data. | Use unique identifiers and clean stale records through a controlled setup process. |
| A test takes a long time and fails far from the cause | It covers an entire user journey with many unrelated behaviors. | Split it into focused tests with independent prerequisites. |
| Browser setup dominates the test | The browser is being used for setup that does not need browser coverage. | Create data through an API or fixture, then use Selenium only for the behavior under test. |
| Element lookup fails immediately after navigation or click | The page has not finished rendering the expected element. | Wait for a specific condition, such as visibility or clickability, rather than relying on a fixed short sleep. |
| Driver does not start in CI | The browser is missing, incompatible, or unavailable to the runner. | Install a supported browser and ensure Selenium Manager or the configured driver can access a compatible driver. |
| Assertions or test reports are missing | WebDriver is being treated as the test framework. | Run browser actions inside a test framework and make assertions there. |
8. Performance, reliability, and cost considerations
Test independence improves reliability and makes tests easier to rerun and distribute, but it does not make a browser test free. Browser startup, page loading, network dependencies, and application setup still take time. Keep the Selenium portion short, reuse fast setup mechanisms where appropriate, and wait for meaningful page conditions instead of adding arbitrary delays.
Reliable tests also need controlled external dependencies. If a third-party service, asynchronous job, or shared environment is part of the scenario, define how the test detects readiness and how it handles unavailable dependencies. Do not silently depend on a previous test or on timing assumptions.
The research sources provide no universal benchmark or fixed speedup for atomic tests. Measure the suite in your environment, and prioritize predictable setup and useful failure boundaries over a target assertion count.
9. ScreenshotNeo for visual evidence
Selenium remains the right tool when the test must interact with a browser and assert application behavior. For a clean screenshot of a page as a separate artifact, ScreenshotNeo provides a website screenshot API and MCP server. Its screenshot API can be used for visual review and capture workflows; it does not replace Selenium interaction tests or their assertions.
Or skip the browser setup
For a screenshot, make one GET request. See the ScreenshotNeo API documentation 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers indicate the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, no card required.
10. FAQ
Does an atomic test have to contain exactly one assertion?
No. Selenium’s guidance emphasizes independence, isolation, short scope, and a clear purpose; it does not define a mandatory assertion count.
Can atomic tests share a browser session?
A fresh WebDriver per test is Selenium’s recommended approach for isolation and simpler parallelization. Shared sessions can carry browser state from one test into another.
Are atomic tests always faster?
Not necessarily. Independent setup has a cost, but API or fixture setup can avoid repeating unrelated browser journeys. The main goal is a reliable, focused test suite.
Is Selenium itself the test runner?
No. WebDriver controls the browser. A framework such as pytest, JUnit, or NUnit runs tests, checks assertions, and reports results.


