Claude Workflow — Detailed Step Reference
Deep reference only. The inline workflows in
CLAUDE.mdSection 8 are the first stop for every task. Read this document only when you need more detail than those decision trees provide — for example, when creating a page object from scratch, adding a device profile, or answering a framework architecture question.Do NOT read this before starting a task. Read
CLAUDE.mdfirst and act from there.
Task Type Identification
Read the user's request and identify which type it is:
| Type | Examples | Go to section |
|---|---|---|
| Add test(s) | "add a test for the login button", "write smoke tests for navigation" | § A |
| Update test(s) | "the selector changed", "update this test to check the new error message" | § B |
| Remove test(s) | "this feature was removed", "delete the bet slip tests" | § C |
| Add page object | "add a page object for the sports page" | § D |
| Add component | "add a component for the bottom nav" | § E |
| Add device profile | "add Samsung Galaxy S22 profile" | § F |
| Framework question | "how does the retry logic work?" | § G |
| Ambiguous / risky | requires touching core/, conftest.py, config/settings.py | § H |
§ A — Adding Tests
Step 1: Research (do not skip)
1. Read docs/TEST_COVERAGE_SPEC.md
→ Is this test already listed? If YES, update its status when done.
→ Is this test already implemented? If YES, do not duplicate it.
2. Check tests/e2e/ for existing test files covering this page/feature.
→ If a file exists, add to it. Do not create a new file for the same feature area.
3. Check pages/ for an existing page object.
→ If page object exists, use it.
→ If page object does not exist, create it first (follow § D).
Step 2: Choose the right fixture
Does the test require the user to be logged in?
YES → use authenticated_page
NO → continue
Must this test always run on mobile, regardless of --device-profile?
YES → use mobile_page
NO → continue
Must this test always run on desktop?
YES → use desktop_page
NO → use page (inherits active device profile — most common)
Step 3: Choose the right markers
Every test must have at least two markers:
Primary tier: @pytest.mark.smoke OR @pytest.mark.regression
Secondary category: @pytest.mark.e2e (always, for browser tests)
Device (if forced): @pytest.mark.mobile OR @pytest.mark.desktop
Smoke = fast, critical path only (< 10s). Regression = thorough, can be slower.
Step 4: Write the test — AAA pattern, strictly
@pytest.mark.smoke
@pytest.mark.e2e
def test_<behaviour_description>(page: Page, settings: Settings) -> None:
"""One sentence: what user action and expected outcome this verifies."""
# Arrange — set up the page/state
sports = SportsPage(page, settings)
sports.load()
# Act — perform the user action
sports.tap_live_tab()
# Assert — verify the outcome the user sees
assert sports.is_live_section_visible(), "Live section did not appear after tapping Live tab."
Step 5: Validate before finishing
- Test function name starts with
test_and describes behaviour, not implementation - No raw selectors (
[data-testid=...]) in the test file - No
time.sleep()calls - Test passes in isolation:
pytest tests/e2e/test_<file>.py::test_<name> -v - Existing tests still pass:
pytest tests/e2e/ -v --tb=short - Update
docs/TEST_COVERAGE_SPEC.mdstatus for this test
§ B — Updating Tests
Step 1: Understand why the test needs updating
A) Selector changed on the page → update the selector in the PAGE OBJECT (not the test)
B) Expected behaviour changed → update the assertion in the test
C) New step added to the flow → add the action to the page object, call it in the test
D) Test is flaky → add a wait or increase timeout in the page object
Step 2: Find all affected locations
Before editing, search for every usage of the method/selector being changed:
grep -r "method_name_or_selector" pages/ tests/ components/
Step 3: Make the minimal change
- Change only what is needed. Do not refactor surrounding code.
- If the selector changed, update the
_PRIVATE_CONSTANTin the page object only. - If the flow changed, update or add methods in the page object, then update the test call.
Step 4: Verify
# Run the changed test
pytest tests/e2e/test_<file>.py::test_<name> -v -s
# Run the full file to check nothing else broke
pytest tests/e2e/test_<file>.py -v --tb=short
# Run the full suite
pytest tests/e2e/ -v --tb=short
§ C — Removing Tests
Only remove a test if:
- The feature it tests has been removed from the app
- The test is a duplicate (identical behaviour tested elsewhere)
- Explicitly told to remove it
Steps:
- Confirm the test is not referenced anywhere else (no shared fixtures that other tests depend on)
- Delete the test function
- If the test file is now empty, delete the file
- If the page object has methods used only by this deleted test, remove those methods too
- Update
docs/TEST_COVERAGE_SPEC.md— mark as removed or delete the row
§ D — Adding a Page Object
When to create a new page object
Create a new page object when:
- A test needs to interact with a page that has no page object yet
- Never create a page object just to have one — only create when a test needs it
Steps
- Create
pages/<page_name>_page.py - Inherit from
BasePage - Define
_PATHand selector constants as_PRIVATE_CONSTANTS - Implement
load()— navigate + wait for a stable identifying element - Implement
is_loaded()— pure visibility check, no side effects - Add action methods for each user interaction
- Add query methods for each readable value
Template:
from __future__ import annotations
from playwright.sync_api import Page
from config.settings import Settings
from core.base_page import BasePage
from utils.logger import get_logger
logger = get_logger(__name__)
class <Name>Page(BasePage):
_PATH: str = "/<path>"
_IDENTIFYING_ELEMENT: str = "[data-testid='<unique-element>']"
def __init__(self, page: Page, config: Settings) -> None:
super().__init__(page, config)
def load(self) -> None:
self.navigate(self._PATH)
self.wait_for_element(self._IDENTIFYING_ELEMENT)
logger.info("<Name>Page loaded.")
def is_loaded(self) -> bool:
return self.is_element_visible(self._IDENTIFYING_ELEMENT)
- Verify: import the page object in a test and call
load()+is_loaded()
§ E — Adding a Component
When to create a new component
Create a component when a UI fragment (bottom nav, score card, modal, carousel) appears on more than one page, or when its interaction logic is complex enough to warrant encapsulation.
Steps
- Create
components/<component_name>.py - Inherit from
BaseComponent - Define
_ROOTselector that uniquely scopes the component - Pass
config=configtosuper().__init__()— mandatory - Implement
is_visible()usingself.root.is_visible() - Add scoped action and query methods using
self.locator(child_selector)
Compose into a page by adding to __init__:
# In the page object
self.bottom_nav = BottomNav(page, config=config) # always pass config=config
§ F — Adding a Device Profile
- Open
config/devices.py - Add a new entry to
DEVICE_PROFILES:
"device_key": DeviceConfig(
name="device_key",
viewport_width=<width>,
viewport_height=<height>,
is_mobile=<True/False>,
has_touch=<True/False>,
device_scale_factor=<float>,
),
- That's it. Nothing else changes.
- Use it:
pytest --device-profile=device_key tests/
§ G — Answering Framework Questions
| Question type | Read this doc |
|---|---|
| Architecture, layers, env/device switching | docs/FRAMEWORK_ARCHITECTURE.md |
| How to build a page object or component | docs/PAGE_OBJECT_GUIDE.md |
| Test patterns, fixtures, markers | docs/TEST_WRITING_GUIDE.md |
| Scroll, swipe, touch, forms, nav, modals | docs/INTERACTION_PATTERNS.md |
| What needs to be tested, status | docs/TEST_COVERAGE_SPEC.md |
| Can I edit file X? | docs/DO_NOT_TOUCH.md |
| AI assertion / visual comparison | docs/AI_INTEGRATION_GUIDE.md |
| Source of truth for any rule | CLAUDE.md |
§ H — Ambiguous or Risky Tasks
If the task:
- Requires touching
core/,conftest.py,config/settings.py, orpytest.ini - Is unclear about what page or behaviour to test
- Would require knowing real selectors from the live site (which you can't see)
- Cannot be done without breaking an existing passing test
Do this:
- State clearly what you understand the task to be
- State what is unclear or risky
- State what you would need to proceed safely
- Do not make assumptions. Do not implement partial solutions.
- Wait for clarification before writing any code
Common Mistakes to Avoid
| Mistake | Correct approach |
|---|---|
| Creating a new test file when one already exists for that feature | Add to the existing file |
| Writing a selector directly in a test | Put it in the page object as a _PRIVATE_CONSTANT |
Using page.click() directly in a test | Call a page object method that calls safe_click() |
| Creating a page object with no test that uses it | Only create when a test needs it |
| Running only the new test and calling it done | Always run the full tests/e2e/ suite |
Forgetting config=config when composing a component | Every component must receive config |
Using assert page.locator(...) in a test | Use a page object query method |
Implementing load() without a wait | load() must wait for a stable element |