Shipkit qa visual
Skill stefan-stepzero/shipkit/install/skills/shipkit-qa-visual
Shipkit — AI-assisted product development framework for Claude Code. Skills, agents, and workflows for shipping MVPs fast.
npx -y skills add stefan-stepzero/shipkit --skill shipkit-qa-visualAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Visual QA using Playwright as a browser automation library. --setup installs Playwright and creates ui-goals.json; default mode writes inline scripts to navigate, screenshot, and report against goals.
SKILL.md
10.8 KB, as published. Nobody here has run it
shipkit-qa-visual
Visual QA for SaaS apps using Playwright as a lightweight browser automation library — not a test framework. Write an inline script, run it, screenshot meaningful states, read the screenshots, report against UI goals.
Modes
Parse $ARGUMENTS to determine mode:
--setup→ Run the Setup flow (one-time per project)- Anything else (or empty) → Run the Visual QA flow
Setup Mode (--setup)
One-time project setup. Walk through each step, skip any that are already done.
1. Install Playwright
Check if playwright is in package.json devDependencies. If not:
npm install -D @playwright/test playwright
Then install Chromium (warn user about ~200MB download):
npx playwright install chromium
2. Create playwright.config.ts
Only if it doesn't exist at the project root. Detect the app's base URL first:
- Check
$ARGUMENTSfor a URL - Probe common ports:
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000(try 3000, 5173, 4321, 8080, 6847) - Check
package.jsonscripts for port hints - Ask the user if none found
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: false,
retries: 0,
timeout: 120_000,
expect: { timeout: 30_000 },
use: {
baseURL: '<detected-url>',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
3. Create directories
mkdir -p screenshots e2e
Add screenshots/ to .gitignore if not already there.
4. Create UI Goals
This is the interactive step. The goals document grounds all future visual feedback.
Scan the codebase for routes/pages:
- Next.js: glob
app/**/page.{tsx,ts,jsx,js}andpages/**/*.{tsx,ts,jsx,js}(exclude_app,_document,api/) - React Router: grep for
<RouteorcreateBrowserRouter - Other: grep for route definitions
Propose goals to the user:
For each detected page, propose what that page should accomplish and what to look for. Present a summary:
Proposed UI Goals
Base URL: http://localhost:3000
Pages detected: 3
/ (Landing)
Goals: Clear value proposition above the fold, CTA visible and prominent
Look for: No layout shift after hydration
/app (Main App)
Goals: Input form clearly labeled, submit shows loading state, results render after completion
Look for: Loading spinner on submit, error states show actionable messages
/settings (Settings)
Goals: Current settings visible on load, changes save with confirmation
Look for: Form preserves values on page refresh
Reply 'confirm' to write, or describe changes.
Wait for user confirmation. Do not write until confirmed.
Write to .shipkit/ui-goals.json:
{
"baseUrl": "http://localhost:3000",
"pages": [
{
"path": "/",
"name": "Landing",
"goals": [
"Clear value proposition above the fold",
"CTA button visible and prominent"
],
"lookFor": [
"No layout shift after hydration"
]
}
],
"lastConfirmed": "2026-04-09T00:00:00Z",
"lastTested": null
}
Schema notes:
pages[].goals— What this page should accomplish. Plain language. These ground visual feedback.pages[].lookFor— Optional. Specific visual things to check — layout, contrast, loading states, error states. More tactical than goals.lastConfirmed— When user last reviewed and approved.lastTested— When screenshots were last taken and reviewed. Set tonulluntil first run.
Visual QA Flow (default)
This is the everyday mode. User describes what to test, Claude writes a script, runs it, reads the screenshots, reports.
Step 1: Load context
- Read
.shipkit/ui-goals.json— these goals ground your visual feedback - Read
playwright.config.tsforbaseURL - If neither exists, tell the user to run
/shipkit-qa-visual --setupfirst
If goals exist, show a one-line summary:
Goals loaded: 3 pages, last confirmed 2026-04-09
Step 2: Write the script
Based on what the user asked for (or if no description given, pick goals that haven't been visually verified recently), write an inline Node script.
Script template — every script must follow this pattern:
const { chromium } = require('playwright');
const fs = require('fs');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setViewportSize({ width: 1280, height: 900 });
// Console capture — always include this
const logs = [];
page.on('console', msg => logs.push(`[${msg.type()}] ${msg.text()}`));
page.on('pageerror', err => logs.push(`[ERROR] ${err.message}`));
// --- Navigation and interaction go here ---
await page.goto('http://localhost:3000/app');
await page.waitForTimeout(3000);
await page.screenshot({ path: 'screenshots/app-initial.png', fullPage: true });
// Interact
await page.locator('[data-testid="problem-input"]').fill('Some input');
await page.locator('[data-testid="submit-btn"]').click();
await page.waitForTimeout(1000);
await page.screenshot({ path: 'screenshots/app-loading.png', fullPage: true });
// Wait for async result
await page.locator('[data-testid="results-panel"]').waitFor({ timeout: 60000 });
await page.waitForTimeout(2000);
await page.screenshot({ path: 'screenshots/app-results.png', fullPage: true });
// --- End of interaction ---
// Write console log — always include this
fs.writeFileSync('screenshots/console.log', logs.join('\n'));
await browser.close();
})();
Write the script to screenshots/qa-script.mjs (overwritten each run).
Conventions — follow these exactly:
| Rule | Detail |
|---|---|
| Selectors | data-testid ONLY: page.locator('[data-testid="..."]'). Never CSS selectors, never text matching, never ARIA roles. If an element lacks a data-testid, tell the user to add one. |
| Viewport | page.setViewportSize({ width: 1280, height: 900 }) — every script |
| Screenshots | fullPage: true always. Descriptive filenames: screenshots/{page}-{state}.png. Overwrite previous. |
| Timeouts | page.waitForTimeout(1000-5000) for animations/hydration. element.waitFor({ timeout: 60000 }) for real API calls. Never rely on default timeouts. |
| Console | Always capture with page.on('console') and page.on('pageerror'). Write to screenshots/console.log. |
| No test framework | Never use test(), expect(), describe(). This is Playwright as a library, not a test runner. |
Step 3: Run the script
node screenshots/qa-script.mjs
If it fails, read the error. Common issues:
- Element not found → the
data-testiddoesn't exist. Tell the user which testid is missing. - Navigation error → wrong URL or app not running. Check the base URL.
- Timeout → the element never appeared. May be a real bug — screenshot what's visible and report.
Attempt one fix if the error is a simple selector/timing issue. If it fails again, report with whatever screenshots were captured.
Step 4: Read and report
- Read each screenshot captured during the run (use the Read tool on the image files)
- Read
screenshots/console.logfor errors, warnings, failed fetches - Compare against
ui-goals.json— for each relevant page's goals and lookFor items, assess whether the screenshot shows the goal being met
Report format:
## Visual QA Report
Base URL: http://localhost:3000
Pages checked: /app
### /app — Main App
Screenshots: app-initial.png, app-loading.png, app-results.png
**Goals:**
- Input form clearly labeled — YES, form labels visible in initial screenshot
- Submit shows loading state — YES, spinner visible in loading screenshot
- Results render after completion — YES, results panel populated in results screenshot
**Look for:**
- Loading spinner on submit — visible, good
- Error states show actionable messages — not tested this run (happy path only)
**Console:** 2 warnings (React hydration), 0 errors. No failed fetches.
**Issues:** None found.
- Update
lastTestedinui-goals.jsonto current ISO timestamp
Step 5: Iterate
After reporting, the user may ask for changes:
- "The button looks wrong" → adjust script or flag for the developer
- "Also check the error state" → write a new script variant, run again
- "Add testids to the settings page" → tell the user which testids to add
This is a conversation, not a one-shot report. Stay in the loop until the user is satisfied.
Context Files
| Reads | Purpose |
|---|---|
.shipkit/ui-goals.json | Goals that ground visual feedback |
playwright.config.ts | Base URL and config |
package.json | Dependency check during setup |
| Writes | When |
|---|---|
.shipkit/ui-goals.json | Created on setup; lastTested updated each run |
screenshots/*.png | Captured during runs (gitignored, overwritten) |
screenshots/console.log | Console output from each run (overwritten) |
screenshots/qa-script.mjs | The inline script (overwritten each run) |
playwright.config.ts | Created on setup if missing |
Integration
| Skill | How |
|---|---|
shipkit-review-shipping | Can reference visual QA as an optional verification step |
<!-- SECTION:after-completion -->
After Completion
Visual QA screenshots and console output are in screenshots/. The report maps findings back to the goals in ui-goals.json.
- Everything looks good → continue development or run
/shipkit-preflight - Issues found → fix the UI, then re-run
/shipkit-qa-visualto verify - Goals stale → edit
.shipkit/ui-goals.jsondirectly to update what matters - Missing testids → add
data-testidattributes to interactive elements, then re-run
Success Criteria
- Playwright installed and Chromium available (setup mode)
-
playwright.config.tsexists at project root -
.shipkit/ui-goals.jsonexists with user-confirmed goals - Screenshots captured to
screenshots/using fullPage and consistent viewport - All selectors use
data-testid— no CSS selectors, text matching, or ARIA roles - Console errors captured to
screenshots/console.log - Report maps visual findings back to goals in
ui-goals.json -
lastTestedupdated inui-goals.jsonafter each run - User can iterate — adjust, re-run, re-check — within the same conversation