Spaces:
Running
Running
File size: 6,995 Bytes
4a04219 c0e8ee8 4a04219 c0e8ee8 4a04219 b480153 4a04219 c0e8ee8 4a04219 c0e8ee8 4a04219 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """Tier 3 β Playwright E2E browser tests for Marionette.
Run without hardware, without daemon. Tests the frontend UI in real browsers.
Uses a real Marionette server with temp paths (no robot connection needed for
these tests β they only test the web UI behavior).
"""
import re
import pytest
from playwright.sync_api import Page, expect
# ββββββββ Page load tests βββββββββββββββββββββββββββββββββββββββββββββ
class TestPageLoad:
def test_page_loads_successfully(self, page: Page, base_url: str):
page.goto(base_url)
expect(page).to_have_title(re.compile("Marionette", re.IGNORECASE))
def test_main_sections_visible(self, page: Page, base_url: str):
page.goto(base_url)
# Record section
expect(page.locator("#record-form")).to_be_visible()
# Moves section
expect(page.locator("#moves-list")).to_be_visible()
# Record button
expect(page.locator("#record-btn")).to_be_visible()
def test_mode_pill_shows_idle(self, page: Page, base_url: str):
page.goto(base_url)
pill = page.locator("#mode-pill")
expect(pill).to_be_visible()
# Wait for first poll to update the pill
page.wait_for_timeout(2000)
expect(pill).to_contain_text(re.compile("idle", re.IGNORECASE))
# ββββββββ Idle state display ββββββββββββββββββββββββββββββββββββββββββ
class TestIdleState:
def test_record_button_enabled(self, page: Page, base_url: str):
page.goto(base_url)
page.wait_for_timeout(2000)
btn = page.locator("#record-btn")
expect(btn).to_be_enabled()
def test_stop_buttons_hidden(self, page: Page, base_url: str):
page.goto(base_url)
page.wait_for_timeout(2000)
expect(page.locator("#stop-recording-btn")).to_be_hidden()
expect(page.locator("#stop-playback-btn")).to_be_hidden()
def test_progress_bar_empty(self, page: Page, base_url: str):
page.goto(base_url)
page.wait_for_timeout(2000)
fill = page.locator("#progress-fill")
width = fill.evaluate("el => el.style.width")
assert width == "0%" or width == ""
def test_phase_display_hidden_when_idle(self, page: Page, base_url: str):
page.goto(base_url)
page.wait_for_timeout(2000)
expect(page.locator("#phase-display")).to_be_hidden()
# ββββββββ Form validation tests βββββββββββββββββββββββββββββββββββββββ
class TestSimplifiedForm:
def test_name_and_duration_visible_by_default(self, page: Page, base_url: str):
page.goto(base_url)
expect(page.locator("#label")).to_be_visible()
expect(page.locator("#duration")).to_be_visible()
expect(page.locator("#record-btn")).to_be_visible()
def test_options_collapsed_by_default(self, page: Page, base_url: str):
"""Audio source should be hidden in Options until user expands it."""
page.goto(base_url)
expect(page.locator("#audio-source-mic")).to_be_hidden()
def test_description_field_removed(self, page: Page, base_url: str):
"""P1: description field was removed to simplify the form."""
page.goto(base_url)
assert page.locator("#description").count() == 0
class TestFormValidation:
def test_duration_field_exists(self, page: Page, base_url: str):
page.goto(base_url)
duration = page.locator("#duration")
expect(duration).to_be_visible()
# Check it has a default value
value = duration.input_value()
assert float(value) > 0
def test_duration_accepts_decimal(self, page: Page, base_url: str):
page.goto(base_url)
duration = page.locator("#duration")
duration.fill("3.7")
assert duration.input_value() == "3.7"
def test_duration_accepts_non_half_second(self, page: Page, base_url: str):
"""P0-2 regression: duration field must accept values like 5.2, not just 0.5 multiples."""
page.goto(base_url)
duration = page.locator("#duration")
for val in ["5.2", "12.3", "0.7", "201.7"]:
duration.fill(val)
assert duration.input_value() == val
# Verify the field passes HTML5 validity
is_valid = duration.evaluate("el => el.checkValidity()")
assert is_valid, f"Duration {val} should be valid but HTML5 validation rejected it"
def test_audio_source_radios_exist(self, page: Page, base_url: str):
page.goto(base_url)
# Audio source is inside collapsible Options β open it first
page.locator("#record-options summary").click()
expect(page.locator("#audio-source-mic")).to_be_visible()
expect(page.locator("#audio-source-none")).to_be_visible()
def test_label_field_exists(self, page: Page, base_url: str):
page.goto(base_url)
expect(page.locator("#label")).to_be_visible()
# ββββββββ Recording submission tests ββββββββββββββββββββββββββββββββββ
class TestRecordingSubmission:
def test_submit_recording_changes_mode(self, page: Page, base_url: str, test_marionette):
page.goto(base_url)
page.wait_for_timeout(2000)
# Open Options and set audio source to "none" to avoid audio backend requirement
page.locator("#record-options summary").click()
page.locator("#audio-source-none").check()
page.locator("#duration").fill("2")
page.locator("#label").fill("e2e-test")
# Submit
page.locator("#record-btn").click()
# Wait for state to update
page.wait_for_timeout(2000)
# Mode pill should show something other than "idle"
pill_text = page.locator("#mode-pill").text_content()
assert pill_text is not None
# Mode should be queued (since no robot is running to process it)
assert "queued" in pill_text.lower() or "countdown" in pill_text.lower() or "recording" in pill_text.lower()
# Reset for other tests
test_marionette._set_idle_state()
test_marionette._pending_recording = None
# ββββββββ Dataset UI tests ββββββββββββββββββββββββββββββββββββββββββββ
class TestDatasetUI:
def test_dataset_selector_visible(self, page: Page, base_url: str):
page.goto(base_url)
page.wait_for_timeout(2000)
expect(page.locator("#dataset-select")).to_be_visible()
def test_new_dataset_button_exists(self, page: Page, base_url: str):
page.goto(base_url)
expect(page.locator("#new-dataset-btn")).to_be_visible()
|