# Setup Pytest Automation

> Initialize and configure a Pytest-based test automation framework with fixtures, plugins, and best practices. Use when: setting up Pytest for the first time, configuring Pytest fixtures, adding plugins (xdist, html, allure), organizing test structure with conftest.py.

- Skill: `bavithiranhardy14/setup-pytest-automation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bavithiranhardy14/setup-pytest-automation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bavithiranhardy14/setup-pytest-automation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: bavithiranhardy14 (https://skillmd.com/u/bavithiranhardy14)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/bavithiranhardy14/setup-pytest-automation

---


# Setup Pytest Automation Framework

## When to Use

- Creating a new Pytest test automation project
- Configuring Pytest fixtures for test setup/teardown
- Adding parallel execution with pytest-xdist
- Setting up HTML and Allure reporting
- Organizing tests with conftest.py

## Procedure

### 1. Install Dependencies

```bash
pip install pytest==7.4.0 pytest-xdist==3.3.1 pytest-html==3.2.0 \
    pytest-metadata==2.0.4 allure-pytest==2.13.2 python-dotenv==1.0.0 \
    playwright==1.36.0 pytest-playwright==0.3.0
```

### 2. Create pytest.ini

```ini
[pytest]
minversion = 7.0
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

markers =
    smoke: Smoke tests
    regression: Regression tests
    ui: UI tests
    playwright: Playwright tests
    mobile: Mobile tests
    multi_browser: Cross-browser tests

addopts =
    -v
    --tb=short
    --strict-markers
    --html=reports/report.html
    --self-contained-html
```

### 3. Create conftest.py

```python
import pytest
import os
from dotenv import load_dotenv
from playwright.sync_api import sync_playwright
from factories.browser_factory import PlaywrightBrowserFactory, PlaywrightPage
from config.settings import config
from utils.logger import get_logger

load_dotenv()
logger = get_logger(__name__)

# ── Session Fixtures ──────────────────────────────────────────

@pytest.fixture(scope="session")
def playwright_instance():
    """Start Playwright once for the entire session."""
    pw = sync_playwright().start()
    yield pw
    pw.stop()

# ── Function Fixtures ─────────────────────────────────────────

@pytest.fixture(scope="function")
def browser_context():
    """Fresh browser context per test."""
    playwright, browser, context = PlaywrightBrowserFactory.create_browser_context(
        browser_name=config.browser_type,
        headless=config.headless
    )
    yield context
    context.close()
    browser.close()
    playwright.stop()

@pytest.fixture(scope="function")
def page(browser_context):
    """Provide a Playwright page."""
    pg = browser_context.new_page()
    yield PlaywrightPage(pg)
    pg.close()

@pytest.fixture(scope="function")
def mobile_page():
    """Mobile browser page (iPhone 12)."""
    playwright, browser, context = PlaywrightBrowserFactory.create_mobile_browser("iPhone 12")
    pg = context.new_page()
    yield PlaywrightPage(pg)
    pg.close()
    context.close()
    browser.close()
    playwright.stop()

@pytest.fixture(params=["chromium", "firefox", "webkit"])
def multi_browser_page(request):
    """Parametrized cross-browser fixture."""
    playwright, browser, context = PlaywrightBrowserFactory.create_browser_context(request.param)
    pg = context.new_page()
    yield PlaywrightPage(pg)
    pg.close()
    context.close()
    browser.close()
    playwright.stop()

# ── Pytest Hooks ──────────────────────────────────────────────

def pytest_configure(config):
    config.addinivalue_line("markers", "smoke: smoke tests")
    config.addinivalue_line("markers", "regression: regression tests")

def pytest_collection_modifyitems(config, items):
    for item in items:
        if "ui" in str(item.fspath):
            item.add_marker(pytest.mark.ui)
        if "playwright" in str(item.fspath):
            item.add_marker(pytest.mark.playwright)

def pytest_runtest_logreport(report):
    if report.when == "call":
        if report.outcome == "passed":
            logger.info(f"PASS  {report.nodeid}")
        elif report.outcome == "failed":
            logger.error(f"FAIL  {report.nodeid}")

def pytest_sessionfinish(session, exitstatus):
    logger.info(f"Session finished — exit status: {exitstatus}")
```

### 4. config/settings.py

```python
import os
from dataclasses import dataclass
from dotenv import load_dotenv

load_dotenv()

@dataclass
class Config:
    browser_type: str = os.getenv("BROWSER", "chromium")
    headless: bool    = os.getenv("HEADLESS", "True").lower() == "true"
    base_url: str     = os.getenv("BASE_URL", "https://app.example.com")
    timeout: int      = int(os.getenv("TIMEOUT", "10000"))

config = Config()
```

### 5. Run Commands

```bash
# All tests
pytest tests/

# By marker
pytest tests/ -m smoke
pytest tests/ -m "playwright and not mobile"

# Parallel (4 workers)
pytest tests/ -n 4

# Specific browser
BROWSER=firefox pytest tests/

# Debug mode (headed)
HEADLESS=False pytest tests/

# With Allure report
pytest tests/ --alluredir=reports/allure-results
allure serve reports/allure-results
```

## Best Practices

- Session fixtures for expensive resources (browser launch)
- Function fixtures for test isolation (fresh context per test)
- Markers for selective test execution in CI/CD
- Hooks for logging, auto-marking, and reporting

