Running Tests with pytest / TestNG
pytest (Python) and TestNG (Java) are the industry-standard test runners for Selenium automation. They provide test organization, parameterization, parallel execution, fixtures, and rich reporting. Understanding how to structure and run your test suite at scale is what separates a script-writer from a professional SDET.
pytest for Selenium (Python)
# ══════════════════════════════════════════════════════════════
# PROJECT STRUCTURE
# ══════════════════════════════════════════════════════════════
# tests/
# conftest.py ← Shared fixtures (driver, base_url, test data)
# test_login.py ← Login tests
# test_checkout.py ← Checkout tests
# pytest.ini ← pytest configuration
# ── pytest.ini ────────────────────────────────────────────────
# [pytest]
# testpaths = tests
# addopts = -v --html=reports/report.html --self-contained-html
# markers =
# smoke: Quick sanity tests (fast)
# regression: Full regression suite (slow)
# api: API tests (no browser)
# ── conftest.py ───────────────────────────────────────────────
import pytest
from selenium import webdriver
@pytest.fixture(scope="session")
def base_url():
return "https://staging.myapp.com"
@pytest.fixture(scope="function")
def driver(request):
options = webdriver.ChromeOptions()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=options)
driver.maximize_window()
yield driver
driver.quit()
# ── test_login.py ─────────────────────────────────────────────
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestLoginSuite:
@pytest.mark.smoke
def test_login_valid_credentials(self, driver, base_url):
"""TC-LOGIN-001: Login with valid credentials"""
driver.get(f"{base_url}/login")
driver.find_element(By.ID, "email").send_keys("alice@test.com")
driver.find_element(By.ID, "password").send_keys("Test@1234")
driver.find_element(By.ID, "submit").click()
wait = WebDriverWait(driver, 10)
wait.until(EC.url_contains("/dashboard"))
assert "/dashboard" in driver.current_url
@pytest.mark.regression
@pytest.mark.parametrize("email,password,expected_error", [
("wrong@test.com", "Test@1234", "Invalid credentials"),
("alice@test.com", "wrongpass", "Invalid credentials"),
("", "Test@1234", "Email is required"),
("alice@test.com", "", "Password is required"),
])
def test_login_invalid_cases(self, driver, base_url, email, password, expected_error):
"""TC-LOGIN-002 to 005: Parameterized negative tests"""
driver.get(f"{base_url}/login")
driver.find_element(By.ID, "email").send_keys(email)
driver.find_element(By.ID, "password").send_keys(password)
driver.find_element(By.ID, "submit").click()
error_msg = driver.find_element(By.CSS_SELECTOR, ".error-message")
assert expected_error in error_msg.text
# ── RUNNING TESTS ────────────────────────────────────────────
# Run all tests: pytest tests/
# Run with verbose: pytest tests/ -v
# Run smoke tests only: pytest tests/ -m smoke
# Run in parallel (4 CPUs): pytest tests/ -n 4 (pip install pytest-xdist)
# Generate HTML report: pytest tests/ --html=report.htmlQuick Quiz — Selenium WebDriver Automation
Common Mistakes
- Putting all tests in one file — organize by feature; login tests in test_login.py, checkout in test_checkout.py — better maintenance and parallel execution
- Not using parameterize for similar test cases — 10 similar tests with different data should be 1 parameterized test, not 10 copy-pasted functions
- Class or module-scoped driver — sharing drivers between test functions causes test pollution; use function scope for isolation
- Not naming tests descriptively — test_1(), test_2() are useless in reports; name tests by their scenario: test_login_fails_with_locked_account()
Tip
Tip
Practice Running Tests with pytest TestNG in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
Technical diagram.
Practice Task
Note
Practice Task — (1) Write a working example of Running Tests with pytest TestNG from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Common Mistake
Warning
A common mistake with Running Tests with pytest TestNG is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready software testing code.
Key Takeaways
- pytest (Python) and TestNG (Java) are the industry-standard test runners for Selenium automation.
- Putting all tests in one file — organize by feature; login tests in test_login.py, checkout in test_checkout.py — better maintenance and parallel execution
- Not using parameterize for similar test cases — 10 similar tests with different data should be 1 parameterized test, not 10 copy-pasted functions
- Class or module-scoped driver — sharing drivers between test functions causes test pollution; use function scope for isolation