""" Step 6 — Collection Management & Home Page Tests - test_collection_list_empty: Shows empty state when no collections exist - test_create_collection_minimal: Create collection with only required field (label) - test_create_collection_with_sessions: Create collection with sessions - test_collection_appears_in_list: Created collection appears in the list - test_collection_detail_view: Detail view shows all collection data - test_edit_collection: Edit a collection and verify changes - test_delete_collection: Delete a collection with confirmation - test_delete_collection_cancel: Cancelling delete does not remove collection - test_search_collections: Search filters collections by label - test_search_no_results: Search with no matches shows empty message - test_search_clear_button: Clear button empties the search field and restores the full list - test_validation_label_required: Empty label shows validation error - test_duplicate_label_rejected: Creating collection with duplicate label shows error - test_reorder_sessions: Reorder sessions in collection - test_remove_session: Remove a session from collection - test_action_bar_buttons: New and Search buttons appear in action bar - test_navigate_to_session_from_collection: Click session in collection detail navigates to session - test_home_empty_state: Home shows empty state when no executions exist - test_home_recent_sessions: Home shows last 4 executed sessions with dates - test_home_session_counts: Home shows session counts for last 7 and 30 days - test_home_history_section: History section groups execution logs by calendar day - test_home_history_date_filter: History section hides logs outside the active date range - test_home_play_button_visible: Play button is visible on recent session cards - test_home_export_html_button: HTML export button visible when data exists, absent otherwise """ from playwright.sync_api import Page, expect from datetime import datetime, timedelta def _wait_for_db(page: Page, app_url: str): """Navigate and wait for the database to be ready.""" page.goto(app_url) page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) def _go_to_collections(page: Page, app_url: str): """Navigate to the collections list and wait for it to load.""" _wait_for_db(page, app_url) page.click('a[href="#/collections"]') page.wait_for_timeout(500) def _go_to_home(page: Page, app_url: str): """Navigate to the home page and wait for it to load.""" _wait_for_db(page, app_url) page.click('a[href="#/home"]') page.wait_for_timeout(500) def _clean_collections(page: Page): """Remove all collections via the repository.""" page.evaluate( """async () => { const collections = await window.__repos.collections.getAll(); for (const c of collections) { await window.__repos.collections.delete(c.id); } }""" ) def _clean_sessions(page: Page): """Remove all sessions via the repository.""" page.evaluate( """async () => { const sessions = await window.__repos.sessions.getAll(); for (const s of sessions) { await window.__repos.sessions.delete(s.id); } }""" ) def _clean_execution_logs(page: Page): """Remove all execution logs via the repository.""" page.evaluate( """async () => { const logs = await window.__repos.executionLogs.getAll(); for (const log of logs) { await window.__repos.executionLogs.delete(log.id); } }""" ) def _create_session_via_repo(page: Page, title): """Create a session via the repository and return its ID.""" return page.evaluate( """async (title) => { const uuid = await window.__repos.settings.get('user_uuid'); return window.__repos.sessions.create({ title: title, description: '', created_by: uuid }); }""", title, ) def test_collection_list_empty(page: Page, app_url: str): """Empty state is shown when no collections exist.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-empty"]')).to_be_visible() def test_create_collection_minimal(page: Page, app_url: str): """Create a collection with only the required label field.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "My First Collection") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text( "My First Collection" ) _clean_collections(page) def test_create_collection_with_sessions(page: Page, app_url: str): """Create a collection with sessions.""" _go_to_collections(page, app_url) _clean_collections(page) _clean_sessions(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) # Create sessions session1_id = _create_session_via_repo(page, "Morning Workout") session2_id = _create_session_via_repo(page, "Evening Stretch") page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) # Create collection page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Weekly Plan") # Add sessions page.select_option('[data-testid="collection-session-select"]', session1_id) page.click('[data-testid="collection-session-add"]') page.select_option('[data-testid="collection-session-select"]', session2_id) page.click('[data-testid="collection-session-add"]') page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text("Weekly Plan") expect(page.locator('[data-testid="collection-detail-session-0"]')).to_contain_text( "Morning Workout" ) expect(page.locator('[data-testid="collection-detail-session-1"]')).to_contain_text( "Evening Stretch" ) _clean_collections(page) _clean_sessions(page) def test_collection_appears_in_list(page: Page, app_url: str): """Created collection appears in the list view.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Test Collection") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.click('[data-testid="collection-back-btn"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-card-title"]')).to_have_text("Test Collection") _clean_collections(page) def test_collection_detail_view(page: Page, app_url: str): """Detail view shows all collection data correctly.""" _go_to_collections(page, app_url) _clean_collections(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "HIIT Session") # Create collection via repo collection_id = page.evaluate( """async ({ sessionId }) => { const uuid = await window.__repos.settings.get('user_uuid'); const id = await window.__repos.collections.create({ label: 'Training Week', created_by: uuid }); await window.__repos.collections.setSessions(id, [sessionId]); return id; }""", {"sessionId": session_id}, ) page.goto(f"{app_url}/#/collections/{collection_id}") page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text("Training Week") expect(page.locator('[data-testid="collection-detail-session-0"]')).to_contain_text( "HIIT Session" ) _clean_collections(page) _clean_sessions(page) def test_edit_collection(page: Page, app_url: str): """Edit a collection and verify changes are persisted.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Original Label") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.click('[data-testid="collection-edit-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Updated Label") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text("Updated Label") _clean_collections(page) def test_delete_collection(page: Page, app_url: str): """Delete a collection with confirmation dialog.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "To Delete") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.on("dialog", lambda dialog: dialog.accept()) page.click('[data-testid="collection-delete-btn"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-empty"]')).to_be_visible() def test_delete_collection_cancel(page: Page, app_url: str): """Cancelling the delete dialog does not remove the collection.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Keep Me") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.on("dialog", lambda dialog: dialog.dismiss()) page.click('[data-testid="collection-delete-btn"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-detail-title"]')).to_have_text("Keep Me") _clean_collections(page) def test_search_collections(page: Page, app_url: str): """Search filters collections by label.""" _go_to_collections(page, app_url) _clean_collections(page) page.evaluate( """async () => { const uuid = await window.__repos.settings.get('user_uuid'); await window.__repos.collections.create({ label: 'Upper Body Week', created_by: uuid }); await window.__repos.collections.create({ label: 'Lower Body Week', created_by: uuid }); await window.__repos.collections.create({ label: 'Full Body Week', created_by: uuid }); }""" ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-card-title"]')).to_have_count(3) page.click('[data-testid="collection-search-toggle"]') page.wait_for_selector('[data-testid="collection-search-input"]') page.fill('[data-testid="collection-search-input"]', "Upper") page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-card-title"]')).to_have_count(1) expect(page.locator('[data-testid="collection-card-title"]')).to_have_text("Upper Body Week") _clean_collections(page) def test_search_no_results(page: Page, app_url: str): """Search with no matches shows no-results message.""" _go_to_collections(page, app_url) _clean_collections(page) page.evaluate( """async () => { const uuid = await window.__repos.settings.get('user_uuid'); await window.__repos.collections.create({ label: 'Upper Body Week', created_by: uuid }); }""" ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-search-toggle"]') page.fill('[data-testid="collection-search-input"]', "zzzznotfound") page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-no-results"]')).to_be_visible() _clean_collections(page) def test_search_clear_button(page: Page, app_url: str): """Clear button empties the search field and restores the full list.""" _go_to_collections(page, app_url) _clean_collections(page) page.evaluate( """async () => { const uuid = await window.__repos.settings.get('user_uuid'); await window.__repos.collections.create({ label: 'Upper Body Week', created_by: uuid }); await window.__repos.collections.create({ label: 'Lower Body Week', created_by: uuid }); }""" ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-search-toggle"]') expect(page.locator('[data-testid="collection-search-clear"]')).not_to_be_visible() page.fill('[data-testid="collection-search-input"]', "Upper") page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-card-title"]')).to_have_count(1) page.click('[data-testid="collection-search-clear"]') expect(page.locator('[data-testid="collection-search-input"]')).to_have_value("") page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-card-title"]')).to_have_count(2) expect(page.locator('[data-testid="collection-search-input"]')).to_be_focused() _clean_collections(page) def test_validation_label_required(page: Page, app_url: str): """Empty label shows validation error and prevents save.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.click('[data-testid="collection-save"]') expect(page.locator('[data-testid="collection-label-error"]')).to_be_visible() expect(page.locator('[data-testid="collection-label"]')).to_be_visible() def test_duplicate_label_rejected(page: Page, app_url: str): """Creating a collection with a duplicate label shows an error.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Unique Collection") page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.click('[data-testid="collection-back-btn"]') page.wait_for_timeout(500) page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Unique Collection") page.click('[data-testid="collection-save"]') page.wait_for_timeout(500) label_error_visible = page.locator('[data-testid="collection-label-error"]').is_visible() save_error_visible = page.locator('[data-testid="collection-save-error"]').is_visible() assert label_error_visible or save_error_visible, "Expected duplicate label error" _clean_collections(page) def test_reorder_sessions(page: Page, app_url: str): """Reorder sessions in a collection.""" _go_to_collections(page, app_url) _clean_collections(page) _clean_sessions(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) s1_id = _create_session_via_repo(page, "Session A") s2_id = _create_session_via_repo(page, "Session B") s3_id = _create_session_via_repo(page, "Session C") page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) # Create collection with 3 sessions page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Reorder Test") for s_id in [s1_id, s2_id, s3_id]: page.select_option('[data-testid="collection-session-select"]', s_id) page.click('[data-testid="collection-session-add"]') page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) # Go to edit page.click('[data-testid="collection-edit-btn"]') page.wait_for_selector('[data-testid="collection-label"]') # Move Session B (index 1) up page.click('[data-testid="collection-session-move-up-1"]') # Save page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) # Verify order: Session B, Session A, Session C expect(page.locator('[data-testid="collection-detail-session-0"]')).to_contain_text("Session B") expect(page.locator('[data-testid="collection-detail-session-1"]')).to_contain_text("Session A") expect(page.locator('[data-testid="collection-detail-session-2"]')).to_contain_text("Session C") _clean_collections(page) _clean_sessions(page) def test_remove_session(page: Page, app_url: str): """Remove a session from a collection.""" _go_to_collections(page, app_url) _clean_collections(page) _clean_sessions(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) s1_id = _create_session_via_repo(page, "Session A") s2_id = _create_session_via_repo(page, "Session B") page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) # Create collection with 2 sessions page.click('[data-testid="collection-new-btn"]') page.wait_for_selector('[data-testid="collection-label"]') page.fill('[data-testid="collection-label"]', "Remove Test") for s_id in [s1_id, s2_id]: page.select_option('[data-testid="collection-session-select"]', s_id) page.click('[data-testid="collection-session-add"]') page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) # Go to edit page.click('[data-testid="collection-edit-btn"]') page.wait_for_selector('[data-testid="collection-label"]') # Remove first session page.click('[data-testid="collection-session-remove-0"]') page.click('[data-testid="collection-save"]') page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) # Only Session B should remain expect(page.locator('[data-testid="collection-detail-session-0"]')).to_contain_text("Session B") _clean_collections(page) _clean_sessions(page) def test_action_bar_buttons(page: Page, app_url: str): """New and Search buttons are visible in the action bar on the collection list.""" _go_to_collections(page, app_url) _clean_collections(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/collections"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="collection-new-btn"]')).to_be_visible() expect(page.locator('[data-testid="collection-search-toggle"]')).to_be_visible() def test_navigate_to_session_from_collection(page: Page, app_url: str): """Clicking a session in collection detail navigates to session detail.""" _go_to_collections(page, app_url) _clean_collections(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "Target Session") collection_id = page.evaluate( """async ({ sessionId }) => { const uuid = await window.__repos.settings.get('user_uuid'); const id = await window.__repos.collections.create({ label: 'Navigation Test', created_by: uuid }); await window.__repos.collections.setSessions(id, [sessionId]); return id; }""", {"sessionId": session_id}, ) page.goto(f"{app_url}/#/collections/{collection_id}") page.wait_for_selector('[data-testid="collection-detail-title"]', timeout=5000) page.click('[data-testid="collection-detail-session-link-0"]') page.wait_for_selector('[data-testid="session-detail-title"]', timeout=5000) expect(page.locator('[data-testid="session-detail-title"]')).to_have_text("Target Session") _clean_collections(page) _clean_sessions(page) def test_home_empty_state(page: Page, app_url: str): """Home shows empty state when no executions exist.""" _go_to_home(page, app_url) _clean_execution_logs(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) expect(page.locator('[data-testid="home-empty"]')).to_be_visible() def test_home_recent_sessions(page: Page, app_url: str): """Home shows last 4 executed sessions with inline exercise summaries.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) # Create sessions session1_id = _create_session_via_repo(page, "Session One") session2_id = _create_session_via_repo(page, "Session Two") session3_id = _create_session_via_repo(page, "Session Three") session4_id = _create_session_via_repo(page, "Session Four") # Create execution logs with different dates page.evaluate( """async ({ s1, s2, s3, s4 }) => { const now = new Date(); const logs = [ { session_id: s1, started_at: new Date(now - 4 * 86400000).toISOString(), finished_at: new Date(now - 4 * 86400000 + 3600000).toISOString() }, { session_id: s2, started_at: new Date(now - 3 * 86400000).toISOString(), finished_at: new Date(now - 3 * 86400000 + 3600000).toISOString() }, { session_id: s3, started_at: new Date(now - 2 * 86400000).toISOString(), finished_at: new Date(now - 2 * 86400000 + 3600000).toISOString() }, { session_id: s4, started_at: new Date(now - 1 * 86400000).toISOString(), finished_at: new Date(now - 1 * 86400000 + 3600000).toISOString() }, ]; for (const log of logs) { await window.__repos.executionLogs.create(log); } }""", {"s1": session1_id, "s2": session2_id, "s3": session3_id, "s4": session4_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # Should show the 4 most recent (Session Four, Three, Two, One) expect(page.locator('[data-testid="home-recent-session-title"]')).to_have_count(4) # Most recent first expect(page.locator('[data-testid="home-recent-session-title"]').first).to_have_text( "Session Four" ) _clean_execution_logs(page) _clean_sessions(page) def test_home_session_counts(page: Page, app_url: str): """Home shows session counts for last 7 and 30 days.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "Count Session") # Create 2 logs within 7 days, 3 more within 30 days page.evaluate( """async ({ sessionId }) => { const now = new Date(); const logs = [ { session_id: sessionId, started_at: new Date(now - 1 * 86400000).toISOString(), finished_at: new Date(now - 1 * 86400000 + 3600000).toISOString() }, { session_id: sessionId, started_at: new Date(now - 3 * 86400000).toISOString(), finished_at: new Date(now - 3 * 86400000 + 3600000).toISOString() }, { session_id: sessionId, started_at: new Date(now - 10 * 86400000).toISOString(), finished_at: new Date(now - 10 * 86400000 + 3600000).toISOString() }, { session_id: sessionId, started_at: new Date(now - 15 * 86400000).toISOString(), finished_at: new Date(now - 15 * 86400000 + 3600000).toISOString() }, { session_id: sessionId, started_at: new Date(now - 25 * 86400000).toISOString(), finished_at: new Date(now - 25 * 86400000 + 3600000).toISOString() }, ]; for (const log of logs) { await window.__repos.executionLogs.create(log); } }""", {"sessionId": session_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # 2 within 7 days expect(page.locator('[data-testid="home-stat-7days"] .stat-value')).to_have_text("2") # 5 within 30 days expect(page.locator('[data-testid="home-stat-30days"] .stat-value')).to_have_text("5") _clean_execution_logs(page) _clean_sessions(page) def test_home_history_section(page: Page, app_url: str): """History section groups execution logs by calendar day.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "History Session") # Two logs on two different days (today and yesterday) page.evaluate( """async ({ sessionId }) => { const now = new Date(); const yesterday = new Date(now - 86400000); const logs = [ { session_id: sessionId, started_at: now.toISOString(), finished_at: new Date(now.getTime() + 3600000).toISOString() }, { session_id: sessionId, started_at: yesterday.toISOString(), finished_at: new Date(yesterday.getTime() + 3600000).toISOString() }, ]; for (const log of logs) { await window.__repos.executionLogs.create(log); } }""", {"sessionId": session_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # History section must show 2 day groups (the .history-day-label elements) expect(page.locator(".history-day-label")).to_have_count(2) # Both logs are visible as history cards history_cards = page.locator('[data-testid^="home-history-session-"]') expect(history_cards).to_have_count(2) _clean_execution_logs(page) _clean_sessions(page) def test_home_history_date_filter(page: Page, app_url: str): """History section hides execution logs that fall outside the active date range.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "Filter Session") # One log 5 days ago (inside default 30-day window) and one 40 days ago (outside) page.evaluate( """async ({ sessionId }) => { const now = new Date(); const logs = [ { session_id: sessionId, started_at: new Date(now - 5 * 86400000).toISOString(), finished_at: new Date(now - 5 * 86400000 + 3600000).toISOString() }, { session_id: sessionId, started_at: new Date(now - 40 * 86400000).toISOString(), finished_at: new Date(now - 40 * 86400000 + 3600000).toISOString() }, ]; for (const log of logs) { await window.__repos.executionLogs.create(log); } }""", {"sessionId": session_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # Default filter is last 30 days: only the 5-day-ago log shows in history history_cards = page.locator('[data-testid^="home-history-session-"]') expect(history_cards).to_have_count(1) _clean_execution_logs(page) _clean_sessions(page) def test_home_play_button_visible(page: Page, app_url: str): """Play button is visible on each recent session card.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) session_id = _create_session_via_repo(page, "Play Button Session") page.evaluate( """async ({ sessionId }) => { const now = new Date(); await window.__repos.executionLogs.create({ session_id: sessionId, started_at: now.toISOString(), finished_at: new Date(now.getTime() + 3600000).toISOString(), }); }""", {"sessionId": session_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) play_btn = page.locator(f'[data-testid="home-recent-session-play-{session_id}"]') expect(play_btn).to_be_visible() _clean_execution_logs(page) _clean_sessions(page) def test_home_export_html_button(page: Page, app_url: str): """HTML export button is visible when execution logs exist and absent on empty state.""" _go_to_home(page, app_url) _clean_execution_logs(page) _clean_sessions(page) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # No data: export button must not be rendered expect(page.locator('[data-testid="home-export-html-btn"]')).to_have_count(0) # Create a session and an execution log session_id = _create_session_via_repo(page, "Export Session") page.evaluate( """async ({ sessionId }) => { const now = new Date(); await window.__repos.executionLogs.create({ session_id: sessionId, started_at: now.toISOString(), finished_at: new Date(now.getTime() + 1800000).toISOString(), }); }""", {"sessionId": session_id}, ) page.reload() page.wait_for_function( "document.documentElement.getAttribute('data-db-ready') === 'true'", timeout=15000, ) page.click('a[href="#/home"]') page.wait_for_timeout(500) # With data: export button must be visible expect(page.locator('[data-testid="home-export-html-btn"]')).to_be_visible() _clean_execution_logs(page) _clean_sessions(page)