Skip to content

Storybook smoke: toBeVisible reads a freshly opened Astryx layer as hidden while its entry animation is pending #4896

Description

@orangeCatDeveloper

What happened

The Storybook smoke on CI intermittently fails product-composer-slash-menu--context-switch-starts-with-a-loading-catalog (4 of the 36 PR runs that reached the smoke between 2026-09-05 19:39Z and 2026-09-06 03:03Z, e.g. run 34007377176) and its sibling product-shell-official-appshell--plus-menu-during-skill-refresh (run 34003729007), both with:

[product-composer-slash-menu--context-switch-starts-with-a-loading-catalog (light/default)] console.error: HL: expect(element).toBeVisible()

Received element is not visible:
  <div aria-label="添加上下文" class="astryx-dropdown-menu … maka-composer-plus-panel" id="_r_3_" role="menu" tabindex="-1" />

The menu never closed. jest-dom's toBeVisible walks up to the Astryx layer element ([popover="auto"]) and reads its computed opacity. The layer enters through layerAnimations (@astryxdesign/core/dist/Layer/layerAnimations.stylex.js), a keyframe animation with animation-fill-mode: backwards, so until the first frame ticks the animation the computed opacity is the from value, 0. jest-dom treats exactly '0' as hidden. On a 2 vCPU runner driving four pages, showPopover() can go 200+ ms without a rendering frame, and the assertion lands inside that window.

#4889 retries the slash-menu assertion with waitFor, which is sufficient for that one site (the waitFor stops at the first ticked frame, or times out if the menu did close). Its description attributes the failure to the menu "being between renders"; the popup is stable, only its computed opacity is. The same read exists in the app-shell sibling (app-shell.stories.tsx, PlusMenuDuringSkillRefresh) and after the final findByRole('listbox') in both stories, and the app-shell harness holds the Skill refresh on a setTimeout(…, 150) that a slow runner outlives before the row is clicked.

How to reproduce

Never on macOS (44 runs, including 6× CPU throttling). Reproduces in the Playwright Linux image with the story rendered on four concurrent pages:

docker run --rm --cpus=2 -v "$PWD:$PWD:ro" -v "$PWD/scratch:/scratch" \
  -e ROUNDS=40 -e CONC=4 mcr.microsoft.com/playwright:v1.62.1-noble node /scratch/repro-slash.mjs

where repro-slash.mjs opens iframe.html?id=<story>&viewMode=story on apps/desktop/storybook-static the way scripts/storybook-visual-smoke.mjs does, and hooks getComputedStyle, showPopover and animation events.

pass=35 fail=5

Trace of one failure (origin/main d2d7efe, before #4889):

1110ms showPopover div#_r_4_ inner=menu/添加上下文
1200ms click div role=menuitem label=选择技能 busy=true
1330ms gcsHit div#_r_4_ display=block opacity=0 visibility=visible popoverOpen=true :: window.getComputedStyle < Jl < Xl < Xl < Xl < Object.Zl   (jest-dom toBeVisible)
1343ms animationstart x1ahk7ht-B div#_r_4_ inner=menu/添加上下文
1496ms animationend x1ahk7ht-B div#_r_4_ inner=menu/添加上下文

Same span in a passing run: showPopover 376ms, animationstart 403ms, click 414ms. The ancestor chain dumped a few hundred milliseconds after the failure is fully visible (display=block opacity=1 popover=auto open=true, no running animations).

The app-shell sibling fails for the timer as well: 40 container runs, 25 failures, of which 18 toBeVisible failures show the editor already containing / and the Skill listbox open (the click landed after the 150ms hold expired and activated the row for real), and 8 fail earlier at toHaveAttribute('aria-busy', 'true').

Environment

CI ubuntu-24.04, scripts/storybook-visual-smoke.mjs at concurrency 4, headless Chromium from Playwright 1.62.1. Stories introduced by #4825.

Logs, screenshots, or additional context

Emulating prefers-reduced-motion: reduce in the smoke script is not a fix: Astryx drops the layer animation under it, but scroll-motion-policy.ts also switches to instant scrolling and appshell--offscreen-active-turns-stay-findable / appshell--first-rail-click-lands-on-its-prompt-and-holds fail by design (the latter asserts "a reduced-motion browser finishes the jump in one frame and this story stops testing anything").

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions