aboutsummaryrefslogtreecommitdiff
path: root/lib/project/tests/test_gui_browser_races.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-12 15:25:06 -0400
committerhistoria <historiavg@proton.me>2026-09-12 15:25:06 -0400
commit464edbc4fd658728f06d3a7d98340aa28ff3e3ec (patch)
treea182f62ef0365f2c2143f88436de78a03d752367 /lib/project/tests/test_gui_browser_races.py
parent1bbe59f78be9a8355bb0bee2bc8ab14ca42ac71c (diff)
downloadproducer-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.py210
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()