pytest Integration
Build robust Python Selenium tests using pytest fixtures, markers, and plugins.
Selenium 3 & 4 Stable Verified against Selenium 4.48.0
pytest is Python’s most popular testing framework, offering powerful features like fixtures, parameterization, and plugins that integrate perfectly with Selenium.
Basic Setup
Installation
pip install pytest selenium pytest-htmlProject 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.pypytest.ini Configuration
[pytest]testpaths = testspython_files = test_*.pypython_classes = Test*python_functions = test_*addopts = -v --html=reports/report.html --self-contained-htmlmarkers = smoke: Quick sanity tests regression: Full regression tests slow: Tests that take a long timeFixtures
Basic Driver Fixture
import pytestfrom selenium import webdriverfrom selenium.webdriver.chrome.options import Options
@pytest.fixturedef 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.fixturedef 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 driverUsing Fixtures in Tests
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 testdef driver_per_test(): driver = webdriver.Chrome() yield driver driver.quit()
@pytest.fixture(scope="class") # Shared within test classdef driver_per_class(): driver = webdriver.Chrome() yield driver driver.quit()
@pytest.fixture(scope="module") # Shared within moduledef driver_per_module(): driver = webdriver.Chrome() yield driver driver.quit()
@pytest.fixture(scope="session") # Shared across all testsdef driver_per_session(): driver = webdriver.Chrome() yield driver driver.quit()Browser Parametrization
import pytestfrom selenium import webdriver
def pytest_addoption(parser): parser.addoption( "--browser", action="store", default="chrome", help="Browser to run tests: chrome, firefox, edge" )
@pytest.fixturedef 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 pytestfrom 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) > 0Markers
import pytest
@pytest.mark.smokedef test_homepage_loads(driver): driver.get("https://example.com") assert "Example" in driver.title
@pytest.mark.regressiondef test_full_checkout_flow(driver): # Full flow test pass
@pytest.mark.slowdef 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): passRun specific markers: pytest -m smoke
Screenshot on Failure
import pytestimport osfrom 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.fixturedef 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
from selenium.webdriver.support.ui import WebDriverWaitfrom 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.pyfrom selenium.webdriver.common.by import Byfrom 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.pyfrom 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_urlUseful pytest Plugins
| Plugin | Purpose |
|---|---|
| pytest-html | HTML reports |
| pytest-xdist | Parallel execution |
| pytest-rerunfailures | Retry failed tests |
| pytest-timeout | Test timeouts |
| pytest-ordering | Control test order |
Parallel Execution
pip install pytest-xdistpytest -n 4 # Run 4 tests in parallelpytest -n auto # Auto-detect CPU countRetry Failed Tests
pip install pytest-rerunfailurespytest --reruns 3 --reruns-delay 2Next Steps
- Page Object Model - Structure page interactions
- Parallel Execution - Scale your tests
- Data-Driven Testing - External test data
- Screenshots and Videos - Capture test evidence
- TestNG and JUnit - Java framework comparison
Copy-paste recipes for this topic
- Soft Assertions: Check Several Things, Report All Failures
Verify multiple fields on one page and report every mismatch at once instead of stopping at the first.