Skip to content

test(ProgressBar, CircularProgress): motion traces read screencast frames in the order they were presented - #9378

Merged
cixzhang merged 2 commits into
meta:mainfrom
vjeux:test/progress-motion-presentation-order
Nov 1, 2026
Merged

cixzhang merged 2 commits into
meta:mainfrom
vjeux:test/progress-motion-presentation-order

Conversation

@vjeux

@vjeux vjeux commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer impact

The ProgressBar storyboard check ("the fill moving from 20% to 80%") failed once in main's CI on a loaded runner, and its re-run passed. Its forward-only check read one frame's leading edge at 74 px right after a frame at 121 px.

Intended invariant

The motion traces judge the frames the page presented, in the order it presented them. A frame that really shows the fill behind an earlier one still fails.

Change

  • Cause: both specs judged their screencast frames in arrival order.
    • Chromium encodes each screencast frame off its main thread and can send a slow one after the next. Each frame carries the swap timestamp it was presented at.
    • 74 px is where the fill rests before the change. In a recorded run the frames go 74 (before the change), 74 (the frame that applies it), 122 (the first that moves), 164 and on.
    • The reported failure is the frame that applied the change arriving after the first one that moved.
  • ProgressBarMotion: the spec's screencast returns its frames in timestamp order through the interaction harness's inPresentationOrder, as the harness's own screencast does. Both of its tests that read the screencast, the storyboard and the main-thread block, now judge presentation order.
  • CircularProgressMotion: its main-thread-block test, which had the same arrival-order read and never-backwards check, calls inPresentationOrder too.
  • Lab devDependency: packages/lab gains @astryxdesign/interaction-harness (workspace:*) as a devDependency, as core already has. That adds three lines to Lab's importer in pnpm-lock.yaml. Lab is private, so no changeset.
  • No frame is dropped. No instrumented run repeated a timestamp, so every frame is kept, as in the harness's own screencast.
  • What still fails: a frame presented later that shows the fill behind an earlier one keeps its own later timestamp, so it stays in place and still fails the trace.
  • Only the two spec files, Lab's package.json and the lockfile change. No bound or tolerance changes.

Evidence

  • Failure before the change: a seam delivers the second screencast frame after the third, carrying its own (older) timestamp, as Chromium's encoder can.
    • ProgressBar storyboard: main's check fails 5 of 5 with the reported numbers ("Expected: >= 121, Received: 74").
    • The same delivery of two frames inside the CircularProgress block fails main's check 5 of 5.
  • Success after the change: both seams pass 5 of 5.
  • The checks still catch a real backward frame: a seam adds a frame showing the earlier picture, stamped just after a later frame. It fails both specs with this change, 5 of 5 each.
  • The specs at this head:
    • ProgressBarMotion, whole spec: Chromium ×10 with 1 worker, 110 of 110.
    • CircularProgressMotion, whole spec: ×5 with 1 worker, 50 of 50; pinned to two cores with 4 workers ×10, 100 of 100.
  • ProgressBarMotion pinned to two cores with 4 workers ×20: 216 of 220. Ordering does not cause these; the checks fail on main under the same load, and a follow-up takes them on:
    • three were the storyboard's jump check (one step of 90 px against 55.5): a frame the loaded capture missed doubles the step between the two around it;
    • one was the block test's check that the block starts before most of the trip (287 px against under 273.8): under load the block started after the fill had made 84% of its trip.
  • Under load, report-only: in 149 instrumented runs pinned to two cores with 4 to 8 workers, frames always arrived in timestamp order, none repeated a timestamp, and every frame was stamped. The reported reordering did not reproduce on this machine; the seam reproduces it deterministically.
  • Product/runtime behavior verified unchanged: only the two spec files, Lab's devDependencies and the lockfile change.

Scope

  • No intended public API, product behavior, visual direction, or policy change.
  • Product changes discovered during the work were removed or split.
  • Public text and artifacts contain no internal Meta context.

Testing

Local gates at 1a245be:

  • prettier and strict eslint on both files;
  • the core and lab typechecks, and pnpm install --frozen-lockfile;
  • pnpm lint, pnpm check:repo and pnpm check:knowledge;
  • the interaction harness's unit cases (104 passed);
  • changeset coverage (tests only, so no changeset).

…ames in the order they were presented

The ProgressBar storyboard's forward-only check failed once on a loaded
runner: one frame's leading edge read 74 px right after one at 121 px. 74 px
is where the fill rests before the change, and in a recorded run the frames
go 74 (before the change), 74 (the frame that applies it), 122 (the first
that moves), 164 and on. The frame that applied the change had arrived after
the first one that moved. Chromium encodes each screencast frame off its main
thread and can send a slow one after the next, while each frame carries the
swap timestamp it was presented at. Both specs judged frames in arrival
order.

- ProgressBarMotion takes its frames by that timestamp
  (`inPresentationOrder`, as the interaction harness's own screencast does)
  and drops a frame repeating a timestamp already kept. Both of its tests
  that read the screencast, the storyboard and the main-thread block, now
  judge presentation order.
- CircularProgressMotion's main-thread-block test reads its frames the same
  way.
- A frame presented later that shows the fill behind an earlier one keeps
  its own later timestamp, so it stays and still fails the trace.

Delivering the second frame after the third, with its own timestamp, made
main's storyboard check fail 5 of 5 with the reported numbers (121, then 74);
it passes 5 of 5 here. The same delivery inside the block fails main's
CircularProgress check 5 of 5 and passes here, and a later-stamped frame that
shows the old position fails both specs here, as it should.
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 1, 2026 7:22pm UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Nov 1, 2026
@astracat-bot

astracat-bot Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 1, 2026, 7:30 PM UTC

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 1a245be

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
ProgressBar · Storybook 7.9 kB (0) · 6.9 kB CSS (0) 413 (0) ✗ · ✓ · ✓
lab/CircularProgress · Storybook 8.5 kB (0) · 1.1 kB CSS (0) 597 (0) — · ✓ · —
Visual changes: 2 snapshots in 1 component

A change is a question for the reviewer, not a failure.

Component · story Before After Diff
ProgressBar · Default (neutral, dark) before frame after frame diff frame
ProgressBar · Default (neutral, light) before frame after frame diff frame

Package size: no published package changed; no new dependencies.

Touched components checked; add ci:run-visual, ci:run-a11y or ci:run-rtl to run the full suites.


Generated by PR Enrichment workflow | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordering the motion specs' screencast frames by their swap timestamp looks right, and a frame that really steps backwards with its own later timestamp still fails. One change is needed first: CircularProgressMotion.a11y.chromium.spec.ts copies the body of the harness's inPresentationOrder, and both specs add the same repeated-timestamp filter beside it, so frame ordering is now defined in three places. Please call inPresentationOrder from both specs (adding @astryxdesign/interaction-harness to Lab's devDependencies, as core already has it), and either drop the filter, which no run has needed, or move it into the shared helper. That also leaves the open ProgressBar and CircularProgress drafts a single call to pick up when they rebase.

[Automated review]

Comment on lines +310 to +312
const presented = [...raw]
.sort((a, b) => a.t - b.t)
.filter((frame, i, ordered) => i === 0 || frame.t > ordered[i - 1].t);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sort is the body of inPresentationOrder (internal/interaction-harness/src/frames.ts:193-197), and the filter repeats the one in the ProgressBar spec. Calling the shared helper here, with @astryxdesign/interaction-harness added to Lab's devDependencies as core already has it, keeps one definition of presentation order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 1a245be: both specs call inPresentationOrder, and Lab now has @astryxdesign/interaction-harness as a devDependency, which adds three lines to the lockfile.

Comment on lines +170 to +172
return inPresentationOrder(frames).filter(
(frame, i, ordered) => i === 0 || frame.t > ordered[i - 1].t,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repeated-timestamp filter lives only in these two specs; the harness's own screencast (frames.ts:124) keeps repeats. No run has repeated a timestamp, and a dropped frame is never judged, so either drop the filter or move it into the shared helper so both specs make one call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped in 1a245be: no instrumented run repeated a timestamp, and the harness's own screencast keeps every frame, so both specs now keep every frame too.

…ss's inPresentationOrder

Review follow-up. CircularProgressMotion called a copy of the harness's
inPresentationOrder, and both specs dropped a frame repeating a timestamp
already kept, which no run has needed. Both now call inPresentationOrder from
@astryxdesign/interaction-harness, which Lab gains as a devDependency (as
core has), and keep every frame.
@cixzhang cixzhang added the ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) label Nov 1, 2026
@cixzhang
cixzhang marked this pull request as ready for review November 1, 2026 19:19

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both motion specs now call the interaction harness's inPresentationOrder and keep every frame, which resolves the earlier request, and a frame that really steps backwards still fails because it keeps its own later timestamp. Both specs pass in Chromium at this revision, and the core and Lab typechecks are clean. No further changes are needed.

[Automated review]

@cixzhang
cixzhang merged commit 7d493c1 into meta:main Nov 1, 2026
113 checks passed
@vjeux
vjeux deleted the test/progress-motion-presentation-order branch November 1, 2026 19:59

This branch was successfully deployed

1 active deployment
Preview — 1a245beb Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run A maintainer lets this outside contributor's pull request run CI (AST-030 FR17) CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants