DO NOT TOUCH
This document lists files and patterns that must not be modified without a deliberate architectural decision. Careless edits to these files break every test in the suite.
If you believe a change is necessary, read the "What to do instead" column first. If the guidance does not cover your case, discuss it with the team before editing.
Protected Files
core/base_page.py
Why protected: Every page object in pages/ inherits from this class. Changes to method signatures, retry logic, or the load() / is_loaded() contract break all page objects simultaneously.
| Do NOT | Do instead |
|---|---|
Change _retry() signature | Add an overloaded variant alongside it |
Change safe_click() / safe_fill() signatures | Add a new method with the extra parameter |
Remove or rename load() / is_loaded() | These are the LSP contract — they cannot change |
Change navigate() to hardcode wait_until | Set NAVIGATION_WAIT_UNTIL= in .env or update the device profile |
core/base_component.py
Why protected: Every UI component in components/ inherits from this class. Same risks as base_page.py.
| Do NOT | Do instead |
|---|---|
Remove the config parameter | It is the DIP fix — hardcoded constants must not return |
Change _retry() | Mirror any base_page.py changes here too |
Change root property semantics | Components depend on this for scoped selectors |
core/browser_manager.py
Why protected: Owns the Playwright browser lifecycle. Incorrect changes cause browser leaks, double-launch errors, or artifact loss.
| Do NOT | Do instead |
|---|---|
Hardcode viewport / is_mobile / has_touch | These must come from DeviceConfig via Settings.get_device_config() |
Call playwright.stop() inside new_context() | Only call it in stop() / __exit__() |
| Add test-framework logic here | This class knows nothing about pytest |
config/settings.py
Why protected: Settings is instantiated once per session. Changes to field names silently break .env files across all environments.
| Do NOT | Do instead |
|---|---|
| Rename an existing field | The env var name is the field name — renaming breaks all .env files |
Remove the _validate_device validator | It provides fail-fast feedback for unknown profiles |
| Hard-code values as field defaults instead of reading from env | Every value that differs between environments must come from .env |
Change model_config env-file priority order | It is intentional: real env vars beat .env files |
config/devices.py
Why protected: DeviceConfig is a frozen dataclass — its fields map directly to Playwright BrowserContext options. Adding a field requires updating BrowserManager.new_context() simultaneously.
| Do NOT | Do instead |
|---|---|
Remove a field from DeviceConfig | It will break BrowserManager which reads all fields |
| Change a field name | Update BrowserManager.new_context() in the same commit |
Delete a profile from DEVICE_PROFILES | Any .env file using that profile will fail validation on startup |
Safe to do: Add new profile entries to DEVICE_PROFILES — this never breaks anything.
conftest.py
Why protected: All pytest fixtures are defined here. Incorrect scope changes cause resource leaks or test isolation failures.
| Do NOT | Do instead |
|---|---|
Change settings from session to function scope | Settings never mutate — session scope is correct and faster |
Change browser_manager from session scope | One browser per session is intentional — function scope leaks processes |
Move pytest_configure or pytest_addoption elsewhere | pytest requires these in conftest.py at the root level |
| Add test logic to fixtures | Fixtures provide resources; tests assert behaviour |
.env (local file)
Why protected: Contains real credentials and local configuration. Must never be committed.
| Do NOT | Do instead |
|---|---|
Commit .env to git | It is in .gitignore — update .env.example instead |
Put AUTH_PASSWORD in .env.example | .env.example is committed — never put real secrets there |
Share .env via Slack / email | Use a secrets manager or a team password vault |
pytest.ini — the addopts line
Why protected: --strict-markers in addopts ensures every marker used in a test is declared. Removing it hides typos in marker names.
| Do NOT | Do instead |
|---|---|
Remove --strict-markers | Fix the marker — either add it to markers = or correct the typo |
Add --no-header or -p no:warnings globally | Only suppress warnings you understand and accept |
Protected Patterns
Never put raw Playwright calls in test files
# ✗ FORBIDDEN in tests/
page.click("[data-testid='submit']")
page.locator(".btn").click()
page.fill("#email", "x")
All Playwright interactions belong in page objects or components.
Never access _private attributes of page objects from tests
# ✗ FORBIDDEN
login._SUBMIT_BTN # private selector constant
login._page.goto(...) # bypasses the page object entirely
login._config # internal config access
Never import Settings to read selectors or paths
# ✗ FORBIDDEN
settings.base_url + "/login" # use LoginPage.load() instead
Never use time.sleep() in tests or page objects
# ✗ FORBIDDEN
import time
time.sleep(3)
Use wait_for_element(), wait_for_load_state(), or page.wait_for_timeout() sparingly.
Never call browser_manager.stop() inside a test
The browser lifecycle is managed by the session-scoped browser_manager fixture. Calling stop() inside a test kills the browser for all subsequent tests in the session.
Safe Change Checklist
Before editing any protected file, verify:
- I have read the "Do instead" column above
- I have searched for all callers of the method / field I am changing
- I am updating tests, page objects, and components in the same commit
- CI passes on both
--device-profile=mobileand--device-profile=desktop - I have updated this document if the protection rules changed