diff options
| author | historia <historiavg@proton.me> | 2026-09-12 15:25:06 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-12 15:25:06 -0400 |
| commit | 464edbc4fd658728f06d3a7d98340aa28ff3e3ec (patch) | |
| tree | a182f62ef0365f2c2143f88436de78a03d752367 /lib/project/tests/test_gui_browser_races.py | |
| parent | 1bbe59f78be9a8355bb0bee2bc8ab14ca42ac71c (diff) | |
| download | producer-464edbc4fd658728f06d3a7d98340aa28ff3e3ec.tar.gz | |
feat: slop web gui
Diffstat (limited to 'lib/project/tests/test_gui_browser_races.py')
| -rw-r--r-- | lib/project/tests/test_gui_browser_races.py | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/project/tests/test_gui_browser_races.py b/lib/project/tests/test_gui_browser_races.py new file mode 100644 index 0000000..fab60ba --- /dev/null +++ b/lib/project/tests/test_gui_browser_races.py @@ -0,0 +1,210 @@ +"""Additional opt-in browser race/cancellation coverage; uses the real API fixture.""" +import os +import re + +import pytest + +from test_gui_browser import studio # noqa: F401 -- shared real loopback fixture + +pytestmark = pytest.mark.skipif(os.environ.get("PRODUCER_BROWSER_TESTS") != "1", reason="Opt-in Playwright browser smoke") + + +def test_gui_browser_cancellation_is_not_pretend(studio): + from playwright.sync_api import sync_playwright, expect + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"]) + page = browser.new_page() + page.goto(studio["url"] + "/#token=" + studio["token"]) + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + studio["control"]["hold"] = True + page.locator("#render-preview").click() + assert studio["control"]["started"].wait(10) + expect(page.locator("#jobs .job").filter(has_text="running")).to_have_count(1, timeout=10000) + page.locator("#setting-denoise_strength").fill("0.4") + page.locator("#render-preview").click() + queued = page.locator("#jobs .job").filter(has_text="queued") + expect(queued).to_have_count(1) + queued.get_by_role("button", name="Cancel", exact=True).click() + expect(page.locator("#jobs .job").filter(has_text="cancelled")).to_have_count(1) + running = page.locator("#jobs .job").filter(has_text="running") + running.get_by_role("button", name="Cancel", exact=True).click() + expect(page.locator("#jobs")).to_contain_text("Cancelling…") + assert page.locator("#jobs .job").filter(has_text="cancelled").count() == 1 + studio["control"]["release"].set() + expect(page.locator("#jobs .job").filter(has_text="cancelled")).to_have_count(2, timeout=10000) + expect(page.locator("#play")).to_be_disabled() + browser.close() + + +def test_gui_browser_late_audio_is_aborted_and_urls_are_released(studio): + from playwright.sync_api import sync_playwright, expect + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"]) + page = browser.new_page() + page.add_init_script("""window.objectURLs = {created: [], revoked: []}; + const create = URL.createObjectURL.bind(URL), revoke = URL.revokeObjectURL.bind(URL); + URL.createObjectURL = blob => { const url = create(blob); objectURLs.created.push(url); return url; }; + URL.revokeObjectURL = url => { objectURLs.revoked.push(url); revoke(url); };""") + page.goto(studio["url"] + "/#token=" + studio["token"]) + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + page.locator("#selection-start").fill("1") + page.locator("#selection-end").fill("4") + page.locator("#render-preview").click() + expect(page.locator("#comparison-status")).to_contain_text("Ready", timeout=30000) + first_source = page.locator("#comparison-source").inner_text() + first_urls = page.evaluate("objectURLs.created.slice()") + assert len(first_urls) == 2 + held = [] + + def hold(route): + response = route.fetch() + held.append((route, response)) + + page.route("**/api/results/**", hold) + page.locator("#selection-start").fill("2") + page.locator("#render-preview").click() + for _ in range(200): + if len(held) == 2: + break + page.wait_for_timeout(50) + assert len(held) == 2 + expect(page.locator("#comparison-status")).to_contain_text("Loading") + page.locator("#selection-start").fill("3") + for route, response in held: + route.fulfill(response=response) + page.wait_for_timeout(500) + assert page.locator("#comparison-source").inner_text() == first_source + expect(page.locator("#comparison-status")).to_contain_text("changed since preview") + assert page.evaluate("objectURLs.created.length") == 2 + page.unroute("**/api/results/**", hold) + page.locator("#render-preview").click() + expect(page.locator("#comparison-status")).to_contain_text("Ready", timeout=30000) + assert page.evaluate("objectURLs.created.length") == 4 + assert page.evaluate("objectURLs.revoked") == first_urls + browser.close() + + +def test_gui_browser_nonzero_monitoring_gain_is_playback_only(studio): + from playwright.sync_api import sync_playwright, expect + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"]) + page = browser.new_page() + page.add_init_script("""window.monitorGains = []; + const createGain = AudioContext.prototype.createGain; + AudioContext.prototype.createGain = function() { + const gain = createGain.call(this); monitorGains.push(gain); return gain; + };""") + page.goto(studio["url"] + "/#token=" + studio["token"]) + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + page.locator("#advanced-settings summary").click() + # Real FFmpeg leveling makes a measurable difference without any AI runtime. + page.locator("#setting-leveling").check() + page.locator("#setting-level_target_db").fill("-12") + page.locator("#render-preview").click() + expect(page.locator("#comparison-status")).to_contain_text("Ready", timeout=30000) + requested = page.locator("#preview-settings").text_content() + meters = page.locator("#meters").inner_text() + page.locator("#match").check() + expect(page.locator("#monitoring-note")).to_contain_text("Monitoring only") + gains = page.evaluate("monitorGains.map(node => node.gain.value)") + assert len(gains) == 2 + assert all(0 < gain <= 1 for gain in gains) + assert min(gains) < .9, "A nonzero measured loudness difference should attenuate one playback side" + assert page.locator("#preview-settings").text_content() == requested + assert page.locator("#meters").inner_text() == meters + page.locator("#prepare-export").click() + expect(page.locator("#export-dialog")).to_be_visible() + frozen_export = page.locator("#export-settings").text_content() + assert '"level_target_db": -12' in frozen_export + assert "original_gain_db" not in frozen_export and "processed_gain_db" not in frozen_export + assert "monitoring" not in frozen_export + page.locator("#cancel-export").click() + page.locator("#match").uncheck() + assert page.evaluate("monitorGains.map(node => node.gain.value)") == [1, 1] + browser.close() + + +@pytest.mark.parametrize("kind", ["scan", "preview"]) +def test_delayed_job_list_preserves_new_submission_intent(studio, kind): + from playwright.sync_api import sync_playwright, expect + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"]) + page = browser.new_page() + page.goto(studio["url"] + "/#token=" + studio["token"]) + if kind == "preview": + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + held = [] + + def hold_one_list(route): + if route.request.method == "GET" and not held: + held.append((route, route.fetch())) + else: + route.continue_() + + page.route("**/api/jobs", hold_one_list) + for _ in range(100): + if held: + break + page.wait_for_timeout(50) + assert held, "Expected an in-flight list captured before the new submission" + with page.expect_response(lambda response: response.request.method == "POST" + and response.url.endswith("/api/jobs")) as posted: + if kind == "scan": + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + else: + page.locator("#render-preview").click() + assert posted.value.status == 202 + expect(page.locator("#jobs .job").filter(has_text=kind.upper())).to_have_count(1) + route, response = held[0] + route.fulfill(response=response) + if kind == "scan": + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + else: + expect(page.locator("#comparison-status")).to_contain_text("Ready", timeout=30000) + browser.close() + + +@pytest.mark.parametrize("interruption", ["switch", "edit", "expire"]) +def test_delayed_audio_resume_does_not_start_superseded_playback(studio, interruption): + from playwright.sync_api import sync_playwright, expect + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True, args=["--no-sandbox"]) + page = browser.new_page() + page.add_init_script("""window.releaseAudioResume = null; + const resume = AudioContext.prototype.resume; + AudioContext.prototype.resume = function() { + return new Promise(resolve => { + window.releaseAudioResume = () => { resume.call(this).then(resolve); }; + }); + };""") + page.goto(studio["url"] + "/#token=" + studio["token"]) + page.get_by_role("button", name=re.compile(r"WAV\s*Narration.wav")).click() + expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) + page.locator("#render-preview").click() + expect(page.locator("#comparison-status")).to_contain_text("Ready", timeout=30000) + page.locator("#play").click() + page.wait_for_function("typeof releaseAudioResume === 'function'") + if interruption == "switch": + page.locator("#listen-processed").click() + expect(page.locator("#listen-processed")).to_have_attribute("aria-pressed", "true") + elif interruption == "edit": + page.locator("#setting-denoise_strength").fill("0.4") + else: + page.route("**/api/jobs", lambda route: route.fulfill( + status=401, content_type="application/json", body='{"detail":"Expired"}')) + expect(page.locator("#workspace")).to_have_attribute("disabled", "", timeout=10000) + page.evaluate("releaseAudioResume()") + # Let both the resume and any erroneously scheduled play promise settle. + page.wait_for_timeout(300) + assert page.locator("#audio-original").evaluate("audio => audio.paused") + assert page.locator("#audio-processed").evaluate("audio => audio.paused") + browser.close() |
