Skip to main content
SeleniumDecoded

pytest Integration

Build robust Python Selenium tests using pytest fixtures, markers, and plugins.

Selenium 3 & 4 Stable

pytest is Python’s most popular testing framework, offering powerful features like fixtures, parameterization, and plugins that integrate perfectly with Selenium.

Basic Setup

Installation

Terminal window
pip install pytest selenium pytest-html

Project Structure

project/
├── conftest.py # Shared fixtures
├── pytest.ini # Configuration
├── pages/
│ ├── __init__.py
│ ├── base_page.py
│ └── login_page.py
└── tests/
├── __init__.py
├── conftest.py # Test-specific fixtures
├── test_login.py
└── test_search.py

pytest.ini Configuration

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --html=reports/report.html --self-contained-html
markers =
smoke: Quick sanity tests
regression: Full regression tests
slow: Tests that take a long time

Fixtures

Basic Driver Fixture

conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
@pytest.fixture
def driver():
"""Create a WebDriver instance for each test."""
options = Options()
options.add_argument("--start-maximized")
driver = webdriver.Chrome(options=options)
yield driver # Provide driver to test
driver.quit() # Cleanup after test
@pytest.fixture
def logged_in_driver(driver):
"""Driver with pre-authenticated session."""
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys("testuser")
driver.find_element(By.ID, "password").send_keys("testpass")
driver.find_element(By.ID, "login").click()
return driver

Using Fixtures in Tests

tests/test_login.py
from selenium.webdriver.common.by import By
def test_valid_login(driver):
"""Test successful login."""
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys("validuser")
driver.find_element(By.ID, "password").send_keys("validpass")
driver.find_element(By.ID, "login").click()
assert "/dashboard" in driver.current_url
def test_dashboard_access(logged_in_driver):
"""Test dashboard with pre-logged-in driver."""
logged_in_driver.get("https://example.com/dashboard")
header = logged_in_driver.find_element(By.TAG_NAME, "h1")
assert header.text == "Welcome"

Fixture Scopes

@pytest.fixture(scope="function") # Default: new for each test
def driver_per_test():
driver = webdriver.Chrome()
yield driver
driver.quit()
@pytest.fixture(scope="class") # Shared within test class
def driver_per_class():
driver = webdriver.Chrome()
yield driver
driver.quit()
@pytest.fixture(scope="module") # Shared within module
def driver_per_module():
driver = webdriver.Chrome()
yield driver
driver.quit()
@pytest.fixture(scope="session") # Shared across all tests
def driver_per_session():
driver = webdriver.Chrome()
yield driver
driver.quit()

Browser Parametrization

conftest.py
import pytest
from selenium import webdriver
def pytest_addoption(parser):
parser.addoption(
"--browser",
action="store",
default="chrome",
help="Browser to run tests: chrome, firefox, edge"
)
@pytest.fixture
def driver(request):
browser = request.config.getoption("--browser")
if browser == "chrome":
driver = webdriver.Chrome()
elif browser == "firefox":
driver = webdriver.Firefox()
elif browser == "edge":
driver = webdriver.Edge()
else:
raise ValueError(f"Unknown browser: {browser}")
driver.maximize_window()
yield driver
driver.quit()

Run with: pytest --browser=firefox

Parameterized Tests

import pytest
from selenium.webdriver.common.by import By
@pytest.mark.parametrize("username,password,expected", [
("user1", "pass1", True),
("user2", "pass2", True),
("invalid", "wrong", False),
])
def test_login(driver, username, password, expected):
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "password").send_keys(password)
driver.find_element(By.ID, "login").click()
if expected:
assert "/dashboard" in driver.current_url
else:
assert driver.find_element(By.CLASS_NAME, "error").is_displayed()
@pytest.mark.parametrize("search_term", [
"selenium",
"webdriver",
"automation",
])
def test_search(driver, search_term):
driver.get("https://example.com")
driver.find_element(By.NAME, "q").send_keys(search_term)
driver.find_element(By.ID, "search").click()
results = driver.find_elements(By.CLASS_NAME, "result")
assert len(results) > 0

Markers

import pytest
@pytest.mark.smoke
def test_homepage_loads(driver):
driver.get("https://example.com")
assert "Example" in driver.title
@pytest.mark.regression
def test_full_checkout_flow(driver):
# Full flow test
pass
@pytest.mark.slow
def test_large_data_export(driver):
# Slow test
pass
@pytest.mark.skip(reason="Feature not implemented")
def test_new_feature(driver):
pass
@pytest.mark.skipif(
condition=True,
reason="Only run on production"
)
def test_production_only(driver):
pass

Run specific markers: pytest -m smoke

Screenshot on Failure

conftest.py
import pytest
import os
from datetime import datetime
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
setattr(item, f"rep_{report.when}", report)
@pytest.fixture
def driver(request):
driver = webdriver.Chrome()
driver.maximize_window()
yield driver
# Take screenshot on failure
if request.node.rep_call.failed:
screenshot_dir = "screenshots"
os.makedirs(screenshot_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
name = request.node.name
filepath = f"{screenshot_dir}/{name}_{timestamp}.png"
driver.save_screenshot(filepath)
print(f"Screenshot saved: {filepath}")
driver.quit()

Page Object Integration

pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def find(self, locator):
return self.wait.until(EC.presence_of_element_located(locator))
def click(self, locator):
self.wait.until(EC.element_to_be_clickable(locator)).click()
# pages/login_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
class LoginPage(BasePage):
USERNAME = (By.ID, "username")
PASSWORD = (By.ID, "password")
LOGIN_BTN = (By.ID, "login")
def login(self, username, password):
self.find(self.USERNAME).send_keys(username)
self.find(self.PASSWORD).send_keys(password)
self.click(self.LOGIN_BTN)
# tests/test_login.py
from pages.login_page import LoginPage
def test_valid_login(driver):
driver.get("https://example.com/login")
login_page = LoginPage(driver)
login_page.login("validuser", "validpass")
assert "/dashboard" in driver.current_url

Useful pytest Plugins

PluginPurpose
pytest-htmlHTML reports
pytest-xdistParallel execution
pytest-rerunfailuresRetry failed tests
pytest-timeoutTest timeouts
pytest-orderingControl test order

Parallel Execution

Terminal window
pip install pytest-xdist
pytest -n 4 # Run 4 tests in parallel
pytest -n auto # Auto-detect CPU count

Retry Failed Tests

Terminal window
pip install pytest-rerunfailures
pytest --reruns 3 --reruns-delay 2

Next Steps