Skip to main content

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 NOTDo instead
Change _retry() signatureAdd an overloaded variant alongside it
Change safe_click() / safe_fill() signaturesAdd 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_untilSet 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 NOTDo instead
Remove the config parameterIt is the DIP fix — hardcoded constants must not return
Change _retry()Mirror any base_page.py changes here too
Change root property semanticsComponents 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 NOTDo instead
Hardcode viewport / is_mobile / has_touchThese 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 hereThis 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 NOTDo instead
Rename an existing fieldThe env var name is the field name — renaming breaks all .env files
Remove the _validate_device validatorIt provides fail-fast feedback for unknown profiles
Hard-code values as field defaults instead of reading from envEvery value that differs between environments must come from .env
Change model_config env-file priority orderIt 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 NOTDo instead
Remove a field from DeviceConfigIt will break BrowserManager which reads all fields
Change a field nameUpdate BrowserManager.new_context() in the same commit
Delete a profile from DEVICE_PROFILESAny .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 NOTDo instead
Change settings from session to function scopeSettings never mutate — session scope is correct and faster
Change browser_manager from session scopeOne browser per session is intentional — function scope leaks processes
Move pytest_configure or pytest_addoption elsewherepytest requires these in conftest.py at the root level
Add test logic to fixturesFixtures provide resources; tests assert behaviour

.env (local file)

Why protected: Contains real credentials and local configuration. Must never be committed.

Do NOTDo instead
Commit .env to gitIt 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 / emailUse 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 NOTDo instead
Remove --strict-markersFix the marker — either add it to markers = or correct the typo
Add --no-header or -p no:warnings globallyOnly 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=mobile and --device-profile=desktop
  • I have updated this document if the protection rules changed