"""
Step 3 — Exercise Management Tests
- test_exercise_list_empty: Shows empty state when no exercises exist
- test_create_exercise_minimal: Create exercise with only required field (title)
- test_create_exercise_full: Create exercise with all fields filled
- test_exercise_appears_in_list: Created exercise appears in the list with badges
- test_exercise_detail_view: Detail view shows all exercise data
- test_edit_exercise: Edit an exercise and verify changes
- test_delete_exercise: Delete an exercise with confirmation
- test_delete_exercise_cancel: Cancelling delete does not remove exercise
- test_delete_exercise_used_in_session: Delete confirm mentions usage; no session_item orphans
- test_exercise_video_embed_and_shorts_urls: embed/shorts YouTube URLs render as thumbnails
- test_search_exercises: Search filters exercises by title
- 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_title_required: Empty title shows validation error
- test_validation_negative_reps: Negative reps shows validation error
- test_validation_negative_weight: Negative weight shows validation error
- test_validation_invalid_url: Invalid URL format is rejected
- test_duplicate_title_rejected: Creating exercise with duplicate title shows error
- test_asymmetric_toggle: Asymmetric toggle shows alternation options
- test_action_bar_buttons: New and Search buttons appear in action bar
"""
from playwright.sync_api import Page, expect


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_exercises(page: Page, app_url: str):
    """Navigate to the exercises list and wait for it to load."""
    _wait_for_db(page, app_url)
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)


def _clean_exercises(page: Page):
    """Remove all exercises via the repository."""
    page.evaluate(
        """async () => {
            const exercises = await window.__repos.exercises.getAll();
            for (const ex of exercises) {
                await window.__repos.exercises.delete(ex.id);
            }
        }"""
    )


def _create_exercise_via_ui(page: Page, title, description="", reps=0, weight=0,
                             asymmetric=False, alternate=False,
                             image_urls=None, video_urls=None):
    """Fill the exercise form and save via UI."""
    # Click New button in action bar
    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    # Fill title
    page.fill('[data-testid="exercise-title"]', title)

    # Fill description
    if description:
        page.fill('[data-testid="exercise-description"]', description)

    # Toggle asymmetric
    if asymmetric:
        page.click('[data-testid="exercise-asymmetric"]')
        if alternate:
            page.click('[data-testid="exercise-alternate-true"]')
        else:
            page.click('[data-testid="exercise-alternate-false"]')

    # Fill reps
    if reps:
        page.fill('[data-testid="exercise-reps"]', str(reps))

    # Fill weight
    if weight:
        page.fill('[data-testid="exercise-weight"]', str(weight))

    # Add image URLs
    if image_urls:
        for url in image_urls:
            page.fill('[data-testid="exercise-new-image-url"]', url)
            page.click('[data-testid="exercise-add-image-url"]')

    # Add video URLs
    if video_urls:
        for url in video_urls:
            page.fill('[data-testid="exercise-new-video-url"]', url)
            page.click('[data-testid="exercise-add-video-url"]')

    # Save
    page.click('[data-testid="exercise-save"]')
    # Wait for navigation to detail view
    page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)


def test_exercise_list_empty(page: Page, app_url: str):
    """Empty state is shown when no exercises exist."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)
    expect(page.locator('[data-testid="exercise-empty"]')).to_be_visible()


def test_create_exercise_minimal(page: Page, app_url: str):
    """Create an exercise with only the required title field."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(page, "Push-ups")

    # Should be on detail view
    expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Push-ups")

    # Clean up
    _clean_exercises(page)


