Browser Test Skill
Run comprehensive Playwright browser tests across desktop, tablet, and mobile viewports.
When to Use
Invoke with /browser-test when you need to:
- •Test your web application across multiple screen sizes
- •Verify responsive behavior and mobile compatibility
- •Test user flows and common interactions
- •Capture screenshots at different viewports
- •Check performance metrics (LCP, CLS, INP)
Usage
bash
/browser-test <port> [target]
Arguments:
- •
port(required) - Port the app is running on (e.g., 3000, 8080) - •
target(optional) - Specific page (/dashboard), flow type (auth,forms,crud), orall(default)
Examples:
bash
# Test entire app on port 3000 /browser-test 3000 # Test only the dashboard page /browser-test 3000 /dashboard # Test only authentication flows /browser-test 3000 auth # Test on different port /browser-test 8080 all
Instructions
Phase 1: Setup and Validation
bash
PORT="$1"
TARGET="${2:-all}"
# Validate port is provided
if [ -z "$PORT" ]; then
echo "❌ Error: Port number is required"
echo ""
echo "Usage: /browser-test <port> [page-or-feature|all]"
echo ""
echo "Examples:"
echo " /browser-test 3000"
echo " /browser-test 3000 all"
echo " /browser-test 3000 /dashboard"
echo " /browser-test 3000 auth"
exit 1
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎭 Browser Testing with Playwright"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Target: http://localhost:$PORT"
echo "Scope: $TARGET"
echo "Viewports: Desktop (1920x1080), Tablet (768x1024), Mobile (375x812)"
echo ""
# Verify the app is running on the specified port
echo "📡 Checking if app is running on port $PORT..."
# Use curl to check if the port responds (works on Windows/Linux/Mac)
if command -v curl > /dev/null 2>&1; then
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT" | grep -q "^[23]"; then
echo "✓ App is reachable at http://localhost:$PORT"
else
echo "⚠️ Warning: Could not reach http://localhost:$PORT"
echo " Make sure your app is running before continuing"
echo ""
fi
else
echo "⚠️ curl not found, skipping reachability check"
echo " Make sure your app is running at http://localhost:$PORT"
echo ""
fi
# Check if Playwright is installed
echo ""
echo "🔍 Checking Playwright installation..."
if npx playwright --version > /dev/null 2>&1; then
PLAYWRIGHT_VERSION=$(npx playwright --version)
echo "✓ Playwright found: $PLAYWRIGHT_VERSION"
else
echo "📦 Playwright not found. Installing..."
# Create package.json if it doesn't exist
if [ ! -f "package.json" ]; then
npm init -y > /dev/null 2>&1
fi
# Install Playwright
npm install --save-dev @playwright/test
# Install Chromium only (faster than all browsers)
npx playwright install chromium
echo "✓ Playwright installed successfully"
fi
# Create browser-tests directory structure
echo ""
echo "📁 Setting up test directory structure..."
mkdir -p browser-tests/tests
mkdir -p browser-tests/screenshots/desktop
mkdir -p browser-tests/screenshots/tablet
mkdir -p browser-tests/screenshots/mobile
mkdir -p browser-tests/reports
echo "✓ Directory structure created"
echo ""
Phase 2: Route and Feature Discovery
bash
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Phase 2: Discovering Routes and Features" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Detect framework FRAMEWORK="unknown" echo "🔍 Detecting framework..." if [ -d "app" ] && ([ -f "next.config.js" ] || [ -f "next.config.ts" ] || [ -f "next.config.mjs" ]); then FRAMEWORK="nextjs-app-router" echo "✓ Detected: Next.js (App Router)" elif [ -d "pages" ] && ([ -f "next.config.js" ] || [ -f "next.config.ts" ]); then FRAMEWORK="nextjs-pages-router" echo "✓ Detected: Next.js (Pages Router)" elif [ -f "nuxt.config.ts" ] || [ -f "nuxt.config.js" ]; then FRAMEWORK="nuxt" echo "✓ Detected: Nuxt" elif [ -f "vite.config.ts" ] || [ -f "vite.config.js" ]; then FRAMEWORK="vite-spa" echo "✓ Detected: Vite SPA" elif [ -f "angular.json" ]; then FRAMEWORK="angular" echo "✓ Detected: Angular" elif [ -d "src" ] && grep -q "react-router" package.json 2>/dev/null; then FRAMEWORK="react-router" echo "✓ Detected: React Router" elif [ -d "src" ] && grep -q "vue-router" package.json 2>/dev/null; then FRAMEWORK="vue-router" echo "✓ Detected: Vue Router" else echo "⚠️ Framework not auto-detected, will use runtime crawling" fi echo "" echo "🗺️ Discovering routes and features..." echo "" # Claude: Based on detected framework, discover routes using static analysis: # # Next.js App Router: # - Scan app/**/page.tsx and app/**/page.jsx # - Map directory structure to routes # - Identify route groups (app/(auth)/login -> /login) # - Identify dynamic routes (app/posts/[id] -> /posts/:id) # # Next.js Pages Router: # - Scan pages/**/*.tsx and pages/**/*.jsx # - Exclude _app, _document, _error # - Map file names to routes # # Nuxt: # - Scan pages/**/*.vue # - Follow Nuxt routing conventions # # React Router / Vue Router: # - Find router configuration files (usually src/router, src/routes) # - Parse route definitions # # Vite SPA / Unknown: # - Fall back to runtime crawling (Phase 2B below) # # For each route, detect features by analyzing source code: # # FORMS - Look for: # - <form>, <input>, <textarea>, <select> elements # - Form libraries: react-hook-form, Formik, useForm # - Validation schemas: Zod, Yup # - API handlers with POST/PUT methods # # AUTHENTICATION - Look for: # - Auth providers: NextAuth, Clerk, Auth0, Supabase # - /login, /signin, /signup, /register routes # - useSession, getServerSession, useAuth hooks # - Middleware with authentication checks # - Protected route patterns # # CRUD OPERATIONS - Look for: # - Table/list components with edit/delete buttons # - Routes with /new, /create, /edit, /:id patterns # - API routes with GET/POST/PUT/DELETE methods # - Database queries in server components/API routes # # INTERACTIVE ELEMENTS - Look for: # - Dialog/Modal components # - Dropdown/Select components # - Accordion/Collapse components # - Tooltip/Popover components # - Tab/TabPanel components # # ERROR PAGES - Look for: # - 404.tsx, not-found.tsx, [not-found].tsx # - 500.tsx, error.tsx # - ErrorBoundary components # # Present discovery results in structured format: # # Example output format: # ┌────────────────────────────────────────┐ # │ Routes Discovered: 12 │ # ├────────────────────────────────────────┤ # │ / (homepage) │ # │ /about │ # │ /dashboard (requires auth) │ # │ /dashboard/settings │ # │ /products (CRUD list) │ # │ /products/[id] (CRUD detail) │ # │ /products/new (CRUD create) │ # │ /login (auth) │ # │ /signup (auth) │ # │ /contact (form) │ # │ /404 (error page) │ # │ /api/health (API - skip browser test) │ # └────────────────────────────────────────┘ # # ┌────────────────────────────────────────┐ # │ Features Detected │ # ├────────────────────────────────────────┤ # │ ✓ Forms: 3 pages │ # │ - /contact (contact form) │ # │ - /login (credentials) │ # │ - /signup (registration) │ # │ │ # │ ✓ Authentication: NextAuth │ # │ - /login, /signup pages │ # │ - Protected: /dashboard/* │ # │ │ # │ ✓ CRUD: Products │ # │ - List: /products │ # │ - Detail: /products/[id] │ # │ - Create: /products/new │ # │ │ # │ ✓ Interactive Elements │ # │ - Modal: Delete confirmation │ # │ - Dropdown: Navigation menu │ # │ │ # │ ✓ Error Pages │ # │ - Custom 404: /404 │ # └────────────────────────────────────────┘ # # If TARGET is not "all", filter to matching scope: # - If TARGET is a path like "/dashboard", only test that route # - If TARGET is "auth", only test authentication flows # - If TARGET is "forms", only test form pages # - If TARGET is "crud", only test CRUD operations echo ""
Phase 3: Test Generation
bash
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Phase 3: Generating Test Scripts"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Generate playwright.config.ts with 3 viewport projects
echo "📝 Generating playwright.config.ts..."
cat > browser-tests/playwright.config.ts << 'PLAYWRIGHT_CONFIG_EOF'
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
outputDir: './test-results',
fullyParallel: false, // Sequential for predictable screenshots
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 1,
workers: 1, // Run one test at a time
timeout: 30000,
use: {
baseURL: `http://localhost:${PORT}`,
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
projects: [
{
name: 'desktop',
use: {
viewport: { width: 1920, height: 1080 },
...devices['Desktop Chrome'],
},
},
{
name: 'tablet',
use: {
viewport: { width: 768, height: 1024 },
...devices['iPad Mini'],
},
},
{
name: 'mobile',
use: {
viewport: { width: 375, height: 812 },
...devices['iPhone 13'],
},
},
],
reporter: [
['json', { outputFile: './reports/test-results.json' }],
['html', { outputFolder: './reports/html', open: 'never' }],
['list'],
],
});
PLAYWRIGHT_CONFIG_EOF
# Replace PORT placeholder
sed -i "s/\${PORT}/$PORT/g" browser-tests/playwright.config.ts
echo "✓ Config generated"
echo ""
# Claude: Generate test spec files based on discovered features
#
# For each test category, generate a spec file:
#
# 1. navigation.spec.ts - For every discovered route:
# - Test page loads without console errors
# - Test title/heading is present
# - Test all internal links are clickable
# - Test back/forward browser navigation
# - Capture screenshot after page load
#
# 2. forms.spec.ts - For each detected form:
# - Test form renders with expected fields
# - Test empty submit shows validation errors
# - Test valid submit succeeds (or shows success state)
# - Test tab order for accessibility
# - Capture screenshots of: form, validation errors, success
#
# 3. auth.spec.ts - Only if auth detected:
# - Test login page renders
# - Test invalid credentials show error
# - Test valid login redirects (if test credentials available)
# - Test logout works
# - Test protected routes redirect to login
# - Note: Ask user for test credentials or check .env.test
#
# 4. crud.spec.ts - Only if CRUD detected:
# - Test list view loads and displays items
# - Test create form submits
# - Test edit form pre-fills data
# - Test delete shows confirmation and removes item
# - Test empty state renders
#
# 5. responsive.spec.ts - Cross-viewport comparison:
# - Test mobile menu vs desktop nav visibility
# - Test no horizontal scroll
# - Test touch targets >= 44x44px on mobile
# - Test text readability (no overflow)
#
# 6. interactive.spec.ts - For detected interactive elements:
# - Test modals open/close (click, Escape, overlay)
# - Test dropdowns expand and allow selection
# - Test accordions expand/collapse
# - Test tooltips appear on hover
#
# 7. error-states.spec.ts - Error handling:
# - Test 404 page for non-existent routes
# - Test form validation errors display
# - Test error page has navigation back to home
#
# 8. performance.spec.ts - Web vitals for all pages:
# - Measure LCP (target: <2.5s)
# - Measure CLS (target: <0.1)
# - Measure INP (target: <200ms)
# - Report per viewport
#
# 9. user-flows.spec.ts - End-to-end journeys:
# Based on detected features, generate flows like:
# - Signup-to-first-action: Register → onboard → complete task
# - Browse-to-purchase: Browse → select → cart → checkout
# - Search-and-filter: Search → filter → view result
# - Settings-update: Navigate → change → save → verify
# - Content-creation: Navigate → create → submit → verify in list
#
# Each test file should:
# - Import from '@playwright/test'
# - Use test.describe() to group related tests
# - Use test() for individual test cases
# - Access viewport via testInfo.project.name
# - Reference actual selectors/routes from Phase 2 discovery
# - Capture screenshots at key steps
# - Use meaningful test names and assertions
#
# Example test structure:
# ```typescript
# import { test, expect } from '@playwright/test';
#
# test.describe('Homepage', () => {
# test('loads successfully', async ({ page }, testInfo) => {
# await page.goto('/');
#
# // Common assertions
# await expect(page).toHaveTitle(/.+/);
# await expect(page.locator('body')).toBeVisible();
#
# // Viewport-specific assertions
# const viewport = testInfo.project.name;
# if (viewport === 'mobile') {
# await expect(page.locator('[data-testid="mobile-menu"]')).toBeVisible();
# } else {
# await expect(page.locator('[data-testid="desktop-nav"]')).toBeVisible();
# }
#
# // Screenshot
# await page.screenshot({
# path: `screenshots/${viewport}/homepage.png`,
# fullPage: true
# });
# });
# });
# ```
#
# For each spec file generated, output:
echo "✓ Generated: tests/navigation.spec.ts"
echo "✓ Generated: tests/forms.spec.ts"
# ... etc for each test file created
#
# Skip spec files for features not detected
echo ""
Phase 4: Test Execution
bash
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Phase 4: Running Tests" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" cd browser-tests echo "🧪 Running Playwright tests across 3 viewports..." echo " This may take a few minutes..." echo "" # Run tests npx playwright test # Capture exit code TEST_EXIT_CODE=$? echo "" if [ $TEST_EXIT_CODE -eq 0 ]; then echo "✅ All tests passed!" else echo "⚠️ Some tests failed. See report for details." echo "" # Claude: Analyze failures # - Read test-results.json # - For each failure, determine if it's: # 1. Test authoring issue (wrong selector, timing) → Fix the test # 2. Application bug (wrong behavior) → Report it # 3. Untestable scenario (needs real auth) → Add test.fixme() annotation # # If fixes are made, re-run: # npx playwright test --last-failed fi cd .. echo ""
Phase 5: Report Generation
bash
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Phase 5: Generating Report"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "📊 Generating test report..."
# Claude: Read browser-tests/reports/test-results.json
# Generate browser-tests/reports/test-report.md with:
#
# Format:
# ```markdown
# # Browser Test Report
#
# **Generated**: 2026-02-05T12:00:00Z
# **Application**: http://localhost:3000
# **Framework**: Next.js (App Router)
#
# ---
#
# ## Summary
#
# | Viewport | Tests | Passed | Failed | Skipped |
# |----------|-------|--------|--------|---------|
# | Desktop (1920x1080) | 42 | 40 | 1 | 1 |
# | Tablet (768x1024) | 42 | 41 | 0 | 1 |
# | Mobile (375x812) | 42 | 39 | 2 | 1 |
# | **Total** | **126** | **120** | **3** | **3** |
#
# **Pass Rate**: 95.2%
#
# ---
#
# ## Routes Tested
#
# | Route | Desktop | Tablet | Mobile | Notes |
# |-------|---------|--------|--------|-------|
# | / | PASS | PASS | PASS | Homepage loads in <1s |
# | /dashboard | PASS | PASS | FAIL | Mobile nav overlaps content |
# | /products | PASS | PASS | PASS | |
# | /login | PASS | PASS | PASS | |
# | /contact | FAIL | PASS | FAIL | Form submit 500 error |
# | /404 | PASS | PASS | PASS | Custom error page works |
#
# ---
#
# ## Test Results by Category
#
# ### Navigation (8 tests per viewport)
# - ✅ All pages load without console errors
# - ✅ Internal links resolve correctly
# - ✅ Back/forward navigation works
# - ❌ Dashboard nav overlaps main content at 375px (mobile)
#
# ### Forms (6 tests per viewport)
# - ✅ Contact form renders all fields
# - ✅ Validation errors appear for empty required fields
# - ❌ Contact form submit returns 500 (server error)
# - ✅ Login form accepts credentials
#
# ### Authentication (4 tests per viewport)
# - ✅ Login page renders
# - ✅ Invalid credentials show error message
# - ⏭️ Valid login (skipped - no test credentials)
# - ✅ Protected routes redirect to /login
#
# ### User Flows (3 flows)
# - ✅ Browse-to-detail: Click product → view detail → back to list
# - ❌ Signup-to-dashboard: Form submit fails at registration step
# - ✅ Settings-update: Change setting → save → persists on reload
#
# ### Responsive Behavior (6 tests per viewport)
# - ✅ Mobile menu visible on mobile/tablet
# - ✅ Desktop nav hidden on mobile
# - ❌ Dashboard sidebar does not collapse on mobile
# - ✅ No horizontal scroll on any viewport
#
# ### Interactive Elements (5 tests per viewport)
# - ✅ Delete confirmation modal opens/closes
# - ✅ Dropdown navigation works
# - ✅ Modal closes on Escape key
#
# ### Error States (4 tests per viewport)
# - ✅ /nonexistent shows 404 page
# - ✅ 404 page has link back to home
# - ✅ Form validation errors are accessible
#
# ### Performance Metrics
#
# | Page | Metric | Desktop | Tablet | Mobile | Target | Status |
# |------|--------|---------|--------|--------|--------|--------|
# | / | LCP | 1.2s | 1.4s | 1.8s | <2.5s | ✅ |
# | / | CLS | 0.02 | 0.03 | 0.05 | <0.1 | ✅ |
# | /dashboard | LCP | 1.8s | 2.1s | 2.8s | <2.5s | ⚠️ |
# | /products | LCP | 0.9s | 1.1s | 1.3s | <2.5s | ✅ |
#
# ---
#
# ## Failures Detail
#
# ### 1. Contact Form Server Error
# **Viewports**: Desktop, Mobile
# **File**: tests/forms.spec.ts:45
# **Description**: Submitting the contact form with valid data returns HTTP 500
# **Expected**: Success message or redirect
# **Actual**: Server error response
# **Screenshot**: screenshots/desktop/contact-form-error.png
# **Recommendation**: Check the /api/contact endpoint server-side handler
#
# ### 2. Dashboard Mobile Layout
# **Viewports**: Mobile
# **File**: tests/responsive.spec.ts:23
# **Description**: Dashboard sidebar does not collapse at mobile width
# **Expected**: Sidebar hidden behind hamburger menu
# **Actual**: Sidebar overlaps main content
# **Screenshot**: screenshots/mobile/dashboard-layout-issue.png
# **Recommendation**: Add responsive breakpoint for sidebar at 768px
#
# ---
#
# ## Screenshots
#
# All screenshots saved to `browser-tests/screenshots/`:
# - `desktop/` - 12 screenshots (1920x1080)
# - `tablet/` - 12 screenshots (768x1024)
# - `mobile/` - 12 screenshots (375x812)
#
# ---
#
# ## Re-running Tests
#
# ```bash
# # Run all tests
# cd browser-tests && npx playwright test
#
# # Run specific test file
# npx playwright test tests/forms.spec.ts
#
# # Run for specific viewport only
# npx playwright test --project=mobile
#
# # Run with UI mode for debugging
# npx playwright test --ui
#
# # View HTML report
# npx playwright show-report reports/html
# ```
#
# ---
#
# ## Files Generated
#
# - `browser-tests/playwright.config.ts` - Playwright configuration
# - `browser-tests/tests/*.spec.ts` - Test spec files
# - `browser-tests/screenshots/` - Screenshots by viewport
# - `browser-tests/reports/test-report.md` - This report
# - `browser-tests/reports/test-results.json` - Machine-readable results
# - `browser-tests/reports/html/` - Interactive HTML report
# ```
echo "✓ Report generated: browser-tests/reports/test-report.md"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Browser Testing Complete"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "📄 View report:"
echo " browser-tests/reports/test-report.md"
echo ""
echo "🖼️ Screenshots:"
echo " browser-tests/screenshots/{desktop,tablet,mobile}/"
echo ""
echo "🔄 Re-run tests:"
echo " cd browser-tests && npx playwright test"
echo ""
echo "🎭 Debug with UI:"
echo " cd browser-tests && npx playwright test --ui"
echo ""
Quick Reference
| Phase | Action | Key Output |
|---|---|---|
| 1 | Setup | Playwright installed, directories created |
| 2 | Discovery | Routes and features identified |
| 3 | Generation | Test spec files created |
| 4 | Execution | Tests run across 3 viewports |
| 5 | Report | Markdown report with results and recommendations |
Notes
- •Tests run sequentially (not parallel) for predictable screenshots
- •Only Chromium is installed (not Firefox/WebKit) for speed
- •Tests are saved to the project (not the skill) so users can re-run them
- •Screenshots captured at key steps in each test flow
- •Performance metrics measured using Web Vitals API
- •User flows test end-to-end journeys based on detected features
- •Report includes specific recommendations for fixing failures