diff --git a/.github/workflows/studio-mac-ui-smoke.yml b/.github/workflows/studio-mac-ui-smoke.yml index 403706bb3b..b820c3f0b2 100644 --- a/.github/workflows/studio-mac-ui-smoke.yml +++ b/.github/workflows/studio-mac-ui-smoke.yml @@ -93,15 +93,47 @@ jobs: # needs already. # Pinned <1.58 because all 1.55-1.58 drivers ship Node 24 on # macos-14 and intermittently hit 'SyntaxError: Unexpected end - # of JSON input' in pipeTransport.js when the Chromium child - # transiently flushes an empty buffer. The crash is racy and - # only triggers on a fraction of runs; the retry wrapper in - # "Drive the chat UI with Playwright" is what actually keeps - # the job green when the race fires. + # of JSON input' in pipeTransport.js. Run 25491698868 showed + # the crash hitting 100% of three retry attempts -- not a + # rare race but a hard reproduction. Belt-and-suspenders fix: + # the test scripts pass --single-process to Chromium (see + # tests/studio/playwright_chat_ui.py) AND we patch + # pipeTransport.js below to swallow JSON parse errors instead + # of crashing the driver Node process. Both together let the + # in-script retry recover from any residual flakes. run: | pip install 'playwright>=1.55,<1.58' python -m playwright install chromium + - name: Patch Playwright pipeTransport.js to tolerate malformed JSON + # In Playwright 1.55-1.58, pipeTransport.js does + # `JSON.parse(message)` with no try/catch; when Chromium dies + # mid-write the partial buffer crashes the driver Node + # process and the test script exits with 'Connection closed + # while reading from the driver'. Newer Playwright versions + # added a try/catch upstream. Backport that here. + run: | + python - <<'PY' + import os, re, sys + import playwright + driver_dir = os.path.join(os.path.dirname(playwright.__file__), "driver", "package", "lib", "server") + path = os.path.join(driver_dir, "pipeTransport.js") + src = open(path).read() + # Wrap both `this.onmessage.call(null, JSON.parse(...))` sites in try/catch. + patched = re.sub( + r"this\.onmessage\.call\(null, JSON\.parse\((message2?)\)\);", + r"try { this.onmessage.call(null, JSON.parse(\1)); } " + r"catch (e) { /* swallow malformed JSON from a crashing browser */ }", + src, + ) + if patched == src: + # Already patched, or upstream changed -- either way, don't fail the build. + print(f"pipeTransport.js: no JSON.parse calls matched at {path}; skipping.") + else: + open(path, "w").write(patched) + print(f"pipeTransport.js: patched JSON.parse calls in {path}") + PY + - name: Reset auth + boot Studio run: | unsloth studio reset-password diff --git a/tests/studio/playwright_chat_ui.py b/tests/studio/playwright_chat_ui.py index e0316313bd..c7c9189eef 100644 --- a/tests/studio/playwright_chat_ui.py +++ b/tests/studio/playwright_chat_ui.py @@ -118,7 +118,26 @@ def parse_rgb(s): with sync_playwright() as p: - browser = p.chromium.launch(headless = True) + # Chromium stability args for macos-14 free runners. Without these + # Chromium browser process dies in the first few seconds (during + # change-password page load) and the driver Node process can't + # parse the truncated stdout JSON-RPC line, throwing + # 'SyntaxError: Unexpected end of JSON input' in pipeTransport.js + # — see runs 25491698868 / 25489049059. --disable-dev-shm-usage and + # --no-sandbox are the standard set; --disable-gpu forces software + # rendering on the headless runner; --single-process keeps the + # renderer in the same process as the browser, eliminating the + # browser↔renderer IPC pipe that was the actual crash site. + _CHROMIUM_STABILITY_ARGS = [ + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-gpu", + "--single-process", + ] + browser = p.chromium.launch( + headless = True, + args = _CHROMIUM_STABILITY_ARGS, + ) ctx = browser.new_context( viewport = {"width": 1280, "height": 900}, # Reduces motion so the theme toggle's view-transition diff --git a/tests/studio/playwright_extra_ui.py b/tests/studio/playwright_extra_ui.py index b71500a39d..72521b336b 100644 --- a/tests/studio/playwright_extra_ui.py +++ b/tests/studio/playwright_extra_ui.py @@ -81,7 +81,20 @@ def runtime_warn(m: str) -> None: with sync_playwright() as p: - browser = p.chromium.launch(headless = True) + # Chromium stability args -- same set as playwright_chat_ui.py. + # Without these Chromium dies in the first seconds on macos-14 + # free runners and pipeTransport.js throws + # 'SyntaxError: Unexpected end of JSON input'. + _CHROMIUM_STABILITY_ARGS = [ + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-gpu", + "--single-process", + ] + browser = p.chromium.launch( + headless = True, + args = _CHROMIUM_STABILITY_ARGS, + ) ctx = browser.new_context( viewport = {"width": 1280, "height": 900}, reduced_motion = "reduce",