--- name: visual-test description: Visually verify a component by launching its Storybook story and taking a screenshot with playwright-cli. Use after making visual changes to a component. disable-model-invocation: true argument-hint: [story-name] allowed-tools: Bash Read Grep Glob --- # Visual Test a Component Visually verify **$ARGUMENTS** by launching Storybook and capturing a screenshot with `playwright-cli`. ## Prerequisites Run `playwright-cli` via `npx` so nothing is installed globally on the user's box. The first invocation downloads `@playwright/cli@0.1.1` into the npx cache; subsequent calls are cached. Every command below uses this form: ```bash npx -y @playwright/cli@0.1.1 ``` ## Critical: use the per-component Storybook only Always boot the **per-component stories package** (`react--stories`) via nx `storybook` target, which only imports its own component's stories and dependencies. ## Steps 1. **Find the component's stories package.** Each v9 component has a dedicated stories package named `react--stories`: ```bash yarn nx show project react--stories --json ``` If nx returns nothing with output of `Could not find project react--stories`, the component doesn't have its own stories package — check for a preview package (`react--preview-stories`) or ask before proceeding. 2. **Start the component's Storybook dev server.** Use the `storybook` target on the stories project directly — it's the most portable, since library aliases like `react-:start` were only added in April 2026 and may not exist in older workspace snapshots: ```bash yarn nx run react--stories:storybook & ``` 3. **Find the storybook port.** Three quirks to know: - Storybook picks a **random high port** on first boot (e.g. `49360`), not the Storybook default `6006`. Don't assume. - The nx wrapper process often exits 0 after delegating to storybook, leaving the actual server running as a child. So the nx PID isn't the storybook PID. - The storybook child opens **two** listening sockets: one for HTTP content, one for the webpack HMR event-stream. They are not ordered — either one can be numerically lower. Picking by port number is unreliable; pick by `Content-Type`. Reliable detection — target the storybook node child (not the yarn wrapper), then probe each listening socket until one returns `text/html`: ```bash # Wait up to 180s for the storybook child to bind an HTTP port. # Pattern matches the node child specifically, not `yarn storybook dev` (the wrapper has no sockets). for i in $(seq 1 180); do SB_CHILD=$(pgrep -f "node.*\.bin/storybook dev" | head -1) if [ -n "$SB_CHILD" ]; then for port in $(lsof -a -p "$SB_CHILD" -i -P -sTCP:LISTEN 2>/dev/null | awk 'NR>1 {print $9}' | sed 's/.*://'); do CT=$(curl -sI --max-time 2 "http://localhost:$port/" 2>/dev/null | grep -i '^content-type:' | grep -i 'text/html') if [ -n "$CT" ]; then SB_PORT=$port; break; fi done if [ -n "$SB_PORT" ]; then break; fi fi sleep 1 done echo "Storybook child PID=$SB_CHILD on port $SB_PORT" ``` Then wait for Storybook to finish compiling stories — the HTTP port answers before `index.json` is populated: ```bash for i in $(seq 1 60); do N=$(curl -s --max-time 2 "http://localhost:$SB_PORT/index.json" 2>/dev/null \ | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('entries', {})))" 2>/dev/null || echo 0) if [ "$N" -gt 0 ]; then break; fi sleep 2 done ``` If no port turns up, or `index.json` never populates — **do not** fall back to the workspace-wide Storybook; read the nx output log and debug the per-component boot. The most common real failure is missing build artifacts for unstable re-export deps (see troubleshooting below). 4. **Open the page with playwright-cli:** ```bash npx -y @playwright/cli@0.1.1 open "http://localhost:$SB_PORT" ``` 5. **Navigate to the specific story iframe** and capture a screenshot. Use the iframe URL for a clean render without Storybook chrome: ```bash npx -y @playwright/cli@0.1.1 goto "http://localhost:$SB_PORT/iframe.html?id=components---default&viewMode=story" npx -y @playwright/cli@0.1.1 screenshot --filename=/tmp/visual-test-$ARGUMENTS.png ``` 6. **View the screenshot** using the Read tool to visually inspect the rendered component. 7. **Use `snapshot`** to get the accessibility tree and find interactive element refs: ```bash npx -y @playwright/cli@0.1.1 snapshot ``` Then interact with elements by ref (e.g., click, hover) before taking more screenshots. 8. **If the component doesn't look right**, go back to the code, fix the issue, and repeat from step 4 (Storybook hot-reloads changes). 9. **Clean up** when done: ```bash npx -y @playwright/cli@0.1.1 close # Kill storybook — the nx wrapper may already be gone, so target the child [ -n "$SB_CHILD" ] && kill "$SB_CHILD" 2>/dev/null lsof -i :$SB_PORT -t 2>/dev/null | xargs kill 2>/dev/null ``` ## Troubleshooting **`yarn nx run react--stories:storybook` says the target doesn't exist.** The workspace graph may be stale (recent reparent). Run `yarn nx reset` then retry. If `stroybook` aliases still don't exist, use the direct yarn invocation: ```bash cd packages/react-components/react-/stories && yarn storybook dev --port 0 & # --port 0 asks Storybook to pick a free port; detect it via the pgrep/lsof pattern above ``` ## Story ID Pattern Story IDs follow the pattern `---`: ``` # Default story for Button components-button--default # Appearance variant components-button--appearance # Default story for Menu components-menu--default ``` To discover exact story IDs, open the Storybook sidebar and use `snapshot` to find navigation links, or check the story file's `export default { title: '...' }` metadata. ## Iframe URL Format ``` # Local storybook (replace $SB_PORT with the actual port) http://localhost:$SB_PORT/iframe.html?id=components-button--default&viewMode=story # Dark theme http://localhost:$SB_PORT/iframe.html?id=components-button--default&viewMode=story&globals=theme:webDarkTheme ``` The `/iframe.html` URL gives a clean render without Storybook chrome — always prefer this for screenshots. ## Tips - Use `npx -y @playwright/cli@0.1.1 snapshot` to get an accessibility tree — useful for verifying ARIA attributes and finding interactive elements. - Use `npx -y @playwright/cli@0.1.1 click ` to interact with the component (test hover states, open menus, etc.) before taking a screenshot. - Use `npx -y @playwright/cli@0.1.1 resize ` to test responsive behavior. - For multiple story variants, take a screenshot of each: Default, Appearance, Size, Disabled, etc.