"""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, channel=os.environ.get("PRODUCER_BROWSER_CHANNEL") or None, args=["--no-sandbox"]) page = browser.new_page() page.goto(studio["url"]) page.locator("#pick-files").set_input_files(studio["source"]) 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) # The launch override disables denoising, hiding its strength control; # enable the denoiser module (checkbox), then pick FFT and edit # another control while the preview is running. page.locator("#enable-denoiser").check() expect(page.locator("#setting-denoiser")).to_be_enabled() page.locator("#setting-denoiser").select_option("fft") expect(page.locator("#setting-denoise_strength")).to_be_visible() 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, channel=os.environ.get("PRODUCER_BROWSER_CHANNEL") or None, 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"]) page.locator("#pick-files").set_input_files(studio["source"]) 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, channel=os.environ.get("PRODUCER_BROWSER_CHANNEL") or None, 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"]) page.locator("#pick-files").set_input_files(studio["source"]) expect(page.locator("#wave-status")).to_contain_text("2,048", timeout=10000) # 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 # The old pacing export dialog is gone; export submits directly and # nothing exists in the output directory to overwrite. page.locator("#output-dir").fill(str(studio["root"] / "delivery")) page.locator("#prepare-export").click() expect(page.locator("#export-results")).to_contain_text("Full recording exported", timeout=30000) 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, channel=os.environ.get("PRODUCER_BROWSER_CHANNEL") or None, args=["--no-sandbox"]) page = browser.new_page() page.goto(studio["url"]) if kind == "preview": page.locator("#pick-files").set_input_files(studio["source"]) 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.locator("#pick-files").set_input_files(studio["source"]) 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, channel=os.environ.get("PRODUCER_BROWSER_CHANNEL") or None, 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"]) page.locator("#pick-files").set_input_files(studio["source"]) 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": # The launch override hides the denoiser module; enable it and # pick FFT so the strength control is editable. page.locator("#enable-denoiser").check() page.locator("#setting-denoiser").select_option("fft") page.locator("#setting-denoise_strength").fill("0.4") else: page.route("**/api/jobs", lambda route: route.fulfill( status=500, content_type="application/json", body='{"detail":"down"}')) expect(page.locator("#connection")).to_contain_text("interrupted", 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()