selection.spec.ts's "the ruler lays out time gradations, and stays put in frame mode" asserts an exact gradation count:
await expect(page.locator("#selruler .sel-tick")).toHaveCount(5);
That count is derived from the loaded video's measured duration and frame rate, and the video it loads is ?test&mock_video's synthesized clip. synthesizeVideoFile captures through MediaRecorder in real time — 30 frames with await new Promise((r) => setTimeout(r, 33)) between them — so the clip's actual duration is whatever the machine took to record it. Under CPU contention it runs long, the fps MediaBunnyVideoBackend derives from the packet span shifts, and rulerStep lands on the next gradation down: 6 ticks instead of 5.
Reproduced on unmodified origin/main (5143eea), no changes applied:
npx playwright test selection.spec.ts -g "the ruler lays out time gradations" --repeat-each=12 --workers=4
3 failed
9 passed
Run on its own it passes every time; it only fails when something else is competing for the CPU, which is exactly what the full suite at fullyParallel: true does — and what a loaded CI runner does.
Two directions, either of which would settle it:
- Assert a range rather than an exact count, or assert the ruler is built from the video at all (that there is more than one gradation and the first is labelled
0:00) rather than pinning the number. The test's own comment says the point is "that the ruler is built from the loaded video rather than hard-coded", which a range still shows.
- Give the test a clip whose duration does not depend on wall-clock time.
synthesizeLongVideoFile already writes samples at explicit timestamps through mediabunny's Output/CanvasSource, decoupled from real time entirely; a short fixed-duration mock built the same way would make this count deterministic.
Found while running the suite for #53, which does not touch rulerMarks, the mock's frame count, or its timing — the flake is left alone there rather than papered over.
selection.spec.ts's "the ruler lays out time gradations, and stays put in frame mode" asserts an exact gradation count:That count is derived from the loaded video's measured duration and frame rate, and the video it loads is
?test&mock_video's synthesized clip.synthesizeVideoFilecaptures throughMediaRecorderin real time — 30 frames withawait new Promise((r) => setTimeout(r, 33))between them — so the clip's actual duration is whatever the machine took to record it. Under CPU contention it runs long, the fpsMediaBunnyVideoBackendderives from the packet span shifts, andrulerSteplands on the next gradation down: 6 ticks instead of 5.Reproduced on unmodified
origin/main(5143eea), no changes applied:Run on its own it passes every time; it only fails when something else is competing for the CPU, which is exactly what the full suite at
fullyParallel: truedoes — and what a loaded CI runner does.Two directions, either of which would settle it:
0:00) rather than pinning the number. The test's own comment says the point is "that the ruler is built from the loaded video rather than hard-coded", which a range still shows.synthesizeLongVideoFilealready writes samples at explicit timestamps through mediabunny'sOutput/CanvasSource, decoupled from real time entirely; a short fixed-duration mock built the same way would make this count deterministic.Found while running the suite for #53, which does not touch
rulerMarks, the mock's frame count, or its timing — the flake is left alone there rather than papered over.