doc.haus Atlas Legal-document workflows on OpenCode, bound to their code
219 documents
packages/storybook/playwright/README.md

Component browser-test system: production Solid components exercised through Storybook stories with per-package isolated suites and Turbo filters, story/spec ownership rules, migration provenance comments, and guidance on what stays in E2E versus moves to stories. Read when adding or migrating component browser tests.

Component browser tests

Production Solid components are tested through their existing Storybook stories without booting the app, configuring a server, seeding browser storage, or navigating unrelated routes.

Keep each spec in the package that owns its production component:

Run a package's isolated browser suite from that package:

# Session UI components.
cd packages/session-ui
bun run test:components
bun run test:components -- component-tests/session-timeline.spec.ts
bun run test:components:ui

# App-owned components.
cd packages/app
bun run test:components
bun run test:components -- component-tests/composer.spec.ts

Both suites are separately filterable Turbo tasks:

bun turbo test:components --filter=@opencode/session-ui
bun turbo test:components --filter=@opencode/app

Component browser coverage deliberately remains separate from each package's default test script and from packages/app's test:e2e, so expensive Storybook checks can be scheduled independently from required unit and full-app journey CI. Set PLAYWRIGHT_STORYBOOK_URL to reuse an existing Storybook instance or PLAYWRIGHT_STORYBOOK_PORT to choose its port.

Adding a test

Keep inspectable scenarios next to the production component in a *.stories.tsx file. A story owns its fixtures, providers, state, and callbacks; its package-local spec owns user-visible interactions and assertions.

import { expect, story } from "../../storybook/playwright/story"

// Moved from packages/app/e2e/regression/session-timeline-context-state.spec.ts
story("preserves collapsed state while a tool completes", async ({ mount }) => {
  const component = await mount("current-session-research-agents--agent-research", {
    args: { scenario: "exploration" },
  })
  const trigger = component.locator('[data-slot="collapsible-trigger"]')

  await expect(trigger).toHaveAttribute("aria-expanded", "false")
  await component.getByRole("button", { name: "Complete read" }).click()
  await expect(trigger).toHaveAttribute("aria-expanded", "false")
})

The story ID is the Storybook component ID followed by -- and the kebab-cased story export. Open the same story in Storybook to inspect exactly the scenario covered by the browser test. Preserve an original-source-path comment for every migrated E2E case.

Keep cross-route navigation, remote-server ownership, persistent session state, full-app virtualization, and workflows spanning independent surfaces in packages/app/e2e/.

Component rendering and integration coverage can be complementary. A local story control that installs a completed message does not test event delivery, production reducer cleanup, or a live stream. Keep those original checks in E2E, including stream/chunk identity, compaction and retry events, independent lifecycle transitions, and the real app scroll owner. A provenance comment records the source of a component assertion; it is not evidence that its integration counterpart can be deleted.

When moving an assertion, preserve its discriminating fixture: file status kinds, empty/single-variant inputs, singleton groups, live message state, and the order of intermediate updates. Verify the actual scroll container overflows before asserting that keyboard activation does not scroll it.