def test_create_exercise_full(page: Page, app_url: str):
    """Create an exercise with all fields filled."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(
        page,
        title="Dumbbell Curl",
        description="Bicep curl with dumbbell",
        reps=12,
        weight=10,
        asymmetric=True,
        alternate=True,
        image_urls=["https://example.com/curl.jpg"],
        video_urls=["https://www.youtube.com/watch?v=dQw4w9WgXcQ"],
    )

    # Verify detail page
    expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Dumbbell Curl")
    expect(page.locator('[data-testid="exercise-detail-description"]')).to_have_text(
        "Bicep curl with dumbbell"
    )
    expect(page.locator('[data-testid="exercise-detail-reps"]')).to_contain_text("12")
    expect(page.locator('[data-testid="exercise-detail-weight"]')).to_contain_text("10")
    expect(page.locator('[data-testid="exercise-detail-asymmetric"]')).to_be_visible()

    # Clean up
    _clean_exercises(page)


def test_exercise_video_embed_and_shorts_urls(page: Page, app_url: str):
    """YouTube /embed/<id> and /shorts/<id> URLs are recognized like watch
    URLs and render as thumbnails on the detail view."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    exercise_id = page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            return window.__repos.exercises.create({
                title: 'Video URL Variants',
                description: '',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [
                    'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
                    'https://www.youtube.com/embed/dQw4w9WgXcQ',
                    'https://www.youtube.com/shorts/dQw4w9WgXcQ',
                ],
                default_reps: 10,
                default_weight: 0,
                created_by: uuid
            });
        }"""
    )

    page.goto(f"{app_url}/#/exercises/{exercise_id}")
    page.wait_for_selector('[data-testid="exercise-detail-video-0"]', timeout=5000)

    for i in range(3):
        video = page.locator(f'[data-testid="exercise-detail-video-{i}"]')
        expect(video).to_have_class("youtube-thumb")
        expect(video.locator("img")).to_have_attribute(
            "src", "https://img.youtube.com/vi/dQw4w9WgXcQ/mqdefault.jpg"
        )

    _clean_exercises(page)


def test_exercise_appears_in_list(page: Page, app_url: str):
    """Created exercise appears in the list view with correct info."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(page, title="Squats", reps=15, weight=50)

    # Go back to list
    page.click('[data-testid="exercise-back-btn"]')
    page.wait_for_timeout(500)

    # Exercise card should be visible
    card_title = page.locator('[data-testid="exercise-card-title"]')
    expect(card_title.first).to_have_text("Squats")

    # Badges
    expect(page.locator('[data-testid="exercise-card-reps"]').first).to_contain_text("15")
    expect(page.locator('[data-testid="exercise-card-weight"]').first).to_contain_text("50")

    # Clean up
    _clean_exercises(page)


def test_exercise_detail_view(page: Page, app_url: str):
    """Detail view shows all exercise data correctly."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    # Create via repo
    exercise_id = page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            return window.__repos.exercises.create({
                title: 'Bench Press',
                description: 'Chest exercise with barbell',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [],
                default_reps: 8,
                default_weight: 60,
                created_by: uuid
            });
        }"""
    )

    # Navigate to detail
    page.goto(f"{app_url}/#/exercises/{exercise_id}")
    page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)

    expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Bench Press")
    expect(page.locator('[data-testid="exercise-detail-description"]')).to_have_text(
        "Chest exercise with barbell"
    )
    expect(page.locator('[data-testid="exercise-detail-reps"]')).to_contain_text("8")
    expect(page.locator('[data-testid="exercise-detail-weight"]')).to_contain_text("60")

    # Clean up
    _clean_exercises(page)


def test_edit_exercise(page: Page, app_url: str):
    """Edit an exercise and verify changes are persisted."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(page, title="Plank", reps=1, weight=0)

    # Click edit button
    page.click('[data-testid="exercise-edit-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    # Change title and reps
    page.fill('[data-testid="exercise-title"]', "Side Plank")
    page.fill('[data-testid="exercise-reps"]', "3")
    page.fill('[data-testid="exercise-description"]', "Hold for 30 seconds each side")

    # Save
    page.click('[data-testid="exercise-save"]')
    page.wait_for_selector('[data-testid="exercise-detail-title"]', timeout=5000)

    # Verify changes
    expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Side Plank")
    expect(page.locator('[data-testid="exercise-detail-description"]')).to_have_text(
        "Hold for 30 seconds each side"
    )
    expect(page.locator('[data-testid="exercise-detail-reps"]')).to_contain_text("3")

    # Clean up
    _clean_exercises(page)


def test_delete_exercise(page: Page, app_url: str):
    """Delete an exercise with confirmation dialog."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(page, title="To Delete")

    # Accept the confirm dialog
    page.on("dialog", lambda dialog: dialog.accept())

    # Click delete button
    page.click('[data-testid="exercise-delete-btn"]')
    page.wait_for_timeout(500)

    # Should be back on list with empty state
    expect(page.locator('[data-testid="exercise-empty"]')).to_be_visible()


