From 58c1248cf39a4fe71d13a71a654e6e5bc16383d2 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 21 Aug 2026 08:19:17 +0200 Subject: [PATCH] fix(site/src): repair failing storybook interaction tests and pixel captures (#28417) The Storybook suites fail on main in two independent ways: three interaction tests in the locally-run Vitest suite (`pnpm vitest run --project=storybook`, which does not run in CI), and three story captures in CI's pixel snapshot job, whose failures do not fail the GitHub check (the job PATCHes `status=failed` to pixel.coder.com and still exits 0). Vitest interaction failures: * `ModelForm > Cost Estimate Shows Loading While Debounce Pending` has been broken since it landed in #28298. In add mode the model identifier field is the Known Model autocomplete, which only commits typed text to `form.values.model` on option selection or dropdown close, so typing free text left the form's model empty and the pricing section never entered its loading state. The story now selects the catalog option. This also fixes the same story's capture failure in the pixel job. * The `SecretsPageView` import error stories raced the dialog enter animation: `findByText` resolves on DOM presence, and under parallel-suite CPU load `toBeVisible()` can run while the Radix dialog content is still at `opacity: 0` from its `fade-in-0` animation. These now poll with `waitFor(...toBeVisible())`, matching the file's other import stories. Pixel capture failures: * `NavbarView > ForMCPCreateOnlyAdmin` / `ForMCPDeleteOnlyAdmin` inherit the meta's tablet+desktop matrix but click the "Admin settings" button that only exists in the desktop navbar, so their tablet captures always fail. They now pin `pixelWithDesktop` like every other admin story in the file. * `AgentsPageLayout > OrganizationScopedMCPServers` fails intermittently because `CompactOrgSelector` option names are timing-dependent: when an org icon fails to load (as in the pixel static server), the avatar fallback initials render and join the option's accessible name (`"MY My Organization 2"`), breaking exact-name queries and polluting screen-reader output. The decorative avatar is now `aria-hidden`, which was verified red-green by forcing unloadable icons locally. > [!NOTE] > Shux (AI agent) authored this PR on behalf of @ibetitsmike. --- .../dashboard/Navbar/NavbarView.stories.tsx | 2 ++ .../components/ModelForm.stories.tsx | 13 ++++++--- .../ChatElements/CompactOrgSelector.tsx | 4 +++ .../SecretsPage/SecretsPageView.stories.tsx | 28 +++++++++++-------- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 8e2f0f7337f..03d24920d7c 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -158,6 +158,7 @@ export const ForMCPUpdateOnlyAdmin: Story = { export const ForMCPDeleteOnlyAdmin: Story = { decorators: [withAuthProvider], parameters: { + pixel: { matrix: pixelWithDesktop }, queries: [{ key: ["tasks", memberTasksFilter], data: [] }], user: MockUserMember, permissions: { @@ -205,6 +206,7 @@ export const ForMCPDeleteOnlyAdmin: Story = { export const ForMCPCreateOnlyAdmin: Story = { decorators: [withAuthProvider], parameters: { + pixel: { matrix: pixelWithDesktop }, queries: [{ key: ["tasks", memberTasksFilter], data: [] }], user: MockUserMember, permissions: { diff --git a/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx b/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx index 696dfaf6d0d..777c1dc202a 100644 --- a/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx +++ b/site/src/pages/AISettingsPage/ModelsPage/components/ModelForm.stories.tsx @@ -636,9 +636,11 @@ export const CostEstimateDebouncesLivePriceLookup: Story = { }, }; -// On an entitled form the live lookup may override the catalog, so typing -// into an empty identifier shows the loading placeholder immediately rather -// than briefly flashing catalog prices before the lookup fires. +// On an entitled form the live lookup may override the catalog, so +// committing a catalog model shows the loading placeholder immediately +// rather than briefly flashing catalog prices before the lookup fires. In +// add mode the identifier autocomplete only commits the model when an +// option is selected, so select one instead of typing free text. export const CostEstimateShowsLoadingWhileDebouncePending: Story = { args: { selectedProviderState: MockAnthropicProviderState, @@ -653,7 +655,10 @@ export const CostEstimateShowsLoadingWhileDebouncePending: Story = { ); await userEvent.type( canvas.getByLabelText(/model identifier/i), - "claude-haiku-4-5", + "claude-haiku", + ); + await userEvent.click( + await screen.findByRole("option", { name: /claude haiku 4\.5/i }), ); await waitForPriceLoading(canvas); // The catalog price must not render while the lookup is pending. diff --git a/site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx b/site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx index f7bd82fa759..57ff76c4002 100644 --- a/site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx +++ b/site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx @@ -104,6 +104,10 @@ export const CompactOrgSelector: FC = ({ > {" "} + expect(dialog.getByText("Failed to parse secrets file.")).toBeVisible(), + ); expect(dialog.getByText("Line 2 must contain KEY=VALUE.")).toBeVisible(); expect(dialog.queryByText("Response data")).not.toBeInTheDocument(); expect(dialog.queryByText("Stack Trace")).not.toBeInTheDocument(); @@ -785,9 +787,11 @@ export const ImportSecretsFileReadError: Story = { new File(["A=1"], "secrets.env"), ); - await expect( - await dialog.findByText("Failed to read the selected file."), - ).toBeVisible(); + await waitFor(() => + expect( + dialog.getByText("Failed to read the selected file."), + ).toBeVisible(), + ); }, }; @@ -838,11 +842,13 @@ export const ImportSecretsRemoveAndRetry: Story = { new File(["not a secret"], "bad.txt"), ); - await expect( - await dialog.findByText( - "Unsupported file type. Import a .env, .json, .yaml, or .yml file.", - ), - ).toBeVisible(); + await waitFor(() => + expect( + dialog.getByText( + "Unsupported file type. Import a .env, .json, .yaml, or .yml file.", + ), + ).toBeVisible(), + ); await user.click(dialog.getByRole("button", { name: "Remove file" })); expect( dialog.queryByText(