- 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
- <ComponentName> [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 <command>
```
## Critical: use the per-component Storybook only
Always boot the **per-component stories package** (`react-<component>-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-<component>-stories`:
```bash
yarn nx show project react-<lowercase-component-name>-stories --json
```
If nx returns nothing with output of `Could not find project react-<component>-stories`, the component doesn't have its own stories package — check for a preview package (`react-<component>-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-<component>:start` were only added in April 2026 and may not exist in older workspace snapshots:
```bash
yarn nx run react-<component>-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-<component>--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-<component>-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-<component>/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 `<category>-<component>--<story>`:
```
# 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 <ref>` to interact with the component (test hover states, open menus, etc.) before taking a screenshot.
- Use `npx -y @playwright/cli@0.1.1 resize <width> <height>` to test responsive behavior.
- For multiple story variants, take a screenshot of each: Default, Appearance, Size, Disabled, etc.
Auf GitHub ansehen