diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md index 3ba11b967..601a3818e 100644 --- a/tests/CLAUDE.md +++ b/tests/CLAUDE.md @@ -388,6 +388,16 @@ property in transit for two frames. That is fixed in the theme, which is why no reading here carries such a wait — a wait in front of a reading whose subject never moves is a mechanism that cannot fail and cannot help. +A wait states its end as well as its fact, and only Playwright's own waits state one by +default. `page.evaluate` takes no timeout in any binding, so a promise awaited inside it +— an animation's `finished`, a module's load, a listener's next call — is a wait nothing +bounds. It does not fail in thirty seconds naming its test; it spends the job's whole +step bound, and the share of the suite already handed to that worker never runs. Race +the await against a `setTimeout` that rejects naming what never arrived, as the fold in +`test_a_thread_reopened_mid_fold_folds_again_when_it_settles` and the diff render in +`test_render_anchors.py` do, or state the fact from inside the page and read it with +`wait_for_function`. + ### A state the page passes through is not a state to poll for Use Playwright's `expect(...)` for a state that will become stable and remain true. diff --git a/tests/test_render_conversations.py b/tests/test_render_conversations.py index ced7cda67..2f3c2fa38 100644 --- a/tests/test_render_conversations.py +++ b/tests/test_render_conversations.py @@ -1128,10 +1128,18 @@ def test_a_thread_reopened_mid_fold_folds_again_when_it_settles(browser, serve): # once: its node left the list when the thread reopened, and the record it must # not clear on its way is the live fold's. Cleared, the thread is pulled out of # the list in the middle of the motion carrying it away. + # The await states its own end. `page.evaluate` takes no timeout in any binding, so + # an animation that never settles `finished` is a wait nothing bounds: on run + # 33165671503 this one didn't, and what should have been this test failing in thirty + # seconds under its own name was the job's whole 45-minute bound, spent here, with + # the quarter of the suite already handed to this worker never run. Thirty seconds + # because that is where every other browser wait in the suite runs out, and the + # rejection comes back through `evaluate` naming the motion that stopped. page.evaluate( "async (i) => { const m = window.__lfHeld[i];" " m.play(); m.currentTime = m.effect.getComputedTiming().duration;" - " await m.finished; }", + " await Promise.race([m.finished, new Promise((_, ranOut) => setTimeout(" + " () => ranOut(new Error(`held fold ${i} never ran out`)), 30_000))]); }", before, ) expect(going.locator(f'.lf-msg[data-mid="{reply["id"]}"]')).to_have_count(1)