517 lines
21 KiB
Python
517 lines
21 KiB
Python
"""
|
||
Step 8 — Execution Mode Tests
|
||
|
||
- test_start_session_navigates_to_execute: Clicking play from detail view navigates to execute view
|
||
- test_session_list_play_buttons: Play buttons appear on each session card in the list
|
||
- test_execute_view_elements: Execute view shows title, progress, current step, next step
|
||
- test_steps_advance_on_done: Pressing Done advances to the next step
|
||
- test_reps_weight_editable_and_saved: Reps and weight inputs are editable and saved to log
|
||
- test_session_complete_after_last_step: Completing all steps redirects to session detail
|
||
- test_asymmetric_side_shown: Side toggle visible for asymmetric exercises only
|
||
- test_asymmetric_side_logged: Side value is saved in the execution log
|
||
- test_combo_rounds_auto_advance: Non-final combo rounds advance silently (no pause screen)
|
||
- test_final_round_screen_shown: Final-round screen appears after last exercise of last round
|
||
- test_continue_advances_past_combo: Continue button advances to next session item
|
||
- test_one_more_round_appends_round: One More Round inserts an extra round into the queue
|
||
- test_exit_mid_session_saves_partial_log: Exit saves only completed items to log
|
||
- test_prefill_from_last_session_run: Re-executing a session prefills from last run
|
||
- test_no_prior_log_uses_defaults: No prior log uses session item defaults for prefill
|
||
- test_progress_bar_reflects_position: Progress label updates as steps are completed
|
||
- test_combo_context_shown: Combo name and round number shown in step card for combo exercises
|
||
"""
|
||
import pytest
|
||
from playwright.sync_api import Page, expect
|
||
|
||
|
||
def _wait_for_db(page: Page, app_url: str):
|
||
page.goto(app_url)
|
||
page.wait_for_function(
|
||
"document.documentElement.getAttribute('data-db-ready') === 'true'",
|
||
timeout=15000,
|
||
)
|
||
|
||
|
||
def _wait_for_execute(page: Page):
|
||
"""Wait for the execute view to be fully loaded."""
|
||
page.wait_for_selector('[data-testid="execute-session-title"]', timeout=10000)
|
||
page.wait_for_timeout(300)
|
||
|
||
|
||
def _create_exercise(page: Page, title: str, asymmetric=False, alternate=False,
|
||
default_reps=10, default_weight=0):
|
||
return page.evaluate(f"""async () => {{
|
||
const userId = await window.__repos.settings.get('user_uuid')
|
||
return window.__repos.exercises.create({{
|
||
title: {repr(title)},
|
||
description: '',
|
||
asymmetric: {'true' if asymmetric else 'false'},
|
||
alternate: {'true' if alternate else 'false'},
|
||
image_urls: '[]',
|
||
video_urls: '[]',
|
||
default_reps: {default_reps},
|
||
default_weight: {default_weight},
|
||
created_by: userId
|
||
}})
|
||
}}""")
|
||
|
||
|
||
def _create_session_with_exercises(page: Page, title: str, items: list):
|
||
"""items: list of dicts with exerciseId, reps, weight."""
|
||
items_json = str(items).replace("'", '"').replace("True", "true").replace("False", "false")
|
||
return page.evaluate(f"""async () => {{
|
||
const userId = await window.__repos.settings.get('user_uuid')
|
||
const sessId = await window.__repos.sessions.create({{
|
||
title: {repr(title)}, description: '', created_by: userId
|
||
}})
|
||
const items = {items_json}
|
||
const mapped = items.map((it, i) => ({{
|
||
item_id: it.item_id, item_type: it.item_type,
|
||
position: i, repetitions: it.repetitions, weight: it.weight
|
||
}}))
|
||
await window.__repos.sessions.setItems(sessId, mapped)
|
||
return sessId
|
||
}}""")
|
||
|
||
|
||
def _create_combo(page: Page, title: str, exercises: list):
|
||
"""exercises: list of {{exerciseId, defaultReps, defaultWeight}}."""
|
||
exercises_json = str(exercises).replace("'", '"').replace("True", "true").replace("False", "false")
|
||
return page.evaluate(f"""async () => {{
|
||
const userId = await window.__repos.settings.get('user_uuid')
|
||
const comboId = await window.__repos.combos.create({{
|
||
title: {repr(title)}, description: '', type: 'NONE',
|
||
timer_config: {{}}, created_by: userId
|
||
}})
|
||
const exercises = {exercises_json}
|
||
await window.__repos.combos.setExercises(comboId, exercises.map(e => ({{
|
||
exerciseId: e.exerciseId,
|
||
defaultReps: e.defaultReps,
|
||
defaultWeight: e.defaultWeight
|
||
}})))
|
||
return comboId
|
||
}}""")
|
||
|
||
|
||
def _clean_all(page: Page):
|
||
page.evaluate("""async () => {
|
||
const sessions = await window.__repos.sessions.getAll()
|
||
for (const s of sessions) await window.__repos.sessions.delete(s.id)
|
||
const combos = await window.__repos.combos.getAll()
|
||
for (const c of combos) await window.__repos.combos.delete(c.id)
|
||
const exercises = await window.__repos.exercises.getAll()
|
||
for (const e of exercises) await window.__repos.exercises.delete(e.id)
|
||
}""")
|
||
|
||
|
||
# ── fixtures ──────────────────────────────────────────────────────────────
|
||
|
||
|
||
@pytest.fixture
|
||
def simple_session(page: Page, app_url: str):
|
||
"""A session with one exercise."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
ex_id = _create_exercise(page, "Press", default_reps=8, default_weight=50)
|
||
sess_id = _create_session_with_exercises(page, "Simple Session", [
|
||
{"item_id": ex_id, "item_type": "exercise", "repetitions": 8, "weight": 50}
|
||
])
|
||
return {"sess_id": sess_id, "ex_id": ex_id}
|
||
|
||
|
||
@pytest.fixture
|
||
def two_step_session(page: Page, app_url: str):
|
||
"""A session with two exercises."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
ex1 = _create_exercise(page, "Step One", default_reps=5, default_weight=30)
|
||
ex2 = _create_exercise(page, "Step Two", default_reps=10, default_weight=0)
|
||
sess_id = _create_session_with_exercises(page, "Two Step Session", [
|
||
{"item_id": ex1, "item_type": "exercise", "repetitions": 5, "weight": 30},
|
||
{"item_id": ex2, "item_type": "exercise", "repetitions": 10, "weight": 0},
|
||
])
|
||
return {"sess_id": sess_id, "ex1": ex1, "ex2": ex2}
|
||
|
||
|
||
@pytest.fixture
|
||
def asymmetric_session(page: Page, app_url: str):
|
||
"""A session with an asymmetric exercise."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
ex_id = _create_exercise(page, "Lunge", asymmetric=True, alternate=True,
|
||
default_reps=12, default_weight=0)
|
||
sess_id = _create_session_with_exercises(page, "Asym Session", [
|
||
{"item_id": ex_id, "item_type": "exercise", "repetitions": 12, "weight": 0}
|
||
])
|
||
return {"sess_id": sess_id, "ex_id": ex_id}
|
||
|
||
|
||
@pytest.fixture
|
||
def combo_session(page: Page, app_url: str):
|
||
"""A session with a 2-exercise combo run for 2 rounds."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
ex1 = _create_exercise(page, "Squat", default_reps=15, default_weight=0)
|
||
ex2 = _create_exercise(page, "Jump", default_reps=20, default_weight=0)
|
||
combo_id = _create_combo(page, "HIIT", [
|
||
{"exerciseId": ex1, "defaultReps": 15, "defaultWeight": 0},
|
||
{"exerciseId": ex2, "defaultReps": 20, "defaultWeight": 0},
|
||
])
|
||
sess_id = _create_session_with_exercises(page, "Combo Session", [
|
||
{"item_id": combo_id, "item_type": "combo", "repetitions": 2, "weight": 0}
|
||
])
|
||
return {"sess_id": sess_id, "combo_id": combo_id, "ex1": ex1, "ex2": ex2}
|
||
|
||
|
||
@pytest.fixture
|
||
def mixed_session(page: Page, app_url: str):
|
||
"""A session with an exercise followed by a combo."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
ex_solo = _create_exercise(page, "Solo Ex", default_reps=5, default_weight=0)
|
||
ex_c1 = _create_exercise(page, "Combo Ex1", default_reps=10, default_weight=0)
|
||
ex_c2 = _create_exercise(page, "Combo Ex2", default_reps=10, default_weight=0)
|
||
combo_id = _create_combo(page, "Mini Combo", [
|
||
{"exerciseId": ex_c1, "defaultReps": 10, "defaultWeight": 0},
|
||
{"exerciseId": ex_c2, "defaultReps": 10, "defaultWeight": 0},
|
||
])
|
||
sess_id = _create_session_with_exercises(page, "Mixed Session", [
|
||
{"item_id": ex_solo, "item_type": "exercise", "repetitions": 5, "weight": 0},
|
||
{"item_id": combo_id, "item_type": "combo", "repetitions": 1, "weight": 0},
|
||
])
|
||
return {"sess_id": sess_id}
|
||
|
||
|
||
# ── tests ─────────────────────────────────────────────────────────────────
|
||
|
||
|
||
def test_start_session_navigates_to_execute(page: Page, app_url: str, simple_session):
|
||
"""Clicking play button from the session detail view navigates to execute view."""
|
||
sess_id = simple_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}")
|
||
page.wait_for_timeout(600)
|
||
|
||
execute_btn = page.locator('[data-testid="session-execute-btn"]')
|
||
expect(execute_btn).to_be_visible()
|
||
execute_btn.click()
|
||
page.wait_for_timeout(800)
|
||
|
||
assert f"/sessions/{sess_id}/execute" in page.url
|
||
expect(page.locator('[data-testid="execute-session-title"]')).to_be_visible()
|
||
|
||
|
||
def test_session_list_play_buttons(page: Page, app_url: str, simple_session):
|
||
"""Every session card in the list shows a play button."""
|
||
sess_id = simple_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions")
|
||
page.wait_for_timeout(600)
|
||
|
||
play_btn = page.locator(f'[data-testid="session-card-play-{sess_id}"]')
|
||
expect(play_btn).to_be_visible()
|
||
|
||
|
||
def test_execute_view_elements(page: Page, app_url: str, two_step_session):
|
||
"""Execute view shows session title, progress, current step, and next step preview."""
|
||
sess_id = two_step_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
expect(page.locator('[data-testid="execute-session-title"]')).to_have_text("Two Step Session")
|
||
expect(page.locator('[data-testid="execute-progress-label"]')).to_contain_text("0 / 2")
|
||
expect(page.locator('[data-testid="execute-current-step"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-reps-input"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-weight-input"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-done-btn"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-next-step"]')).to_be_visible()
|
||
|
||
|
||
def test_steps_advance_on_done(page: Page, app_url: str, two_step_session):
|
||
"""Pressing Done advances from one step to the next."""
|
||
sess_id = two_step_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
first_title = page.locator('[data-testid="execute-exercise-title"]').inner_text()
|
||
assert first_title == "Step One"
|
||
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
second_title = page.locator('[data-testid="execute-exercise-title"]').inner_text()
|
||
assert second_title == "Step Two"
|
||
|
||
progress = page.locator('[data-testid="execute-progress-label"]').inner_text()
|
||
assert "1 / 2" in progress
|
||
|
||
|
||
def test_reps_weight_editable_and_saved(page: Page, app_url: str, simple_session):
|
||
"""Reps and weight inputs can be edited and the values are saved to the execution log."""
|
||
sess_id = simple_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
page.fill('[data-testid="execute-reps-input"]', "12")
|
||
page.fill('[data-testid="execute-weight-input"]', "55")
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
# Check log saved correct values
|
||
result = page.evaluate(f"""async () => {{
|
||
const logs = await window.__repos.executionLogs.getBySessionId('{sess_id}')
|
||
if (!logs.length) return null
|
||
const items = await window.__repos.executionLogs.getItems(logs[0].id)
|
||
return items.length ? {{reps: items[0].reps_done, weight: items[0].weight_used}} : null
|
||
}}""")
|
||
assert result is not None
|
||
assert result["reps"] == 12
|
||
assert result["weight"] == 55.0
|
||
|
||
|
||
def test_session_complete_after_last_step(page: Page, app_url: str, simple_session):
|
||
"""Completing all steps redirects back to the session detail view."""
|
||
sess_id = simple_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
assert f"/sessions/{sess_id}" in page.url
|
||
assert "/execute" not in page.url
|
||
|
||
|
||
def test_asymmetric_side_shown(page: Page, app_url: str, asymmetric_session):
|
||
"""Side toggle is visible for asymmetric exercises and hidden for non-asymmetric ones."""
|
||
_wait_for_db(page, app_url)
|
||
_clean_all(page)
|
||
|
||
ex_sym = _create_exercise(page, "Squat Sym", default_reps=10)
|
||
ex_asym = _create_exercise(page, "Lunge Asym", asymmetric=True, alternate=True, default_reps=10)
|
||
sess_id = _create_session_with_exercises(page, "Side Test Session", [
|
||
{"item_id": ex_sym, "item_type": "exercise", "repetitions": 10, "weight": 0},
|
||
{"item_id": ex_asym, "item_type": "exercise", "repetitions": 10, "weight": 0},
|
||
])
|
||
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# First step: non-asymmetric — no side toggle
|
||
expect(page.locator('[data-testid="execute-side-toggle"]')).not_to_be_visible()
|
||
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Second step: asymmetric — side toggle visible
|
||
side_toggle = page.locator('[data-testid="execute-side-toggle"]')
|
||
expect(side_toggle).to_be_visible()
|
||
assert "LEFT" in side_toggle.inner_text() or "RIGHT" in side_toggle.inner_text()
|
||
|
||
# Toggle side
|
||
initial = side_toggle.inner_text()
|
||
side_toggle.click()
|
||
page.wait_for_timeout(200)
|
||
toggled = side_toggle.inner_text()
|
||
assert initial != toggled
|
||
|
||
|
||
def test_asymmetric_side_logged(page: Page, app_url: str, asymmetric_session):
|
||
"""Side value is stored in execution_log_item.side for asymmetric exercises."""
|
||
sess_id = asymmetric_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Switch to RIGHT
|
||
page.click('[data-testid="execute-side-toggle"]')
|
||
page.wait_for_timeout(200)
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
result = page.evaluate(f"""async () => {{
|
||
const logs = await window.__repos.executionLogs.getBySessionId('{sess_id}')
|
||
if (!logs.length) return null
|
||
const items = await window.__repos.executionLogs.getItems(logs[0].id)
|
||
return items.length ? items[0].side : null
|
||
}}""")
|
||
assert result == "right"
|
||
|
||
|
||
def test_combo_rounds_auto_advance(page: Page, app_url: str, combo_session):
|
||
"""Between non-final rounds of a combo, no final-round screen appears."""
|
||
sess_id = combo_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Step 1: Squat Round 1
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Step 2: Jump Round 1 — after Done, should auto-advance to Round 2 (no pause screen)
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# No final-round screen; we should be on Squat Round 2
|
||
expect(page.locator('[data-testid="execute-final-round"]')).not_to_be_visible()
|
||
expect(page.locator('[data-testid="execute-exercise-title"]')).to_have_text("Squat")
|
||
combo_ctx = page.locator('[data-testid="execute-combo-context"]').inner_text()
|
||
assert "2" in combo_ctx
|
||
|
||
|
||
def test_final_round_screen_shown(page: Page, app_url: str, combo_session):
|
||
"""Final-round screen appears after the last exercise of the last round."""
|
||
sess_id = combo_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Complete all 4 steps (2 exercises × 2 rounds)
|
||
for _ in range(3):
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Step 4 — last exercise of last round
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
expect(page.locator('[data-testid="execute-final-round"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-continue-btn"]')).to_be_visible()
|
||
expect(page.locator('[data-testid="execute-one-more-btn"]')).to_be_visible()
|
||
|
||
|
||
def test_continue_advances_past_combo(page: Page, app_url: str, mixed_session):
|
||
"""Continue → after last combo round advances to the next session item."""
|
||
sess_id = mixed_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Solo Ex (step 1)
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Combo Ex1 (step 2, round 1 of 1)
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Combo Ex2 (step 3, last step of last round) → final-round screen
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
expect(page.locator('[data-testid="execute-final-round"]')).to_be_visible()
|
||
|
||
# Click Continue → should finish session and redirect (no more items)
|
||
page.click('[data-testid="execute-continue-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
assert f"/sessions/{sess_id}" in page.url
|
||
assert "/execute" not in page.url
|
||
|
||
|
||
def test_one_more_round_appends_round(page: Page, app_url: str, combo_session):
|
||
"""One More Round inserts an extra round and updates the progress."""
|
||
sess_id = combo_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Complete all 4 steps to reach final-round screen
|
||
for _ in range(4):
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
expect(page.locator('[data-testid="execute-final-round"]')).to_be_visible()
|
||
|
||
# Click One More Round
|
||
page.click('[data-testid="execute-one-more-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Should be on first exercise of new round
|
||
progress = page.locator('[data-testid="execute-progress-label"]').inner_text()
|
||
assert "/ 6" in progress # 4 original + 2 new = 6 total
|
||
|
||
combo_ctx = page.locator('[data-testid="execute-combo-context"]').inner_text()
|
||
assert "3" in combo_ctx # Round 3
|
||
|
||
|
||
def test_exit_mid_session_saves_partial_log(page: Page, app_url: str, two_step_session):
|
||
"""Exiting mid-session saves only the completed steps to the execution log."""
|
||
sess_id = two_step_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Complete step 1 only
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
# Exit mid-session
|
||
page.click('[data-testid="execute-exit-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
assert f"/sessions/{sess_id}" in page.url
|
||
assert "/execute" not in page.url
|
||
|
||
result = page.evaluate(f"""async () => {{
|
||
const logs = await window.__repos.executionLogs.getBySessionId('{sess_id}')
|
||
if (!logs.length) return null
|
||
const items = await window.__repos.executionLogs.getItems(logs[0].id)
|
||
return {{items: items.length, finishedAt: logs[0].finished_at}}
|
||
}}""")
|
||
assert result is not None
|
||
assert result["items"] == 1
|
||
assert result["finishedAt"] is not None
|
||
|
||
|
||
def test_prefill_from_last_session_run(page: Page, app_url: str, simple_session):
|
||
"""Re-executing a session prefills reps/weight from the last run."""
|
||
sess_id = simple_session["sess_id"]
|
||
|
||
# First run with custom values
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
page.fill('[data-testid="execute-reps-input"]', "15")
|
||
page.fill('[data-testid="execute-weight-input"]', "75")
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(800)
|
||
|
||
# Second run — should prefill from last run
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
assert page.locator('[data-testid="execute-reps-input"]').input_value() == "15"
|
||
assert page.locator('[data-testid="execute-weight-input"]').input_value() == "75"
|
||
|
||
|
||
def test_no_prior_log_uses_defaults(page: Page, app_url: str, simple_session):
|
||
"""With no prior log, session item defaults are used for prefill."""
|
||
sess_id = simple_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
# Default reps=8, weight=50 (from fixture)
|
||
assert page.locator('[data-testid="execute-reps-input"]').input_value() == "8"
|
||
assert page.locator('[data-testid="execute-weight-input"]').input_value() == "50"
|
||
|
||
|
||
def test_progress_bar_reflects_position(page: Page, app_url: str, two_step_session):
|
||
"""Progress label updates from 0/2 → 1/2 → redirects after 2/2."""
|
||
sess_id = two_step_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
expect(page.locator('[data-testid="execute-progress-label"]')).to_contain_text("0 / 2")
|
||
|
||
page.click('[data-testid="execute-done-btn"]')
|
||
page.wait_for_timeout(500)
|
||
|
||
expect(page.locator('[data-testid="execute-progress-label"]')).to_contain_text("1 / 2")
|
||
|
||
|
||
def test_combo_context_shown(page: Page, app_url: str, combo_session):
|
||
"""Combo name and round number are shown in the step card for combo exercises."""
|
||
sess_id = combo_session["sess_id"]
|
||
page.goto(f"{app_url}#/sessions/{sess_id}/execute")
|
||
_wait_for_execute(page)
|
||
|
||
combo_ctx = page.locator('[data-testid="execute-combo-context"]')
|
||
expect(combo_ctx).to_be_visible()
|
||
ctx_text = combo_ctx.inner_text()
|
||
assert "HIIT" in ctx_text
|
||
assert "1" in ctx_text # round 1
|
||
assert "2" in ctx_text # of 2
|