def test_delete_exercise_cancel(page: Page, app_url: str):
    """Cancelling the delete dialog does not remove the exercise."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    _create_exercise_via_ui(page, title="Keep Me")

    # Dismiss the confirm dialog
    page.on("dialog", lambda dialog: dialog.dismiss())

    # Click delete button
    page.click('[data-testid="exercise-delete-btn"]')
    page.wait_for_timeout(500)

    # Exercise should still be shown
    expect(page.locator('[data-testid="exercise-detail-title"]')).to_have_text("Keep Me")

    # Clean up
    _clean_exercises(page)


def test_delete_exercise_used_in_session(page: Page, app_url: str):
    """Deleting an exercise used in a session and a combo warns about the
    usage, and after deletion no session_item rows reference it (no orphans)."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    exercise_id = page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            return window.__repos.exercises.create({
                title: 'Used Everywhere',
                description: '',
                asymmetric: false,
                alternate: false,
                image_urls: [],
                video_urls: [],
                default_reps: 10,
                default_weight: 0,
                created_by: uuid
            });
        }"""
    )
    page.evaluate(
        f"""async () => {{
            const uuid = await window.__repos.settings.get('user_uuid');
            const sessionId = await window.__repos.sessions.create({{
                title: 'Session Using Exercise', description: '', created_by: uuid
            }});
            await window.__repos.sessions.setItems(sessionId, [
                {{ item_id: '{exercise_id}', item_type: 'exercise', repetitions: 10, weight: 0 }},
            ]);
            const comboId = await window.__repos.combos.create({{
                title: 'Combo Using Exercise', description: '', type: 'NONE', created_by: uuid
            }});
            await window.__repos.combos.setExercises(comboId, ['{exercise_id}']);
        }}"""
    )

    dialog_message = {}

    def on_dialog(dialog):
        dialog_message["text"] = dialog.message
        dialog.accept()

    page.on("dialog", on_dialog)

    page.goto(f"{app_url}/#/exercises/{exercise_id}")
    page.wait_for_selector('[data-testid="exercise-delete-btn"]', timeout=5000)
    page.click('[data-testid="exercise-delete-btn"]')
    page.wait_for_timeout(500)

    msg = dialog_message.get("text", "")
    assert "1 session(s)" in msg, f"Confirm must mention session usage, got: {msg}"
    assert "1 combo(s)" in msg, f"Confirm must mention combo usage, got: {msg}"

    orphans = page.evaluate(
        f"""async () => (await window.__db.selectAll(
            "SELECT * FROM session_item WHERE item_id = '{exercise_id}'"
        )).length"""
    )
    assert orphans == 0, f"Expected no orphaned session_item rows, got {orphans}"

    # Clean up the seeded session/combo
    page.evaluate(
        """async () => {
            for (const s of await window.__repos.sessions.getAll())
                await window.__repos.sessions.delete(s.id)
            for (const c of await window.__repos.combos.getAll())
                await window.__repos.combos.delete(c.id)
        }"""
    )


def test_search_exercises(page: Page, app_url: str):
    """Search filters exercises by title."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    # Create multiple exercises via repo
    page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            await window.__repos.exercises.create({
                title: 'Push-ups', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 10, default_weight: 0,
                created_by: uuid
            });
            await window.__repos.exercises.create({
                title: 'Pull-ups', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 8, default_weight: 0,
                created_by: uuid
            });
            await window.__repos.exercises.create({
                title: 'Squats', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 15, default_weight: 0,
                created_by: uuid
            });
        }"""
    )

    # Reload exercises list
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    # All 3 should be visible
    expect(page.locator('[data-testid="exercise-card-title"]')).to_have_count(3)

    # Toggle search
    page.click('[data-testid="exercise-search-toggle"]')
    page.wait_for_selector('[data-testid="exercise-search-input"]')

    # Type search query
    page.fill('[data-testid="exercise-search-input"]', "ups")
    page.wait_for_timeout(500)

    # Only Push-ups and Pull-ups should be visible
    expect(page.locator('[data-testid="exercise-card-title"]')).to_have_count(2)

    # Clean up
    _clean_exercises(page)


def test_search_no_results(page: Page, app_url: str):
    """Search with no matches shows no-results message."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    # Create one exercise
    page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            await window.__repos.exercises.create({
                title: 'Push-ups', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 10, default_weight: 0,
                created_by: uuid
            });
        }"""
    )

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    # Toggle search and type non-matching query
    page.click('[data-testid="exercise-search-toggle"]')
    page.fill('[data-testid="exercise-search-input"]', "zzzznotfound")
    page.wait_for_timeout(500)

    expect(page.locator('[data-testid="exercise-no-results"]')).to_be_visible()

    # Clean up
    _clean_exercises(page)


def test_search_clear_button(page: Page, app_url: str):
    """Clear button empties the search field and restores the full list."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)

    page.evaluate(
        """async () => {
            const uuid = await window.__repos.settings.get('user_uuid');
            await window.__repos.exercises.create({
                title: 'Push-ups', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 10, default_weight: 0,
                created_by: uuid
            });
            await window.__repos.exercises.create({
                title: 'Squats', description: '', asymmetric: false, alternate: false,
                image_urls: [], video_urls: [], default_reps: 15, default_weight: 0,
                created_by: uuid
            });
        }"""
    )

    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    page.click('[data-testid="exercise-search-toggle"]')

    # Clear button is hidden until there is text in the field
    expect(page.locator('[data-testid="exercise-search-clear"]')).not_to_be_visible()

    page.fill('[data-testid="exercise-search-input"]', "Push")
    page.wait_for_timeout(500)
    expect(page.locator('[data-testid="exercise-card-title"]')).to_have_count(1)

    # Clicking the clear button empties the field and restores the full list
    page.click('[data-testid="exercise-search-clear"]')
    expect(page.locator('[data-testid="exercise-search-input"]')).to_have_value("")
    page.wait_for_timeout(500)
    expect(page.locator('[data-testid="exercise-card-title"]')).to_have_count(2)

    # Search field keeps focus after clearing
    expect(page.locator('[data-testid="exercise-search-input"]')).to_be_focused()

    # Clean up
    _clean_exercises(page)


