IMPORTANT: This file contains team-wide Python coding standards and is committed to version control. All team members should follow these standards. For personal preferences or local development overrides, create a CLAUDE.local.md file (gitignored, not tracked in version control).
This file contains Python-specific coding standards and best practices. These supplement the language-agnostic rules in CLAUDE.md.
- Use camelCase for variable, function, and method names
- Use PascalCase for class names
- Use UPPER_CASE for constants and environment variables
- Use all lower-case filenames (with no underscores) composed of (at most) a three word description of the purpose of the file
- Use Python 3.11 syntax and features
- Do not use type hints from the typing module unless needed for a dataclass
- Follow the style guide of the Black project except where it conflicts with rules in CLAUDE.md or this file
- Use two spaces for indentation (not tabs)
- Wrap lines at 120 characters
- Do NOT leave space characters at the end of a line
- Run
spacinglinter first to ensure blank line consistency (if not installed, then runpip install spacing) - Run
ruff checkandruff formatto ensure code quality (if not installed, then runpip install ruff) - Follow ruff configuration settings in
pyproject.tomlin the git repo root - Common ruff violations to avoid:
- E722: Use specific exception types, not bare
except: - F401: Remove unused imports
- E501: Line too long (follow 120 character limit)
- E722: Use specific exception types, not bare
- Use
__init__.pyfiles for package initialization - Use
__main__.pyfile for executable packages (enablespython -m package_name) - Use pyproject.toml for Python dependency management instead of requirements.txt
- Follow PEP 621 standards for project metadata and dependency specification
- Use virtual environment
.venvin the git repo root for Python changes
- Use single quotes for strings
- Use triple double quotes for docstrings
- Use explicit imports and not wildcard imports
- Use absolute imports and not relative imports
- Keep imports at the top of a function, method, or file
- Include reST style docstrings for all functions and classes
- Use
typeandrtypeannotations in docstrings
Example:
def calculateTotal(items):
"""
Calculate the total price of items.
:type items: list
:param items: List of items with price attributes
:rtype: float
:return: Total price of all items
"""
return sum(item.price for item in items)- Use the filename
test_foo.pyfor thefoo.pymodule - Put all unit test files in a
test/subdirectory with a structure that models the project structure- For example: core/foo.py => test/core/test_foo.py
- Write unit tests using pytest and mocker
- Always run tests using
pytest(notpython -m pytest) - Override the rule for function names in the test suite for functions that are a test function:
- use a prefix
test_followed by a suffix in camelCase describing the purpose of the test- For example:
test_checkForInvalidCeIdortest_auditFeatureFileAssociationNoIssues
- For example:
- use a prefix
- When fixing tests, only run the failing tests during iteration
- Prefer short, to-the-point tests that test situations corresponding to a single use case
- Do not call private methods directly inside unit tests
- Never mock methods of the class under test
- Use mocks only when necessary, for example:
- when using a third-party interface (e.g, an API call) always use a mock
- when natural set up would be too difficult (e.g., a race condition)
Use Playwright for end-to-end (E2E) testing of web applications:
- Testing complete user flows through the browser (login, checkout, workflows)
- Validating UI interactions and visual behavior
- Cross-browser compatibility testing (Chromium, Firefox, WebKit)
- Testing JavaScript-heavy applications where DOM manipulation is critical
Do not use Playwright for:
- Unit tests of business logic (use pytest instead)
- API testing without UI (use requests or httpx)
- Performance testing (use specialized tools)
Step 1: Install Python package
pip install pytest-playwrightStep 2: Install browser binaries (required!)
playwright installAdd to dependencies in pyproject.toml:
[tool.poetry.dependencies]
pytest = "^8.3.4"
pytest-playwright = "^0.6.2"Or in requirements.txt:
pytest>=7.4.3
pytest-playwright>=0.4.3
Directory Structure
Place Playwright E2E tests in a separate directory from unit tests:
project/
├── src/ # Source code
├── tests/ # Unit tests
│ ├── test_models.py
│ └── test_services.py
├── tests/e2e/ # E2E tests (recommended)
│ ├── test_login.py
│ ├── test_checkout.py
│ └── conftest.py # Shared fixtures
└── pyproject.toml
Alternative structure (top-level e2e directory):
project/
├── src/
├── tests/ # Unit tests only
├── e2e/ # E2E tests
│ ├── test_login.py
│ └── conftest.py
└── pyproject.toml
Why separate directories?
- E2E tests are slower than unit tests
- Different test scope and purpose
- Allows running unit tests separately for fast feedback
- Clear separation of concerns
Test File Naming
Follow pytest conventions:
- File names:
test_*.py(e.g.,test_login.py,test_checkout.py) - Test functions:
def test_*(e.g.,def test_successful_login(page):)
Use the existing /test command with directory path:
# Run all E2E tests
/test tests/e2e/
# Or if using top-level e2e/ directory
/test e2e/
# Run specific E2E test file
/test tests/e2e/test_login.py
# Run specific test function
/test tests/e2e/test_login.py::test_successful_loginUsing pytest directly (for Playwright-specific options):
# Run with visible browser (headed mode)
pytest tests/e2e/ --headed
# Run with specific browser
pytest tests/e2e/ --browser firefox
pytest tests/e2e/ --browser webkit
# Run with multiple browsers
pytest tests/e2e/ --browser chromium --browser firefox
# Run with screenshots on failure
pytest tests/e2e/ --screenshot only-on-failure
# Run with tracing (for debugging)
pytest tests/e2e/ --tracing on
# Slow motion for debugging (milliseconds)
pytest tests/e2e/ --headed --slowmo 1000pytest.ini (optional, for default Playwright options):
[pytest]
# Separate E2E tests from unit tests
testpaths = tests
# Playwright default options (applies to all E2E tests)
addopts =
--browser chromium
--screenshot only-on-failureconftest.py (for custom fixtures):
import pytest
@pytest.fixture(scope='session')
def browser_context_args(browser_context_args):
"""Customize browser context for all tests."""
return {
**browser_context_args,
'viewport': {
'width': 1920,
'height': 1080,
},
'ignore_https_errors': True,
}
@pytest.fixture(scope='session')
def browser_type_launch_args(browser_type_launch_args):
"""Customize browser launch options."""
return {
**browser_type_launch_args,
'headless': False, # Run headed by default
}Basic Test Example:
from playwright.sync_api import Page, expect
def test_successful_login(page: Page):
"""Test user can login with valid credentials."""
page.goto('https://example.com/login')
page.get_by_label('Username').fill('testuser')
page.get_by_label('Password').fill('password123')
page.get_by_role('button', name='Login').click()
# Playwright auto-waits for elements
expect(page).to_have_url(re.compile(r'.*/dashboard'))
expect(page.get_by_text('Welcome')).to_be_visible()Page Object Pattern (recommended for maintainability):
pages/login_page.py:
from playwright.sync_api import Page
class LoginPage:
def __init__(self, page: Page):
self.page = page
self.username_input = page.get_by_label('Username')
self.password_input = page.get_by_label('Password')
self.login_button = page.get_by_role('button', name='Login')
def navigate(self):
self.page.goto('https://example.com/login')
def login(self, username: str, password: str):
self.username_input.fill(username)
self.password_input.fill(password)
self.login_button.click()tests/e2e/test_login.py:
import pytest
from playwright.sync_api import Page
from pages.login_page import LoginPage
@pytest.fixture
def login_page(page: Page):
return LoginPage(page)
def test_login_with_page_object(login_page):
"""Test login using Page Object pattern."""
login_page.navigate()
login_page.login('testuser', 'password123')
# Assert successful login...Fixtures and Isolation
- Use
pagefixture provided by pytest-playwright (function scope, isolated per test) - Each test gets a fresh browser context and page
- No shared state between tests
API vs Sync
- Always use sync API with pytest-playwright (not async/await)
- Import from
playwright.sync_api, notplaywright.async_api
Locators
- Prefer role-based locators:
page.get_by_role('button', name='Submit') - Use label-based locators:
page.get_by_label('Email') - Use text-based locators:
page.get_by_text('Welcome') - Avoid CSS selectors when possible (more brittle)
Auto-Waiting
- Playwright auto-waits for elements to be actionable
- Avoid manual
time.sleep()- usepage.wait_for_selector()if needed - Use
expect()for assertions with built-in waiting
Test Organization
- One test per user scenario
- Keep tests focused and independent
- Use Page Objects for complex pages
- Share setup via fixtures in
conftest.py
Debugging
- Run with
--headedto see browser - Use
--slowmo 1000to slow down actions - Use
page.pause()to pause execution and inspect - Enable
--tracing onto record test execution
Performance
- E2E tests are slower - keep suite manageable
- Run E2E tests separately from unit tests
- Consider parallel execution:
pytest tests/e2e/ -n auto(requires pytest-xdist) - Mock external APIs when possible to improve speed
Browser binaries not found
Error: "chromium" browser was not found
Solution: Run playwright install after installing package
Virtual environment path issues
If browsers installed globally but script runs in venv:
# Install browsers in virtual environment
PLAYWRIGHT_BROWSERS_PATH=0 playwright installTimeout errors
If pages load slowly:
# Increase timeout for specific operation
page.goto('https://example.com/', timeout=60000) # 60 seconds
# Or set default timeout
page.set_default_timeout(30000) # 30 secondsTests are flaky
Causes: Race conditions, network timing, element availability
Solutions:
- Use Playwright's auto-waiting (built-in)
- Use proper locators (
get_by_role,get_by_label) - Avoid
time.sleep(), usewait_for_selector()instead - Use
expect()assertions with built-in retry logic