import re import pytest from playwright.sync_api import Page, expect @pytest.fixture(scope="module") def server_url(): return "http://127.0.0.1:8090" class TestPageLoad: def test_loads_and_shows_empty_queue(self, page: Page, server_url): page.goto(server_url) expect(page.locator("h1")).to_have_text("Jam Session") expect(page.locator("#queue-list .empty-state")).to_be_visible() expect(page.locator("#add-form")).to_be_visible() expect(page.locator("#current-title")).to_be_visible() def test_shows_user_count(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#user-count")).to_be_visible() expect(page.locator("#nickname-display")).not_to_be_empty() class TestErrorToast: def _wait_for_ws_ready(self, page): expect(page.locator("#nickname-display")).not_to_be_empty(timeout=10000) def test_shows_error_for_invalid_url(self, page: Page, server_url): page.goto(server_url) page.wait_for_selector("#url-input") self._wait_for_ws_ready(page) page.fill("#url-input", "https://example.com/not-a-video") page.click("#add-form button") expect(page.locator("#error-toast")).to_have_css("display", "flex", timeout=10000) expect(page.locator("#error-toast")).to_contain_text("YouTube", timeout=5000) def test_shows_error_for_non_youtube_url(self, page: Page, server_url): page.goto(server_url) page.wait_for_selector("#url-input") self._wait_for_ws_ready(page) page.fill("#url-input", "https://google.com") page.click("#add-form button") expect(page.locator("#error-toast")).to_be_visible(timeout=10000) class TestNicknameEdit: def test_edit_icon_opens_input(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#nickname-edit-icon")).to_be_visible() page.click("#nickname-edit-icon") expect(page.locator("#nickname-input")).to_be_visible() def test_pressing_escape_cancels_edit(self, page: Page, server_url): page.goto(server_url) page.click("#nickname-edit-icon") page.press("#nickname-input", "Escape") expect(page.locator("#nickname-input")).not_to_be_visible() class TestQRCode: def test_qr_image_loads(self, page: Page, server_url): page.goto(server_url) qr_img = page.locator("#qr-image") expect(qr_img).to_be_visible() expect(qr_img).not_to_have_attribute("src", "") def test_server_url_displayed(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#server-url")).not_to_be_empty() class TestNavigation: def test_refresh_button_exists(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#refresh-btn")).to_be_visible() class TestHostControls: def test_host_controls_initially_hidden(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#host-controls")).not_to_be_visible() expect(page.locator("#host-settings")).not_to_be_visible() def test_stop_server_modal_opens(self, page: Page, server_url): page.goto(f"{server_url}/?host=fake-token") expect(page.locator("#nickname-edit-icon")).to_be_visible() page.click("#nickname-edit-icon") expect(page.locator("#nickname-input")).to_be_visible(timeout=5000) page.press("#nickname-input", "Enter") page.wait_for_timeout(500) expect(page.locator("#stop-server-modal")).not_to_be_visible() class TestShareSection: def test_share_section_visible(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#share h2")).to_be_visible() class TestVoteToggle: def test_vote_button_click_toggles_state(self, page: Page, server_url): page.goto(server_url) page.wait_for_selector("#url-input") expect(page.locator("#nickname-display")).not_to_be_empty(timeout=10000) page.fill("#url-input", "https://youtube.com/watch?v=dQw4w9WgXcQ") page.click("#add-form button") page.wait_for_selector("#queue-list li[data-track-id]", timeout=15000) vote_btn = page.locator("#queue-list .vote-btn").first vote_btn.click() expect(vote_btn).to_have_class(re.compile(r"voted")) class TestDragReorder: def test_drag_handle_present(self, page: Page, server_url): page.goto(server_url) page.wait_for_selector("#url-input") page.fill("#url-input", "https://youtube.com/watch?v=dQw4w9WgXcQ") page.click("#add-form button") page.wait_for_selector(".drag-handle", timeout=10000) expect(page.locator(".drag-handle").first).to_be_visible() class TestSkipVoteButton: def test_skip_button_present(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#skip-vote-btn")).to_be_visible() expect(page.locator("#skip-badge")).to_be_visible() def test_skip_button_starts_unvoted(self, page: Page, server_url): page.goto(server_url) btn = page.locator("#skip-vote-btn") expect(btn).not_to_have_class(re.compile(r"voted")) # noqa: F821 — re imported at module top class TestProgressBar: def test_progress_bar_visible(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#progress-bar")).to_be_visible() expect(page.locator("#progress-fill")).to_be_attached() def test_time_labels_visible(self, page: Page, server_url): page.goto(server_url) expect(page.locator("#time-elapsed")).to_be_visible() expect(page.locator("#time-remaining")).to_be_visible() expect(page.locator("#time-total")).to_be_visible()