def test_validation_title_required(page: Page, app_url: str):
    """Empty title shows validation error and prevents save."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    # Click New
    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    # Leave title empty, click Save
    page.click('[data-testid="exercise-save"]')

    # Error should appear
    expect(page.locator('[data-testid="exercise-title-error"]')).to_be_visible()

    # Should still be on the form (not navigated away)
    expect(page.locator('[data-testid="exercise-title"]')).to_be_visible()


def test_validation_negative_reps(page: Page, app_url: str):
    """Negative reps shows validation error."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    page.fill('[data-testid="exercise-title"]', "Test Exercise")
    page.fill('[data-testid="exercise-reps"]', "-5")
    page.click('[data-testid="exercise-save"]')

    expect(page.locator('[data-testid="exercise-reps-error"]')).to_be_visible()


def test_validation_negative_weight(page: Page, app_url: str):
    """Negative weight shows validation error."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    page.fill('[data-testid="exercise-title"]', "Test Exercise")
    page.fill('[data-testid="exercise-weight"]', "-10")
    page.click('[data-testid="exercise-save"]')

    expect(page.locator('[data-testid="exercise-weight-error"]')).to_be_visible()


def test_validation_invalid_url(page: Page, app_url: str):
    """Invalid URL format is rejected when adding an image URL."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    # Try adding an invalid URL
    page.fill('[data-testid="exercise-new-image-url"]', "not-a-url")
    page.click('[data-testid="exercise-add-image-url"]')

    expect(page.locator('[data-testid="exercise-image-url-error"]')).to_be_visible()


def test_duplicate_title_rejected(page: Page, app_url: str):
    """Creating an exercise with a duplicate title shows an error."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    # Create first exercise
    _create_exercise_via_ui(page, title="Unique Exercise")

    # Go back to list
    page.click('[data-testid="exercise-back-btn"]')
    page.wait_for_timeout(500)

    # Try to create another with the same title
    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')
    page.fill('[data-testid="exercise-title"]', "Unique Exercise")
    page.click('[data-testid="exercise-save"]')
    page.wait_for_timeout(500)

    # Should show error (title error or save error)
    title_error_visible = page.locator('[data-testid="exercise-title-error"]').is_visible()
    save_error_visible = page.locator('[data-testid="exercise-save-error"]').is_visible()
    assert title_error_visible or save_error_visible, "Expected duplicate title error"

    # Clean up
    page.goto(app_url)
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    _clean_exercises(page)


def test_asymmetric_toggle(page: Page, app_url: str):
    """Asymmetric toggle shows/hides alternation radio buttons."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    page.click('[data-testid="exercise-new-btn"]')
    page.wait_for_selector('[data-testid="exercise-title"]')

    # Alternate options should not be visible initially
    expect(page.locator('[data-testid="exercise-alternate-true"]')).not_to_be_visible()
    expect(page.locator('[data-testid="exercise-alternate-false"]')).not_to_be_visible()

    # Toggle asymmetric
    page.click('[data-testid="exercise-asymmetric"]')

    # Alternate options should now be visible
    expect(page.locator('[data-testid="exercise-alternate-true"]')).to_be_visible()
    expect(page.locator('[data-testid="exercise-alternate-false"]')).to_be_visible()

    # Untoggle asymmetric
    page.click('[data-testid="exercise-asymmetric"]')

    # Alternate options should be hidden again
    expect(page.locator('[data-testid="exercise-alternate-true"]')).not_to_be_visible()


def test_action_bar_buttons(page: Page, app_url: str):
    """New and Search buttons are visible in the action bar on the exercise list."""
    _go_to_exercises(page, app_url)
    _clean_exercises(page)
    page.reload()
    page.wait_for_function(
        "document.documentElement.getAttribute('data-db-ready') === 'true'",
        timeout=15000,
    )
    page.click('a[href="#/exercises"]')
    page.wait_for_timeout(500)

    expect(page.locator('[data-testid="exercise-new-btn"]')).to_be_visible()
    expect(page.locator('[data-testid="exercise-search-toggle"]')).to_be_visible()
