What
@screenshot e2e assertions that frame a Vue node containing a text widget are exposed to a text-rasterization nondeterminism that is not specific to any one spec. It has already ejected unrelated PRs from the merge queue.
Evidence
Traced while fixing one instance in #15356 (pan.spec.ts → @mobile Can pan with touch):
- The failure is always
40 pixels (ratio 0.01 of all image pixels) are different, and the actual/diff PNGs are byte-identical across different CI runners.
- Every pixel outside the
ckpt_name widget's value text is identical between passing and failing runs, including the node's own transform and border. The diff is anti-aliasing inside the glyphs of a truncated value — identical glyph positions (per-glyph centroid drift < 0.09px), roughly 5% less ink.
- Playwright already captures a stable screenshot and re-fails all three retries with the same 40 pixels, so no readiness signal can wait it out.
Why this is not one test's problem
Nothing in that mechanism is specific to panning. The conditions are: a text widget whose value is truncated, inside a filtered and contain-ed node subtree, captured by an exact-match screenshot. Those conditions hold in every @screenshot test that frames a Vue node with a text widget.
Impact
Not just red PR checks — silent merge-queue ejection of already-approved changes. Three queue branches on 2026-08-17 on the same base 926d78c8bf: pr-14068 and pr-15067 both failed with the identical 40 pixels (neither can move a canvas), while pr-15108 passed on the same base.
Suggested direction
Decide this once, centrally, rather than per spec as each one starts flaking:
- a default
maxDiffPixels for @screenshot assertions that frame node text, sized well below a real regression (a 1px pan error moves 3615 px; an observed genuine rendering change moved 3146), or
- masking the widget value text where the assertion is about geometry rather than text, or
- pinning whatever in the font/rasterization stack is varying, if it can be identified.
15356 bounds the single known instance; this issue is for the shared cause.
What
@screenshote2e assertions that frame a Vue node containing a text widget are exposed to a text-rasterization nondeterminism that is not specific to any one spec. It has already ejected unrelated PRs from the merge queue.Evidence
Traced while fixing one instance in #15356 (
pan.spec.ts→@mobile Can pan with touch):40 pixels (ratio 0.01 of all image pixels) are different, and the actual/diff PNGs are byte-identical across different CI runners.ckpt_namewidget's value text is identical between passing and failing runs, including the node's own transform and border. The diff is anti-aliasing inside the glyphs of atruncated value — identical glyph positions (per-glyph centroid drift < 0.09px), roughly 5% less ink.Why this is not one test's problem
Nothing in that mechanism is specific to panning. The conditions are: a text widget whose value is
truncated, inside a filtered andcontain-ed node subtree, captured by an exact-match screenshot. Those conditions hold in every@screenshottest that frames a Vue node with a text widget.Impact
Not just red PR checks — silent merge-queue ejection of already-approved changes. Three queue branches on 2026-08-17 on the same base
926d78c8bf:pr-14068andpr-15067both failed with the identical40 pixels(neither can move a canvas), whilepr-15108passed on the same base.Suggested direction
Decide this once, centrally, rather than per spec as each one starts flaking:
maxDiffPixelsfor@screenshotassertions that frame node text, sized well below a real regression (a 1px pan error moves 3615 px; an observed genuine rendering change moved 3146), or15356 bounds the single known instance; this issue is for the shared cause.