Skip to main content

Claude Workflow — Detailed Step Reference

Deep reference only. The inline workflows in CLAUDE.md Section 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.md first and act from there.


Task Type Identification

Read the user's request and identify which type it is:

TypeExamplesGo 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 / riskyrequires 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.md status 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_CONSTANT in 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:

  1. Confirm the test is not referenced anywhere else (no shared fixtures that other tests depend on)
  2. Delete the test function
  3. If the test file is now empty, delete the file
  4. If the page object has methods used only by this deleted test, remove those methods too
  5. 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

  1. Create pages/<page_name>_page.py
  2. Inherit from BasePage
  3. Define _PATH and selector constants as _PRIVATE_CONSTANTS
  4. Implement load() — navigate + wait for a stable identifying element
  5. Implement is_loaded() — pure visibility check, no side effects
  6. Add action methods for each user interaction
  7. 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)
  1. 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

  1. Create components/<component_name>.py
  2. Inherit from BaseComponent
  3. Define _ROOT selector that uniquely scopes the component
  4. Pass config=config to super().__init__() — mandatory
  5. Implement is_visible() using self.root.is_visible()
  6. 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

  1. Open config/devices.py
  2. 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>,
),
  1. That's it. Nothing else changes.
  2. Use it: pytest --device-profile=device_key tests/

§ G — Answering Framework Questions

Question typeRead this doc
Architecture, layers, env/device switchingdocs/FRAMEWORK_ARCHITECTURE.md
How to build a page object or componentdocs/PAGE_OBJECT_GUIDE.md
Test patterns, fixtures, markersdocs/TEST_WRITING_GUIDE.md
Scroll, swipe, touch, forms, nav, modalsdocs/INTERACTION_PATTERNS.md
What needs to be tested, statusdocs/TEST_COVERAGE_SPEC.md
Can I edit file X?docs/DO_NOT_TOUCH.md
AI assertion / visual comparisondocs/AI_INTEGRATION_GUIDE.md
Source of truth for any ruleCLAUDE.md

§ H — Ambiguous or Risky Tasks

If the task:

  • Requires touching core/, conftest.py, config/settings.py, or pytest.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:

  1. State clearly what you understand the task to be
  2. State what is unclear or risky
  3. State what you would need to proceed safely
  4. Do not make assumptions. Do not implement partial solutions.
  5. Wait for clarification before writing any code

Common Mistakes to Avoid

MistakeCorrect approach
Creating a new test file when one already exists for that featureAdd to the existing file
Writing a selector directly in a testPut it in the page object as a _PRIVATE_CONSTANT
Using page.click() directly in a testCall a page object method that calls safe_click()
Creating a page object with no test that uses itOnly create when a test needs it
Running only the new test and calling it doneAlways run the full tests/e2e/ suite
Forgetting config=config when composing a componentEvery component must receive config
Using assert page.locator(...) in a testUse a page object query method
Implementing load() without a waitload() must wait for a stable element