From b92d349a51fc02ca255b88742caaf3b59911e701 Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 08:39:20 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 14 ++ README.md | 3 + commands/check.md | 135 +++++++++++ commands/take-screenshot.md | 107 +++++++++ commands/test-feature.md | 100 ++++++++ plugin.lock.json | 69 ++++++ skills/web-tests/SKILL.md | 430 ++++++++++++++++++++++++++++++++++ skills/web-tests/package.json | 26 ++ skills/web-tests/run.js | 217 +++++++++++++++++ 9 files changed, 1101 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 commands/check.md create mode 100644 commands/take-screenshot.md create mode 100644 commands/test-feature.md create mode 100644 plugin.lock.json create mode 100644 skills/web-tests/SKILL.md create mode 100644 skills/web-tests/package.json create mode 100755 skills/web-tests/run.js diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..e65cf2a --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "ui-tests", + "description": "Claude Code Skill for web testing and browser automation with Playwright. Auto-detects dev servers, saves test scripts and screenshots to your working directory.", + "version": "1.0.0", + "author": { + "name": "Claude Craftkit" + }, + "skills": [ + "./skills" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e52bde --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ui-tests + +Claude Code Skill for web testing and browser automation with Playwright. Auto-detects dev servers, saves test scripts and screenshots to your working directory. diff --git a/commands/check.md b/commands/check.md new file mode 100644 index 0000000..f940f90 --- /dev/null +++ b/commands/check.md @@ -0,0 +1,135 @@ +--- +description: Check webpage for issues (broken links, errors, etc.) +--- + +--url +--check-type + +## Check Webpage for Issues + +Automatically check webpages for common issues like broken links, console errors, missing images, or accessibility problems. + +**Works from any directory** - Saves report to your working directory's `.web-tests/` folder. + +## How It Works + +When you run `/check`, Claude will: + +1. **Auto-detect dev server** (or use provided URL) +2. **Create check script** in `.web-tests/scripts/` +3. **Run automated checks** based on check-type +4. **Generate report** in `.web-tests/reports/` +5. **Show summary** of findings + +## Usage + +```bash +# Check for broken links +/check --check-type links + +# Check for console errors +/check --url http://localhost:3001 --check-type console + +# Check for missing images +/check --check-type images + +# Check accessibility +/check --check-type a11y + +# Check all issues +/check --check-type all +``` + +## Check Types + +- `links` - Find broken links (404s, timeouts) +- `console` - Capture JavaScript errors and warnings +- `images` - Check for missing or broken images +- `a11y` - Basic accessibility checks (ARIA, alt text, etc.) +- `performance` - Basic performance metrics +- `all` - Run all checks + +## What Claude Will Do + +1. **Auto-detect dev server** (if no URL provided) +2. **Create check script** in `.web-tests/scripts/check-{type}-{timestamp}.js` +3. **Execute checks** with: `CWD=$(pwd) cd && node run.js .web-tests/scripts/check-*.js` +4. **Generate report** in `.web-tests/reports/` +5. **Display summary** of issues found + +## Output Structure + +``` +your-repo/ +└── .web-tests/ + ├── scripts/ + │ └── check-links-2025-10-23.js + └── reports/ + └── check-links-2025-10-23.md +``` + +## Report Contents + +The report will include: + +- **Summary** - Total issues found +- **Details** - Each issue with location and description +- **Severity** - Critical, Warning, Info +- **Suggestions** - How to fix each issue + +## Examples + +```bash +# Check for broken links +/check --check-type links + +# Check for console errors on specific page +/check --url http://localhost:3001/dashboard --check-type console + +# Full health check +/check --check-type all + +# Check accessibility +/check --url http://localhost:3001 --check-type a11y +``` + +## What Gets Checked + +**Links:** +- External links (status codes) +- Internal links (navigation) +- Anchor links +- Download links + +**Console:** +- JavaScript errors +- Network failures +- Console warnings + +**Images:** +- Missing images (404s) +- Broken src attributes +- Missing alt text + +**Accessibility:** +- Missing ARIA labels +- Color contrast (basic) +- Semantic HTML +- Keyboard navigation + +**Performance:** +- Page load time +- Resource sizes +- Number of requests + +## Tips + +- **Run before deployment** - Catch issues early +- **Check after changes** - Verify nothing broke +- **Commit reports** - Track issues over time +- **Automated quality checks** - Quick health check + +## See Also + +- `/test` - Run automated tests +- `/screenshot` - Take screenshots diff --git a/commands/take-screenshot.md b/commands/take-screenshot.md new file mode 100644 index 0000000..5a0f77c --- /dev/null +++ b/commands/take-screenshot.md @@ -0,0 +1,107 @@ +--- +description: Take screenshots of web pages +--- + +--url +--viewports + +## Take Screenshots + +Capture screenshots of web pages across different viewports. Screenshots are saved to `.web-tests/screenshots/` in your working directory. + +**Works from any directory** - Saves screenshots to your working directory's `.web-tests/screenshots/` folder. + +## How It Works + +When you run `/screenshot`, Claude will: + +1. **Auto-detect dev server** (or use provided URL) +2. **Create screenshot script** in `.web-tests/scripts/` +3. **Capture screenshots** across specified viewports +4. **Save to** `.web-tests/screenshots/` with timestamps +5. **Report** file locations + +## Usage + +```bash +# Screenshot current dev server +/screenshot + +# Screenshot specific URL +/screenshot --url http://localhost:3001 + +# Screenshot across all viewports +/screenshot --viewports all + +# Screenshot specific viewports +/screenshot --viewports desktop,mobile + +# Screenshot external site +/screenshot --url https://example.com --viewports desktop +``` + +## Viewport Options + +- `all` - Desktop (1920x1080), Tablet (768x1024), Mobile (375x667) +- `desktop` - 1920x1080 +- `tablet` - 768x1024 +- `mobile` - 375x667 +- `custom` - Claude will ask for dimensions + +## What Claude Will Do + +1. **Auto-detect dev server** (if no URL provided) +2. **Create screenshot script** in `.web-tests/scripts/screenshot-{timestamp}.js` +3. **Execute script** with: `CWD=$(pwd) cd && node run.js .web-tests/scripts/screenshot-*.js` +4. **Take screenshots** across specified viewports +5. **Report file locations** + +## Output Structure + +``` +your-repo/ +└── .web-tests/ + ├── scripts/ + │ └── screenshot-2025-10-23.js + └── screenshots/ + ├── desktop-2025-10-23T12-30-45.png + ├── tablet-2025-10-23T12-30-48.png + └── mobile-2025-10-23T12-30-51.png +``` + +## Screenshot Options + +Claude can configure: + +- **Full page** - Captures entire scrollable page (default: `true`) +- **Specific element** - Screenshot just one element +- **Visible viewport** - Only visible portion +- **Wait for load** - Waits for page to fully load + +## Examples + +```bash +# Quick screenshot of current dev server +/screenshot + +# Screenshot homepage across all devices +/screenshot --url http://localhost:3001 --viewports all + +# Mobile-only screenshot +/screenshot --viewports mobile + +# Screenshot specific page +/screenshot --url http://localhost:3001/dashboard --viewports desktop +``` + +## Tips + +- Screenshots include **full page** by default (scrolls entire page) +- **Timestamps in filenames** prevent overwriting +- **Saved in working directory** - easy to commit or share +- **Browser visible** - see what's being captured + +## See Also + +- `/test` - Run automated tests +- `/check` - Check for broken links or issues diff --git a/commands/test-feature.md b/commands/test-feature.md new file mode 100644 index 0000000..b41aa79 --- /dev/null +++ b/commands/test-feature.md @@ -0,0 +1,100 @@ +--- +description: Test a webapp feature with Playwright +--- + +--url +--test-type + +## Test Webapp Feature + +Run automated browser tests for your webapp using Playwright. Tests are saved to `.web-tests/` in your current working directory. + +**Works from any directory** - Saves test scripts and screenshots to your working directory's `.web-tests/` folder. + +## How It Works + +When you run `/test`, Claude will: + +1. **Auto-detect running dev servers** (or use the provided URL) +2. **Create a custom test script** in `.web-tests/scripts/` +3. **Execute the test** with visible browser +4. **Save screenshots** to `.web-tests/screenshots/` +5. **Show results** with console output + +## Usage + +```bash +# Test specific feature +/test --test-type login + +# Test with specific URL +/test --url http://localhost:3001 --test-type form + +# Test responsive design +/test --test-type responsive + +# Test page load +/test --url https://example.com --test-type page-load +``` + +## Common Test Types + +- `login` - Test login flow and authentication +- `form` - Test form filling and submission +- `responsive` - Test across multiple viewports (desktop, tablet, mobile) +- `page-load` - Test if page loads correctly +- `navigation` - Test navigation and routing +- `custom` - Claude will ask what to test + +## What Claude Will Do + +1. **Auto-detect dev server** (if testing localhost) +2. **Write test script** to `.web-tests/scripts/test-{type}-{timestamp}.js` +3. **Execute test** using: `CWD=$(pwd) cd && node run.js .web-tests/scripts/test-*.js` +4. **Report results** with any errors or successes +5. **Save screenshots** automatically to `.web-tests/screenshots/` + +## Output Structure + +``` +your-repo/ +└── .web-tests/ + ├── scripts/ + │ └── test-login-2025-10-23.js + └── screenshots/ + ├── login-page-2025-10-23T12-30-45.png + └── dashboard-2025-10-23T12-30-50.png +``` + +## Environment Variables + +Claude can use these environment variables: + +- `CWD` - Your current working directory (auto-set) +- `HEADLESS` - Set to `true` for background execution (default: `false`) +- `SLOW_MO` - Slow down actions for debugging (default: `0`) + +## Examples + +```bash +# Test login with credentials +/test --test-type login + +# Test contact form +/test --test-type form --url http://localhost:3001/contact + +# Test homepage across viewports +/test --test-type responsive --url http://localhost:3001 +``` + +## Tips + +- Tests run with **visible browser** by default (easier to debug) +- **Screenshots are automatic** - saved to `.web-tests/screenshots/` +- **Test scripts are reusable** - found in `.web-tests/scripts/` +- **Auto-detects servers** - no need to specify URL for localhost + +## See Also + +- `/screenshot` - Take screenshots of pages +- `/check` - Check for broken links or issues diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..e55344b --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,69 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:marcioaltoe/claude-craftkit:plugins/ui-tests", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "1cda1f763c6ffd508a88065babf84b146c2d7e5a", + "treeHash": "4c126a02d9c5801bd76e4f65b15a58ab5fb267ce31a82ca6fbecf8803d717f5c", + "generatedAt": "2025-11-28T10:27:01.884187Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "ui-tests", + "description": "Claude Code Skill for web testing and browser automation with Playwright. Auto-detects dev servers, saves test scripts and screenshots to your working directory.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "44893006b3ee6c1de73de0383ae9e3c3668c13eb697fb01ed0a89816fddc94fd" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "0b86167354822a59df9159483a756fa163c2cd900a303db46377dba095f4fb45" + }, + { + "path": "commands/check.md", + "sha256": "e017b0bf18523f44d26d0183340c6d0b585d05a530622975605a1bda321e1b4c" + }, + { + "path": "commands/take-screenshot.md", + "sha256": "ccb94488e8a90dc24dc2a2425582ee92fad55be5525bc846eacdea5380739bf9" + }, + { + "path": "commands/test-feature.md", + "sha256": "eb79d2fa987489298414674073a3790d463b0cad0659701fcce34deb85785437" + }, + { + "path": "skills/web-tests/run.js", + "sha256": "8c23dbecbc1b2436561119226ec3f2b7359db85a863613f5d0276db7d9cd9450" + }, + { + "path": "skills/web-tests/package.json", + "sha256": "5aeec2bb881a2e6d1b8544f938c62f90fde91d4cdb2eb63e91704a42490a6e59" + }, + { + "path": "skills/web-tests/SKILL.md", + "sha256": "270061b5c72e60c5a93dd11ca1203a1b6285113c8701fdebd5de08d0a1cf1d45" + }, + { + "path": "skills/web-tests/lib/helpers.js", + "sha256": "e514cf7979e89189652cea7ddc17fb8b613f10111bad5de338b5a90b9cad54d3" + } + ], + "dirSha256": "4c126a02d9c5801bd76e4f65b15a58ab5fb267ce31a82ca6fbecf8803d717f5c" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/web-tests/SKILL.md b/skills/web-tests/SKILL.md new file mode 100644 index 0000000..b3efa92 --- /dev/null +++ b/skills/web-tests/SKILL.md @@ -0,0 +1,430 @@ +--- +name: web-tests +description: Complete browser automation with Playwright. **ALWAYS use when user needs browser testing, E2E testing, screenshots, form testing, or responsive design validation.** Auto-detects dev servers, saves test scripts to working directory. Examples - "test this page", "take screenshots of responsive design", "test login flow", "check for broken links", "validate form submission". +--- + +**IMPORTANT - Path Resolution:** +This skill is installed globally but saves outputs to the user's working directory. Always pass `CWD` environment variable when executing commands to ensure outputs go to the correct location. + +Common installation paths: + +- Plugin system: `~/.claude/plugins/marketplaces/claude-craftkit/plugins/ui-tests/skills/web-tests` +- Manual global: `~/.claude/skills/web-tests` +- Project-specific: `/.claude/skills/web-tests` + +# Web Testing & Browser Automation + +General-purpose browser automation skill. I'll write custom Playwright code for any automation task you request and execute it via the universal executor. + +**CRITICAL WORKFLOW - Follow these steps in order:** + +1. **Auto-detect dev servers** - For localhost testing, ALWAYS run server detection FIRST: + + ```bash + cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))" + ``` + + - If **1 server found**: Use it automatically, inform user + - If **multiple servers found**: Ask user which one to test + - If **no servers found**: Ask for URL or offer to help start dev server + +2. **Write scripts to user's working directory** - Save to `.web-tests/scripts/test-*.js` in user's repo + +3. **Use visible browser by default** - Always use `headless: false` unless user specifically requests headless mode + +4. **Parameterize URLs** - Always make URLs configurable via constant at top of script + +## How It Works + +1. You describe what you want to test/automate +2. I auto-detect running dev servers (or ask for URL if testing external site) +3. I write custom Playwright code in `.web-tests/scripts/test-*.js` (in user's working directory) +4. I execute it via: `CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-*.js` +5. Results displayed in real-time, browser window visible for debugging +6. Screenshots automatically saved to `.web-tests/screenshots/` + +## Setup (First Time) + +```bash +cd $SKILL_DIR +npm run setup +``` + +This installs Playwright and Chromium browser. Only needed once. + +## Execution Pattern + +**Step 1: Detect dev servers (for localhost testing)** + +```bash +cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))" +``` + +**Step 2: Write test script to user's .web-tests/scripts/ with URL parameter** + +```javascript +// .web-tests/scripts/test-page.js +const { chromium } = require("playwright"); + +// Parameterized URL (detected or user-provided) +const TARGET_URL = "http://localhost:3001"; // <-- Auto-detected or from user + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + await page.goto(TARGET_URL); + console.log("Page loaded:", await page.title()); + + await page.screenshot({ + path: ".web-tests/screenshots/page.png", + fullPage: true, + }); + console.log("📸 Screenshot saved to .web-tests/screenshots/page.png"); + + await browser.close(); +})(); +``` + +**Step 3: Execute from skill directory with CWD** + +```bash +CWD=$(pwd) cd $SKILL_DIR && node run.js $(pwd)/.web-tests/scripts/test-page.js +``` + +## Common Patterns + +### Test a Page (Multiple Viewports) + +```javascript +// .web-tests/scripts/test-responsive.js +const { chromium } = require("playwright"); +const helpers = require("$SKILL_DIR/lib/helpers"); // Path will be resolved + +const TARGET_URL = "http://localhost:3001"; // Auto-detected + +(async () => { + const browser = await chromium.launch({ headless: false, slowMo: 100 }); + const page = await browser.newPage(); + + // Desktop test + await page.setViewportSize({ width: 1920, height: 1080 }); + await page.goto(TARGET_URL); + console.log("Desktop - Title:", await page.title()); + await helpers.takeScreenshot(page, "desktop"); // Saves to .web-tests/screenshots/ + + // Mobile test + await page.setViewportSize({ width: 375, height: 667 }); + await helpers.takeScreenshot(page, "mobile"); + + await browser.close(); +})(); +``` + +### Test Login Flow + +```javascript +// .web-tests/scripts/test-login.js +const { chromium } = require("playwright"); + +const TARGET_URL = "http://localhost:3001"; // Auto-detected + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + await page.goto(`${TARGET_URL}/login`); + + await page.fill('input[name="email"]', "test@example.com"); + await page.fill('input[name="password"]', "password123"); + await page.click('button[type="submit"]'); + + // Wait for redirect + await page.waitForURL("**/dashboard"); + console.log("✅ Login successful, redirected to dashboard"); + + await browser.close(); +})(); +``` + +### Fill and Submit Form + +```javascript +// .web-tests/scripts/test-form.js +const { chromium } = require("playwright"); +const helpers = require("$SKILL_DIR/lib/helpers"); + +const TARGET_URL = "http://localhost:3001"; // Auto-detected + +(async () => { + const browser = await chromium.launch({ headless: false, slowMo: 50 }); + const page = await browser.newPage(); + + await page.goto(`${TARGET_URL}/contact`); + + await page.fill('input[name="name"]', "John Doe"); + await page.fill('input[name="email"]', "john@example.com"); + await page.fill('textarea[name="message"]', "Test message"); + + await helpers.takeScreenshot(page, "form-filled"); + await page.click('button[type="submit"]'); + + // Verify submission + await page.waitForSelector(".success-message"); + console.log("✅ Form submitted successfully"); + await helpers.takeScreenshot(page, "form-success"); + + await browser.close(); +})(); +``` + +### Check for Broken Links + +```javascript +// .web-tests/scripts/test-broken-links.js +const { chromium } = require("playwright"); + +const TARGET_URL = "http://localhost:3001"; + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + await page.goto(TARGET_URL); + + const links = await page.locator('a[href^="http"]').all(); + const results = { working: 0, broken: [] }; + + for (const link of links) { + const href = await link.getAttribute("href"); + try { + const response = await page.request.head(href); + if (response.ok()) { + results.working++; + } else { + results.broken.push({ url: href, status: response.status() }); + } + } catch (e) { + results.broken.push({ url: href, error: e.message }); + } + } + + console.log(`✅ Working links: ${results.working}`); + console.log(`❌ Broken links:`, results.broken); + + await browser.close(); +})(); +``` + +### Take Screenshot with Error Handling + +```javascript +// .web-tests/scripts/screenshot-with-error-handling.js +const { chromium } = require("playwright"); +const helpers = require("$SKILL_DIR/lib/helpers"); + +const TARGET_URL = "http://localhost:3001"; + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + try { + await page.goto(TARGET_URL, { + waitUntil: "networkidle", + timeout: 10000, + }); + + await helpers.takeScreenshot(page, "page-success"); + console.log("📸 Screenshot saved successfully"); + } catch (error) { + console.error("❌ Error:", error.message); + await helpers.takeScreenshot(page, "page-error"); + } finally { + await browser.close(); + } +})(); +``` + +### Test Responsive Design (Full) + +```javascript +// .web-tests/scripts/test-responsive-full.js +const { chromium } = require("playwright"); +const helpers = require("$SKILL_DIR/lib/helpers"); + +const TARGET_URL = "http://localhost:3001"; // Auto-detected + +(async () => { + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage(); + + const viewports = [ + { name: "Desktop", width: 1920, height: 1080 }, + { name: "Tablet", width: 768, height: 1024 }, + { name: "Mobile", width: 375, height: 667 }, + ]; + + for (const viewport of viewports) { + console.log( + `Testing ${viewport.name} (${viewport.width}x${viewport.height})` + ); + + await page.setViewportSize({ + width: viewport.width, + height: viewport.height, + }); + + await page.goto(TARGET_URL); + await page.waitForTimeout(1000); + + await helpers.takeScreenshot(page, viewport.name.toLowerCase()); + } + + console.log("✅ All viewports tested"); + await browser.close(); +})(); +``` + +## Inline Execution (Simple Tasks) + +For quick one-off tasks, you can execute code inline without creating files: + +```bash +# Take a quick screenshot +cd $SKILL_DIR && CWD=$(pwd) node run.js " +const browser = await chromium.launch({ headless: false }); +const page = await browser.newPage(); +await page.goto('http://localhost:3001'); +await helpers.takeScreenshot(page, 'quick-test'); +console.log('Screenshot saved'); +await browser.close(); +" +``` + +**When to use inline vs files:** + +- **Inline**: Quick one-off tasks (screenshot, check if element exists, get page title) +- **Files**: Complex tests, responsive design checks, anything user might want to re-run + +## Available Helpers + +Optional utility functions in `lib/helpers.js`: + +```javascript +const helpers = require("./lib/helpers"); + +// Detect running dev servers (CRITICAL - use this first!) +const servers = await helpers.detectDevServers(); +console.log("Found servers:", servers); + +// Safe click with retry +await helpers.safeClick(page, "button.submit", { retries: 3 }); + +// Safe type with clear +await helpers.safeType(page, "#username", "testuser"); + +// Take timestamped screenshot (auto-saves to .web-tests/screenshots/) +await helpers.takeScreenshot(page, "test-result"); + +// Handle cookie banners +await helpers.handleCookieBanner(page); + +// Extract table data +const data = await helpers.extractTableData(page, "table.results"); +``` + +See `lib/helpers.js` for full list. + +## Directory Structure + +When testing, the skill creates this structure in the user's working directory: + +``` +user-repo/ +└── .web-tests/ + ├── scripts/ # Test scripts (reusable) + │ ├── test-login.js + │ ├── test-form.js + │ ├── test-responsive.js + │ └── test-broken-links.js + └── screenshots/ # Screenshots with timestamps + ├── desktop-2025-10-23T12-30-45.png + ├── mobile-2025-10-23T12-30-51.png + └── form-success-2025-10-23T12-31-05.png +``` + +## Tips + +- **CRITICAL: Detect servers FIRST** - Always run `detectDevServers()` before writing test code for localhost testing +- **Save to .web-tests/** - Write scripts to `.web-tests/scripts/`, screenshots auto-save to `.web-tests/screenshots/` +- **Parameterize URLs** - Put detected/provided URL in a `TARGET_URL` constant at the top of every script +- **DEFAULT: Visible browser** - Always use `headless: false` unless user explicitly asks for headless mode +- **Headless mode** - Only use `headless: true` when user specifically requests "headless" or "background" execution +- **Slow down:** Use `slowMo: 100` to make actions visible and easier to follow +- **Wait strategies:** Use `waitForURL`, `waitForSelector`, `waitForLoadState` instead of fixed timeouts +- **Error handling:** Always use try-catch for robust automation +- **Console output:** Use `console.log()` to track progress and show what's happening +- **Use helpers:** The `helpers.takeScreenshot()` automatically saves to `.web-tests/screenshots/` + +## Troubleshooting + +**Playwright not installed:** + +```bash +cd $SKILL_DIR && npm run setup +``` + +**Module not found:** +Ensure running from skill directory via `run.js` wrapper with CWD set + +**Browser doesn't open:** +Check `headless: false` and ensure display available + +**Element not found:** +Add wait: `await page.waitForSelector('.element', { timeout: 10000 })` + +**Screenshots not in .web-tests/:** +Make sure `CWD` environment variable is set when executing + +## Example Usage + +``` +User: "Test if the marketing page looks good" + +Claude: I'll test the marketing page across multiple viewports. Let me first detect running servers... +[Runs: detectDevServers()] +[Output: Found server on port 3001] +I found your dev server running on http://localhost:3001 + +[Writes custom automation script to .web-tests/scripts/test-marketing.js with URL parameterized] +[Runs: CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-marketing.js] +[Shows results with screenshots from .web-tests/screenshots/] +``` + +``` +User: "Check if login redirects correctly" + +Claude: I'll test the login flow. First, let me check for running servers... +[Runs: detectDevServers()] +[Output: Found servers on ports 3000 and 3001] +I found 2 dev servers. Which one should I test? +- http://localhost:3000 +- http://localhost:3001 + +User: "Use 3001" + +[Writes login automation to .web-tests/scripts/test-login.js] +[Runs: CWD=$(pwd) cd $SKILL_DIR && node run.js .web-tests/scripts/test-login.js] +[Reports: ✅ Login successful, redirected to /dashboard] +``` + +## Notes + +- Each automation is custom-written for your specific request +- Not limited to pre-built scripts - any browser task possible +- Auto-detects running dev servers to eliminate hardcoded URLs +- Test scripts saved to `.web-tests/scripts/` for reusability +- Screenshots automatically saved to `.web-tests/screenshots/` with timestamps +- Code executes reliably with proper module resolution via `run.js` +- Works as global tool - no per-repo installation needed diff --git a/skills/web-tests/package.json b/skills/web-tests/package.json new file mode 100644 index 0000000..5ed55e4 --- /dev/null +++ b/skills/web-tests/package.json @@ -0,0 +1,26 @@ +{ + "name": "ui-tests", + "version": "1.0.0", + "description": "Web testing and browser automation with Playwright for Claude Code", + "author": "Claude Craftkit", + "main": "run.js", + "scripts": { + "setup": "npm install && npx playwright install chromium", + "install-all-browsers": "npx playwright install chromium firefox webkit" + }, + "keywords": [ + "ui-tests", + "playwright", + "automation", + "browser-testing", + "web-automation", + "claude-skill" + ], + "dependencies": { + "playwright": "^1.56.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "license": "MIT" +} diff --git a/skills/web-tests/run.js b/skills/web-tests/run.js new file mode 100755 index 0000000..d9a0278 --- /dev/null +++ b/skills/web-tests/run.js @@ -0,0 +1,217 @@ +#!/usr/bin/env node +/** + * Universal Playwright Executor for Claude Code + * + * Executes Playwright automation code from: + * - File path: node run.js script.js + * - Inline code: node run.js 'await page.goto("...")' + * - Stdin: cat script.js | node run.js + * + * Ensures proper module resolution by running from skill directory. + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Change to skill directory for proper module resolution +process.chdir(__dirname); + +/** + * Check if Playwright is installed + */ +function checkPlaywrightInstalled() { + try { + require.resolve('playwright'); + return true; + } catch (e) { + return false; + } +} + +/** + * Install Playwright if missing + */ +function installPlaywright() { + console.log('📦 Playwright not found. Installing...'); + try { + execSync('npm install', { stdio: 'inherit', cwd: __dirname }); + execSync('npx playwright install chromium', { stdio: 'inherit', cwd: __dirname }); + console.log('✅ Playwright installed successfully'); + return true; + } catch (e) { + console.error('❌ Failed to install Playwright:', e.message); + console.error('Please run manually: cd', __dirname, '&& npm run setup'); + return false; + } +} + +/** + * Get code to execute from various sources + */ +function getCodeToExecute() { + const args = process.argv.slice(2); + + // Case 1: File path provided + if (args.length > 0 && fs.existsSync(args[0])) { + const filePath = path.resolve(args[0]); + console.log(`📄 Executing file: ${filePath}`); + return fs.readFileSync(filePath, 'utf8'); + } + + // Case 2: Inline code provided as argument + if (args.length > 0) { + console.log('⚡ Executing inline code'); + return args.join(' '); + } + + // Case 3: Code from stdin + if (!process.stdin.isTTY) { + console.log('📥 Reading from stdin'); + return fs.readFileSync(0, 'utf8'); + } + + // No input + console.error('❌ No code to execute'); + console.error('Usage:'); + console.error(' node run.js script.js # Execute file'); + console.error(' node run.js "code here" # Execute inline'); + console.error(' cat script.js | node run.js # Execute from stdin'); + console.error(''); + console.error('Environment Variables:'); + console.error(' CWD=/path/to/repo node run.js script.js # Set working directory for outputs'); + process.exit(1); +} + +/** + * Clean up old temporary execution files from previous runs + */ +function cleanupOldTempFiles() { + try { + const files = fs.readdirSync(__dirname); + const tempFiles = files.filter(f => f.startsWith('.temp-execution-') && f.endsWith('.js')); + + if (tempFiles.length > 0) { + tempFiles.forEach(file => { + const filePath = path.join(__dirname, file); + try { + fs.unlinkSync(filePath); + } catch (e) { + // Ignore errors - file might be in use or already deleted + } + }); + } + } catch (e) { + // Ignore directory read errors + } +} + +/** + * Wrap code in async IIFE if not already wrapped + */ +function wrapCodeIfNeeded(code) { + // Check if code already has require() and async structure + const hasRequire = code.includes('require('); + const hasAsyncIIFE = code.includes('(async () => {') || code.includes('(async()=>{'); + + // If it's already a complete script, return as-is + if (hasRequire && hasAsyncIIFE) { + return code; + } + + // If it's just Playwright commands, wrap in full template + if (!hasRequire) { + return ` +const { chromium, firefox, webkit, devices } = require('playwright'); +const helpers = require('./lib/helpers'); + +(async () => { + try { + ${code} + } catch (error) { + console.error('❌ Automation error:', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +})(); +`; + } + + // If has require but no async wrapper + if (!hasAsyncIIFE) { + return ` +(async () => { + try { + ${code} + } catch (error) { + console.error('❌ Automation error:', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +})(); +`; + } + + return code; +} + +/** + * Main execution + */ +async function main() { + console.log('🎭 Web Tests - Universal Executor\n'); + + // Show working directory info + if (process.env.CWD) { + console.log(`📁 Working directory: ${process.env.CWD}`); + console.log(`📸 Screenshots will be saved to: ${process.env.CWD}/.web-tests/screenshots/\n`); + } + + // Clean up old temp files from previous runs + cleanupOldTempFiles(); + + // Check Playwright installation + if (!checkPlaywrightInstalled()) { + const installed = installPlaywright(); + if (!installed) { + process.exit(1); + } + } + + // Get code to execute + const rawCode = getCodeToExecute(); + const code = wrapCodeIfNeeded(rawCode); + + // Create temporary file for execution + const tempFile = path.join(__dirname, `.temp-execution-${Date.now()}.js`); + + try { + // Write code to temp file + fs.writeFileSync(tempFile, code, 'utf8'); + + // Execute the code + console.log('🚀 Starting automation...\n'); + require(tempFile); + + // Note: Temp file will be cleaned up on next run + // This allows long-running async operations to complete safely + + } catch (error) { + console.error('❌ Execution failed:', error.message); + if (error.stack) { + console.error('\n📋 Stack trace:'); + console.error(error.stack); + } + process.exit(1); + } +} + +// Run main function +main().catch(error => { + console.error('❌ Fatal error:', error.message); + process.exit(1); +});