Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:17:04 +08:00
commit e758c0ab84
56 changed files with 9997 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
{
"name": "testing-tools",
"description": "Automated testing frameworks and quality assurance tooling",
"version": "0.0.0-2025.11.28",
"author": {
"name": "Connor",
"email": "noreply@claudex.dev"
},
"skills": [
"./skills/playwright-e2e-automation",
"./skills/tdd-automation"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# testing-tools
Automated testing frameworks and quality assurance tooling

252
plugin.lock.json Normal file
View File

@@ -0,0 +1,252 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:cskiro/claudex:testing-tools",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "59d8d5a9413c2b77bbff9e4b086c829e43d7bc58",
"treeHash": "2dc0bfaee4f19cf1c633db815c08c030f987af6ed843b3fcbeac2f41ab17f9bb",
"generatedAt": "2025-11-28T10:15:53.396710Z",
"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": "testing-tools",
"description": "Automated testing frameworks and quality assurance tooling"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "f57fbeb0403191907d95002e4d253b110e9b19a0719ab9ee636cca7c7df18f89"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "6565f6863caa8227078ea99c283e13cad386601dc40d25b128d34b8ccb147090"
},
{
"path": "skills/tdd-automation/CHANGELOG.md",
"sha256": "d9f9efe4427863d22a2d03411ff895c0fac387d4fd45c56f726f23bf8a3b07b6"
},
{
"path": "skills/tdd-automation/index.js",
"sha256": "90c4a1bbf4160ba42bb48817db81d772bd95fad38d00c5ca9ac30e63e5946f68"
},
{
"path": "skills/tdd-automation/README.md",
"sha256": "cb63dad5b043467be51b1378fd656de13e6a79e48e64e8364df1d4d88f2ab84f"
},
{
"path": "skills/tdd-automation/SKILL.md",
"sha256": "00939e4fd5d00c2ee7a25e707c70261dd770736c26fa77e122cb7f2de3b4e424"
},
{
"path": "skills/tdd-automation/utils/merge-claude-md.js",
"sha256": "38c6ce20de4b25bc907bbfaf56b880f9b5929f52abca2ebb97fc9970945d8070"
},
{
"path": "skills/tdd-automation/utils/update-package-json.js",
"sha256": "6446f58996930956eefb6ff0c22524e0a3a57d7c514c6327b0cd6843c53b3ea8"
},
{
"path": "skills/tdd-automation/utils/detect-project-type.js",
"sha256": "24ef9590db105fc12142403b991c1115cf1c4e8e38da85d5c1ab961dbaf3b42b"
},
{
"path": "skills/tdd-automation/utils/install-hooks.js",
"sha256": "25c2bfa2aa5fe0949a2c6617960913901291c4ae0e207e736a89d8ab796d941d"
},
{
"path": "skills/tdd-automation/utils/validate-claude-md.js",
"sha256": "60d4189b8ac6de073cda7d03530f1afb7663551ea901121bfcee999102045aba"
},
{
"path": "skills/tdd-automation/examples/.gitkeep",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "skills/tdd-automation/scripts/remove-tdd-section.js",
"sha256": "e4a36075b01e97e4e792ae59275571112cf29f2222f292dfa60ea21a48bb4fc8"
},
{
"path": "skills/tdd-automation/scripts/validate-tdd.js",
"sha256": "1c32502fbbb283d3bd4d22a02a06e0c2ccc3711e0ed3265aa872043cb4bdfe8b"
},
{
"path": "skills/tdd-automation/scripts/generate-test.js",
"sha256": "2192171feb30235713e2d47a41b207e5ee3e333d61fda0235dcb1957def0aa0b"
},
{
"path": "skills/tdd-automation/scripts/rollback-tdd.js",
"sha256": "ebb1cb0ffc8d7e7b6051b6c7ab2119c0452edf7632f5b6429559991daf12f0d4"
},
{
"path": "skills/tdd-automation/templates/pre-commit.sh",
"sha256": "74f425ce3746e6e3689dee79c12a576bf655edcd056849027c5ba82a1ab1ce0a"
},
{
"path": "skills/tdd-automation/templates/tdd-auto-enforcer.sh",
"sha256": "23771c535d8f973104a98376d326147e3253bf96d2c3edb089d3c10959982761"
},
{
"path": "skills/tdd-automation/reference/.gitkeep",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"path": "skills/playwright-e2e-automation/CHANGELOG.md",
"sha256": "de35c47ba531aeffdc0ec4e31ee6ab24404cdc6cc73095df922a64a2ab1aa6c0"
},
{
"path": "skills/playwright-e2e-automation/README.md",
"sha256": "5c4ce8a2a3c30a75926dd9df3990ff6a892b35bccd28ea6a884e301589b5e53e"
},
{
"path": "skills/playwright-e2e-automation/SKILL.md",
"sha256": "95c51cd493687b67af40094614a07c09265e588b3ad334224f366b36b066dc75"
},
{
"path": "skills/playwright-e2e-automation/examples/react-vite/example-test.spec.ts",
"sha256": "8c394a3bc1e4822153e9423b3d6b155981c7105a4354cee6368426ac63d8b24e"
},
{
"path": "skills/playwright-e2e-automation/examples/react-vite/example-page-object.ts",
"sha256": "2fa7ca1a5b2e4030819d54663d4fbe644ac693ec3e8af6fee52961f585c89415"
},
{
"path": "skills/playwright-e2e-automation/examples/reports/fix-recommendations.example.md",
"sha256": "58858f2209acd03cf96b01e6a8cee51f4e63a3738380aea0d73743c09feb7221"
},
{
"path": "skills/playwright-e2e-automation/examples/reports/visual-analysis-report.example.md",
"sha256": "2d6ae998818c0b7711355753dc4edecb2dda80c5077d9764f6acf3acf0bb787d"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-2-setup.md",
"sha256": "e26132ccd86c5f033dbc9babeeccc10362cae5d000dccd36117a590b44a26c2f"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-8-export.md",
"sha256": "2c195085edb931dfe77e885b7a7797618b00a288cbd37337da85486a30512e1a"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-6-regression.md",
"sha256": "b593116efded3aa9627d67c2d75f5a0f023df15b55214fbb188a67d278d25065"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-7-fixes.md",
"sha256": "70d28e5a594739f8be69f8fc4ec2db6ada3ffebf26a399d1eb2bbea55cd8943d"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-2.5-preflight.md",
"sha256": "4393f5fbf4d43c5a7dcce5708a4a294bd2687aecabd8fc7b195d0a95c36f0b79"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-4-capture.md",
"sha256": "b70c817f68be0084e124f5cd25adec226e5bc17e082847c93e6dc3a3ae688497"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-1-discovery.md",
"sha256": "2f118396ca808b57327fee8314a73f415ce821304f2d3bcacdc48f549984d400"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-3-generation.md",
"sha256": "df8bc6957d137d07203d2159279386fadaec29fbc0b04e36b34e31de0e33df26"
},
{
"path": "skills/playwright-e2e-automation/workflow/phase-5-analysis.md",
"sha256": "8ac791730d996918cb85b314003daaedee13b923844d54db9d786382f2303eeb"
},
{
"path": "skills/playwright-e2e-automation/scripts/README.md",
"sha256": "94e9b2dfe339d6a2bd2efd4b752d87f3f1aa5ed8172e274bdf0eaa110c5c7026"
},
{
"path": "skills/playwright-e2e-automation/templates/playwright.config.template.ts",
"sha256": "368e5e4449eecbe589f1a39b4c174f62a67e3f194544f2a88befb87fcb38e455"
},
{
"path": "skills/playwright-e2e-automation/templates/test-spec.template.ts",
"sha256": "5ef5857248eb2d701d7d9cc504db0edff0285ed16062c2f742a180e030cf9746"
},
{
"path": "skills/playwright-e2e-automation/templates/global-setup.template.ts",
"sha256": "1cf33e734671f33b127502bb5dea3bb97098a4e8625713e3e4d7a342a5ece75c"
},
{
"path": "skills/playwright-e2e-automation/templates/global-teardown.template.ts",
"sha256": "f46fe5a8912bb7f657d0f8e8b2126b2404abdbd881c4ed54b6d2b8c19461d80f"
},
{
"path": "skills/playwright-e2e-automation/templates/page-object.template.ts",
"sha256": "e0b67e6841818823a335cbff71eec1c8c61e301fa07dd4fa7d162836b19178f4"
},
{
"path": "skills/playwright-e2e-automation/templates/screenshot-helper.template.ts",
"sha256": "34803983e5a29aa26e34bcf81eeddac634cf415fbec049645a897930c5d921d8"
},
{
"path": "skills/playwright-e2e-automation/templates/css/vanilla.css",
"sha256": "b2168b3de50640e7f705e19d54f414d2d6d8a85f1bf38017eec5036e24785f78"
},
{
"path": "skills/playwright-e2e-automation/templates/css/tailwind-v3.css",
"sha256": "8afe45f5f6ab2a62dd106cc385aae7376c9a875450f3b0599f7a9044f8b81b98"
},
{
"path": "skills/playwright-e2e-automation/templates/css/tailwind-v4.css",
"sha256": "f06f1da8a4dcebafb41ab576d4ed9658f96c8a70791ae7e92cbe3f53b1962a93"
},
{
"path": "skills/playwright-e2e-automation/templates/configs/postcss-tailwind-v3.js",
"sha256": "dd28449f67a88d120997e27864b6a1ca6277dc23e3f0fbefeee9b3ad2e068b9b"
},
{
"path": "skills/playwright-e2e-automation/templates/configs/postcss-tailwind-v4.js",
"sha256": "0cb3d2cb69c8f4020e5f06f9763185d4be38b05ddc92c05d10746ea738e644e3"
},
{
"path": "skills/playwright-e2e-automation/data/playwright-best-practices.md",
"sha256": "ab54c39d74de70e77b06f8a226a19283266a26850972e5eea8a30e1dd699d837"
},
{
"path": "skills/playwright-e2e-automation/data/framework-detection-patterns.yaml",
"sha256": "c743ddeb7724ba4e5f938c361831d9859f86ed5d134b055b558204b6c09fd434"
},
{
"path": "skills/playwright-e2e-automation/data/error-patterns.yaml",
"sha256": "85805085b6cd51dc3bf1884c52ef9707090530199d851a1190a05d61e6b07e7b"
},
{
"path": "skills/playwright-e2e-automation/data/accessibility-checks.md",
"sha256": "967bd933ee183026ea27dcd9164d3ea00c21991e4ae8ad560ac6b569cd151c89"
},
{
"path": "skills/playwright-e2e-automation/data/common-ui-bugs.md",
"sha256": "ccb66a51d6e1a7c5182e92a0a19634178bcbe2ee733f939f0399e91be48b5334"
},
{
"path": "skills/playwright-e2e-automation/data/framework-versions.yaml",
"sha256": "abf39a2a28fbcd43e0d9675ec5d4dbab32328ce1d3ae8aaa2c268a4ea2fe3fa6"
},
{
"path": "skills/playwright-e2e-automation/reference/ci-cd-integration.md",
"sha256": "7cbe26dada1adb3502c2a43fd3fe7924edf3671c5ef14f39aa17e1b53b602160"
},
{
"path": "skills/playwright-e2e-automation/reference/troubleshooting.md",
"sha256": "d2e2e7cfdfbf9dd9feec023829aa693b0e4305ee7b03c5f930d2bb1e0340de50"
}
],
"dirSha256": "2dc0bfaee4f19cf1c633db815c08c030f987af6ed843b3fcbeac2f41ab17f9bb"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,22 @@
# Changelog
## 0.3.0
- Refactored to Anthropic progressive disclosure pattern
- Updated description with "Use PROACTIVELY when..." format
- Removed version/author/category/tags from frontmatter
## 0.2.0
- Added framework version detection (Tailwind v3/v4, React 17-19, Next.js 13-14)
- Added pre-flight health check (Phase 2.5)
- Added error pattern recovery database
- Fixed Tailwind CSS v4 compatibility
## 0.1.0
- Initial release with zero-setup Playwright automation
- Multi-framework support: React/Vite, Next.js, Node.js, static
- LLM-powered visual analysis for UI bug detection
- Visual regression testing with baseline comparison
- Fix recommendations with file:line references

View File

@@ -0,0 +1,387 @@
# Playwright E2E Automation
> Automated Playwright e2e testing framework with LLM-powered visual debugging, screenshot analysis, and regression testing
## Quick Start
### Trigger Phrases
Simply ask Claude Code:
```
"set up playwright testing for my app"
"help me debug UI issues with screenshots"
"create e2e tests with visual regression"
"analyze my app's UI with screenshots"
```
### What Happens
This skill will automatically:
1. **Detect your application** - Identifies React/Vite, Node.js, static sites, or full-stack apps
2. **Detect framework versions** (NEW) - Determines Tailwind v3 vs v4, React version, etc.
3. **Pre-flight validation** (NEW) - Checks app loads before running tests, catches config errors early
4. **Install Playwright** - Runs `npm init playwright@latest` with optimal configuration
5. **Generate test suite** - Creates screenshot-enabled tests with version-appropriate templates
6. **Capture screenshots** - Takes full-page screenshots at key interaction points
7. **Analyze visually** - Uses LLM vision to identify UI bugs, layout issues, accessibility problems
8. **Detect regressions** - Compares against baselines to find unexpected visual changes
9. **Generate fixes** - Produces actionable code recommendations with file:line references
10. **Export test suite** - Provides production-ready tests you can run independently
**Total time**: ~5-8 minutes (one-time setup)
**New in v0.2.0**: Version detection and pre-flight validation prevent configuration errors
## Features
### Zero-Setup Automation
No configuration required. The skill:
- Detects your framework automatically (React, Vite, Next.js, Express, etc.)
- Installs Playwright and browsers without prompts
- Generates optimal configuration based on your app type
- Creates tests following best practices
- Runs everything end-to-end
### Multi-Framework Support
Works with:
- **React/Vite** - Modern React apps with Vite dev server
- **Next.js** - Server-side rendered React applications
- **Node.js/Express** - Backend services with HTML responses
- **Static HTML/CSS/JS** - Traditional web applications
- **Full-stack** - Combined frontend + backend applications
### Version-Aware Configuration (NEW in v0.2.0)
The skill now detects installed framework versions and adapts automatically:
**Tailwind CSS**:
- **v3.x**: Uses `@tailwind base; @tailwind components; @tailwind utilities;` syntax
- **v4.x**: Uses `@import "tailwindcss";` syntax and `@tailwindcss/postcss` plugin
**React**:
- **v17**: Classic JSX transform (requires React import)
- **v18+**: Automatic JSX transform (no import needed)
**Detection Process**:
1. Reads `package.json` dependencies
2. Matches versions against compatibility database
3. Selects appropriate templates (CSS, PostCSS config, etc.)
4. Warns about breaking changes or unknown versions
**Pre-flight Validation**:
- Loads app in browser before running tests
- Monitors console for critical errors
- Matches errors against known patterns (Tailwind v4 syntax, PostCSS plugin, etc.)
- Provides specific fix steps with file:line references
- **Prevents cascade failures**: One config error won't fail all 10 tests
**Example Error Detection**:
```
❌ Pre-flight check failed: Tailwind CSS v4 syntax mismatch
Root cause: CSS file uses @tailwind directives but v4 requires @import
Fix:
1. Update src/index.css:
Change from: @tailwind base; @tailwind components; @tailwind utilities;
Change to: @import "tailwindcss";
2. Update postcss.config.js:
Change from: plugins: { tailwindcss: {} }
Change to: plugins: { '@tailwindcss/postcss': {} }
3. Restart dev server: npm run dev
```
### LLM-Powered Visual Analysis
Automatically identifies:
- **UI Bugs** - Broken layouts, overlapping elements, cut-off text
- **Accessibility Issues** - Color contrast, missing labels, improper heading hierarchy
- **Responsive Problems** - Elements not scaling, overflow on mobile
- **Visual Regressions** - Unexpected changes from baseline screenshots
- **Missing Elements** - Expected UI components not rendered
### Actionable Fix Recommendations
Generates specific fixes with:
- File paths and line numbers (`src/components/Button.tsx:45`)
- Current code snippets showing the issue
- Recommended code changes
- Explanation of why the fix works
- Priority level (critical, high, medium, low)
### Production-Ready Test Suite
Exports include:
- Organized test files following best practices
- Page object models for maintainability
- Screenshot helpers and utilities
- npm scripts for test execution
- README with usage instructions
- CI/CD integration examples
## Usage Examples
### Example 1: Initial Setup for React App
```
User: "Set up Playwright testing for my Vite React app"
Claude (via skill):
✅ Detected React + Vite application
✅ Installing Playwright...
✅ Generated playwright.config.ts with base URL http://localhost:5173
✅ Created 5 test specs covering critical journeys
✅ Captured 23 screenshots across desktop, tablet, mobile viewports
✅ Visual analysis complete: Found 3 issues (1 high, 2 medium)
✅ Generated fix recommendations in fix-recommendations.md
✅ Test suite exported to tests/e2e/
Next steps:
- Review fix-recommendations.md
- Run tests: npm run test:e2e
- Update baselines after fixes: npm run test:e2e:update-snapshots
```
### Example 2: Visual Regression Detection
```
User: "Check for visual regressions after my recent changes"
Claude (via skill):
✅ Running existing Playwright test suite...
✅ Captured 23 screenshots
✅ Comparing against baselines...
⚠️ Found 4 visual changes:
- Home page hero section: Layout shift detected (12% difference)
- Navigation menu: New dropdown added (expected change)
- Contact form: Button text color changed (2% difference)
- Footer: Logo size increased (5% difference)
📊 Visual regression report: screenshots/diffs/report.html
Would you like to:
1. Accept these changes as new baseline
2. Review specific diffs
3. Get fix recommendations for unintended changes
```
### Example 3: Bug Investigation
```
User: "Users report the checkout button is cut off on mobile"
Claude (via skill):
✅ Generating mobile viewport test...
✅ Captured screenshot: screenshots/current/checkout-mobile.png
✅ Visual analysis complete
🐛 Issue confirmed: Button text truncated on 375px viewport
Location: src/components/CheckoutButton.tsx:28
Current code:
```tsx
<button className="px-6 py-3 text-xl font-bold">
Complete Purchase
</button>
```
Recommended fix:
```tsx
<button className="px-4 py-2 text-sm sm:text-base md:text-xl font-bold max-w-full">
Complete Purchase
</button>
```
Reasoning: Fixed padding and font size cause overflow on narrow screens.
Added responsive sizing and max-width constraint.
Test added: tests/e2e/checkout-responsive.spec.ts
```
## What Gets Generated
After running this skill, you'll have:
```
your-project/
├── playwright.config.ts # Playwright configuration
├── tests/
│ └── e2e/
│ ├── setup/
│ │ └── global-setup.ts # Dev server startup
│ ├── pages/
│ │ └── *.page.ts # Page object models
│ ├── specs/
│ │ └── *.spec.ts # Test specifications
│ └── utils/
│ └── screenshot-helper.ts
├── screenshots/
│ ├── baselines/ # Reference images
│ ├── current/ # Latest run
│ └── diffs/ # Visual comparisons
├── fix-recommendations.md # Generated fix suggestions
├── visual-analysis-report.md # LLM analysis results
└── package.json # Updated with test scripts
```
## Performance
**Typical execution time** (React app with 5 critical journeys):
- Application detection: ~5 seconds
- Playwright installation: ~2-3 minutes (one-time)
- Test generation: ~30 seconds
- Test execution: ~30-60 seconds
- Visual analysis: ~1-2 minutes
- Regression comparison: ~10 seconds
- Fix generation: ~30 seconds
**Total**: ~5-8 minutes (excluding one-time Playwright install)
## CI/CD Integration
### GitHub Actions
The skill generates GitHub Actions workflow examples. Basic setup:
```yaml
name: Playwright E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-screenshots
path: screenshots/
```
### Baseline Management
**In CI**:
- Store baselines in repository: `git add screenshots/baselines/`
- Tests fail if visual diffs exceed threshold
- Review artifacts before merging
**Locally**:
- Update baselines: `npm run test:e2e:update-snapshots`
- Commit updated baselines after review
## Advanced Usage
### Custom Test Generation
After initial setup, you can:
1. **Add more tests** - Follow the pattern in generated specs
2. **Customize viewports** - Edit playwright.config.ts
3. **Add custom assertions** - Extend screenshot helpers
4. **Configure browsers** - Enable Firefox/WebKit in config
5. **Adjust thresholds** - Modify visual diff sensitivity
### Visual Analysis Customization
The skill's analysis focuses on:
- WCAG 2.1 AA accessibility compliance (see `data/accessibility-checks.md`)
- Common UI bug patterns (see `data/common-ui-bugs.md`)
- Framework-specific best practices
### Integration with Existing Tests
This skill complements your existing test suite:
- **Unit tests** (Vitest/Jest) - Test logic and calculations
- **Integration tests** - Test component interaction
- **E2E tests** (Playwright) - Test full user workflows + visual regression
All three work together without conflicts.
## Troubleshooting
### "Application not detected"
**Solution**: Specify manually
```
"Set up Playwright for my [framework] app running on port [port]"
```
### "Dev server not running"
**Solution**: The skill will attempt to start it automatically. If that fails:
```bash
npm run dev # Start your dev server first
```
Then re-run the skill.
### "Screenshot capture timeout"
**Solution**: Increase timeout in playwright.config.ts:
```typescript
timeout: 60000, // 60 seconds instead of default 30
```
### "Visual analysis found too many false positives"
**Solution**: Adjust the visual diff threshold:
```typescript
expect(await page.screenshot()).toMatchSnapshot({
maxDiffPixelRatio: 0.05, // Allow 5% difference
});
```
## Requirements
- **Node.js**: >=16.0.0
- **npm**: >=7.0.0
- **Disk space**: ~500MB for Playwright browsers (one-time)
- **Memory**: ~500MB during test execution
## Best Practices
1. **Baseline management** - Commit baselines to git, update deliberately
2. **Screenshot organization** - Use .gitignore for current/diffs, keep baselines
3. **Test critical paths** - Focus on user journeys that matter (80/20 rule)
4. **Run in CI** - Catch regressions before production
5. **Review diffs carefully** - Not all changes are bugs
6. **Use semantic selectors** - Prefer getByRole over CSS selectors
7. **Capture context** - Take screenshots before AND after interactions
## Learn More
- [Playwright Documentation](https://playwright.dev/)
- [Visual Regression Testing Guide](https://playwright.dev/docs/test-snapshots)
- [Accessibility Testing](https://playwright.dev/docs/accessibility-testing)
- [CI/CD Integration](https://playwright.dev/docs/ci)
## Support
Issues with this skill? Please report at:
- [Claude Code Issues](https://github.com/anthropics/claude-code/issues)
---
**Created with**: skill-creator v0.1.0
**Skill Version**: 0.1.0
**Last Updated**: 2025-11-01

View File

@@ -0,0 +1,141 @@
---
name: playwright-e2e-automation
description: Use PROACTIVELY when setting up e2e testing, debugging UI issues, or creating regression test suites. Automated Playwright framework with LLM-powered visual analysis, screenshot capture, and fix recommendations with file:line references. Zero-setup for React/Vite, Node.js, static sites, and full-stack applications. Not for unit testing, API-only testing, or mobile native apps.
---
# Playwright E2E Automation
## Overview
This skill automates the complete Playwright e2e testing lifecycle with LLM-powered visual debugging. It detects your app type, installs Playwright, generates tests, captures screenshots, analyzes for UI bugs, and produces fix recommendations with file paths and line numbers.
**Key Capabilities**:
- Zero-setup automation with multi-framework support
- Visual debugging with screenshot capture and LLM analysis
- Regression testing with baseline comparison
- Actionable fix recommendations with file:line references
- CI/CD ready test suite export
## When to Use This Skill
**Trigger Phrases**:
- "set up playwright testing for my app"
- "help me debug UI issues with screenshots"
- "create e2e tests with visual regression"
- "analyze my app's UI with screenshots"
- "generate playwright tests for [my app]"
**Use Cases**:
- Setting up Playwright testing from scratch
- Debugging visual/UI bugs hard to describe in text
- Creating screenshot-based regression testing
- Generating e2e test suites for new applications
- Identifying accessibility issues through visual inspection
**NOT for**:
- Unit testing or component testing (use Vitest/Jest)
- API-only testing without UI
- Performance/load testing
- Mobile native app testing (use Detox/Appium)
## Response Style
- **Automated**: Execute entire workflow with minimal user intervention
- **Informative**: Clear progress updates at each phase
- **Visual**: Always capture and analyze screenshots
- **Actionable**: Generate specific fixes with file paths and line numbers
## Quick Decision Matrix
| User Request | Action | Reference |
|--------------|--------|-----------|
| "set up playwright" | Full setup workflow | `workflow/phase-1-discovery.md``phase-2-setup.md` |
| "debug UI issues" | Capture + Analyze | `workflow/phase-4-capture.md``phase-5-analysis.md` |
| "check for regressions" | Compare baselines | `workflow/phase-6-regression.md` |
| "generate fix recommendations" | Analyze + Generate | `workflow/phase-7-fixes.md` |
| "export test suite" | Package for CI/CD | `workflow/phase-8-export.md` |
## Workflow Overview
### Phase 1: Application Discovery
Detect app type, framework versions, and optimal configuration.
**Details**: `workflow/phase-1-discovery.md`
### Phase 2: Playwright Setup
Install Playwright and generate configuration.
**Details**: `workflow/phase-2-setup.md`
### Phase 2.5: Pre-flight Health Check
Validate app loads correctly before full test suite.
**Details**: `workflow/phase-2.5-preflight.md`
### Phase 3: Test Generation
Create screenshot-enabled tests for critical workflows.
**Details**: `workflow/phase-3-generation.md`
### Phase 4: Screenshot Capture
Run tests and capture visual data.
**Details**: `workflow/phase-4-capture.md`
### Phase 5: Visual Analysis
LLM-powered analysis to identify UI bugs.
**Details**: `workflow/phase-5-analysis.md`
### Phase 6: Regression Detection
Compare screenshots against baselines.
**Details**: `workflow/phase-6-regression.md`
### Phase 7: Fix Generation
Map issues to source code with actionable fixes.
**Details**: `workflow/phase-7-fixes.md`
### Phase 8: Test Suite Export
Package production-ready test suite.
**Details**: `workflow/phase-8-export.md`
## Important Reminders
1. **Capture before AND after interactions** - Provides context for visual debugging
2. **Use semantic selectors** - Prefer getByRole, getByLabel over CSS selectors
3. **Baseline management is critical** - Keep in sync with intentional UI changes
4. **LLM analysis is supplementary** - Use alongside automated assertions
5. **Test critical paths first** - Focus on user journeys that matter most (80/20 rule)
6. **Screenshots are large** - Consider .gitignore for screenshots/, use CI artifacts
7. **Run tests in CI** - Catch visual regressions before production
8. **Update baselines deliberately** - Review diffs carefully before accepting
## Limitations
- Requires Node.js >= 16
- Browser download needs ~500MB disk space
- Screenshot comparison requires consistent rendering (may vary across OS)
- LLM analysis adds ~5-10 seconds per screenshot
- Not suitable for testing behind VPNs without additional configuration
## Reference Materials
| Resource | Purpose |
|----------|---------|
| `workflow/*.md` | Detailed phase instructions |
| `reference/troubleshooting.md` | Common issues and fixes |
| `reference/ci-cd-integration.md` | GitHub Actions, GitLab CI examples |
| `data/framework-versions.yaml` | Version compatibility database |
| `data/error-patterns.yaml` | Known error patterns with recovery |
| `templates/` | Config and test templates |
| `examples/` | Sample setups for different frameworks |
## Success Criteria
- [ ] Playwright installed with browsers
- [ ] Configuration generated for app type
- [ ] Test suite created (3-5 critical journey tests)
- [ ] Screenshots captured and organized
- [ ] Visual analysis completed with issue categorization
- [ ] Regression comparison performed
- [ ] Fix recommendations generated
- [ ] Test suite exported with documentation
- [ ] All tests executable via `npm run test:e2e`
---
**Total time**: ~5-8 minutes (excluding one-time Playwright install)

View File

@@ -0,0 +1,414 @@
# Accessibility Checks for Visual Analysis
WCAG 2.1 AA compliance criteria for LLM-powered screenshot analysis.
## Overview
When analyzing screenshots, check for these accessibility violations. This guide follows WCAG 2.1 Level AA standards.
## 1. Color Contrast
### Minimum Contrast Ratios
**Text:**
- Normal text (< 18pt or < 14pt bold): **4.5:1**
- Large text (≥ 18pt or ≥ 14pt bold): **3:1**
**UI Components:**
- Form inputs, buttons, icons: **3:1** against background
### Common Violations in Screenshots
```
❌ Light gray text on white background (2:1 ratio)
✅ Dark gray #595959 on white #FFFFFF (7:1 ratio)
❌ Blue link #4A90E2 on light blue #E8F4FF (1.8:1 ratio)
✅ Blue link #0066CC on white #FFFFFF (8.2:1 ratio)
❌ Gray placeholder text #CCCCCC on white (1.6:1 ratio)
✅ Gray placeholder text #757575 on white (4.6:1 ratio)
```
### Visual Indicators
When analyzing screenshots, look for:
- Pale or faded text that's hard to read
- Low-contrast buttons that don't stand out
- Links that blend into surrounding text
- Disabled states that are barely distinguishable
## 2. Text Size and Readability
### Minimum Font Sizes
- Body text: **16px** minimum (1rem)
- Small text acceptable: **14px** for secondary content
- Avoid: Text smaller than **12px** (fails WCAG)
### Common Violations
```
❌ Body text at 12px - too small for many users
✅ Body text at 16px or larger
❌ Mobile text at 10px - illegible on small screens
✅ Mobile text at 14px minimum
❌ Long paragraphs with no line height spacing
✅ Line height 1.5x for body text (e.g., 16px text with 24px line height)
```
### Visual Indicators
- Text that appears squished or compressed
- Long lines of text with minimal spacing
- Tiny labels on buttons or form fields
## 3. Focus Indicators
### Requirements
All interactive elements must have **visible focus indicators**:
- Minimum **2px** outline or border
- Contrast ratio of **3:1** against background
- Clearly visible when tabbing through interface
### Common Violations
```
❌ No visible outline when button is focused
✅ Blue 2px outline appears on focus
❌ Focus outline same color as background (invisible)
✅ High-contrast focus outline (e.g., black on white)
❌ Focus state only indicated by subtle background color change
✅ Focus state with outline + background color change
```
### Visual Indicators in Screenshots
Look for:
- Focused element (if screenshot captures tab state)
- Absence of visible outline or border
- Focus indicator that's too subtle or low-contrast
## 4. Form Labels and Instructions
### Requirements
- Every form input must have a visible **<label>** or aria-label
- Labels must be **adjacent** to their inputs
- Required fields must be clearly indicated
- Error messages must be **visible and associated** with inputs
### Common Violations
```
❌ Input with only placeholder text (disappears when typing)
✅ Input with persistent label above or beside it
❌ Label far away from input (hard to associate)
✅ Label immediately adjacent to input
❌ Required field marked only with color (red border)
✅ Required field marked with * and "Required" text
❌ Error message in different part of page
✅ Error message directly below input field
```
### Visual Indicators
- Inputs without visible labels
- Placeholder text used as labels (disappears on focus)
- Required fields indicated only by color
- Error states without clear error text
## 5. Heading Hierarchy
### Requirements
- Headings must follow logical order: **H1 → H2 → H3** (no skipping)
- Page should have exactly **one H1** (page title)
- Headings should be **visually distinct** from body text
### Common Violations
```
❌ Page with H1 → H4 (skips H2, H3)
✅ Page with H1 → H2 → H3
❌ Multiple H1 headings on same page
✅ Single H1 for page title, H2s for sections
❌ Heading text same size as body text
✅ Headings progressively larger: H3 < H2 < H1
```
### Visual Indicators in Screenshots
- Headings that don't look like headings (same size as body)
- Missing visual hierarchy (all headings same size)
- Text that looks like headings but isn't (bold body text)
## 6. Alternative Text for Images
### Requirements
- Decorative images: Empty alt="" or aria-hidden="true"
- Informative images: Descriptive alt text
- Complex images (charts, graphs): Detailed description
### Common Violations
**Note:** Can't always detect from screenshots alone, but can identify likely issues:
```
❌ Icon buttons with no visible text label (likely missing aria-label)
✅ Icon buttons with visible text label or tooltip
❌ Charts/graphs with no accompanying data table or description
✅ Charts with descriptive caption or linked data table
❌ Images that convey important info but might lack alt text
✅ Important info also available in visible text
```
### Visual Indicators
- Icon-only buttons without text labels
- Charts/infographics without textual explanations
- Images that appear to contain important information
## 7. Keyboard Navigation
### Requirements
- All interactive elements accessible via keyboard
- Logical tab order (top to bottom, left to right)
- No keyboard traps
- Skip links for navigation
### Visual Analysis Cues
**Can identify potential issues from screenshots:**
```
❌ Custom dropdown without visible keyboard focus states
✅ Standard HTML select or custom with clear focus indicators
❌ Modal dialog with no visible close button (might trap keyboard)
✅ Modal with visible, accessible close button
❌ Navigation menu requiring hover (might be keyboard inaccessible)
✅ Navigation menu that works on click/enter
```
## 8. Touch Target Size
### Minimum Sizes (Mobile)
- Interactive elements: **44x44 CSS pixels** minimum
- Adequate spacing between targets: **8px** minimum
### Common Violations
```
❌ Mobile buttons at 32x32px (too small)
✅ Mobile buttons at 48x48px
❌ Links in mobile menu spaced 4px apart (accidental taps)
✅ Links spaced 12px apart
❌ Checkbox at 16x16px on mobile (hard to tap)
✅ Checkbox with expanded tap area 44x44px
```
### Visual Indicators in Mobile Screenshots
- Tiny buttons that would be hard to tap accurately
- Densely packed clickable elements
- Links or buttons too close together
## 9. Responsive Design
### Requirements
- Content readable without horizontal scrolling
- No text truncation
- Proper scaling on different viewports
- No overlapping content
### Common Violations
```
❌ Desktop layout on mobile with horizontal scroll
✅ Mobile-optimized layout with no horizontal scroll
❌ Text cut off at viewport edge
✅ Text wraps properly within viewport
❌ Fixed-width elements overflow on small screens
✅ Flexible/responsive elements scale to screen size
❌ Overlapping elements on mobile (buttons on top of text)
✅ Elements stack vertically with proper spacing
```
### Visual Indicators Across Viewports
When comparing desktop/tablet/mobile screenshots:
- Text that gets cut off on smaller screens
- Overlapping or compressed elements
- Horizontal scrollbars on mobile
- Unreadable small text on mobile
## 10. Color Not Sole Indicator
### Requirements
- Information must not rely on **color alone**
- Use patterns, icons, or text in addition to color
### Common Violations
```
❌ Required fields indicated only by red border
✅ Required fields with red border + "*" icon + "Required" text
❌ Success/error only shown by green/red color
✅ Success/error shown by color + icon + text message
❌ Chart legend with only colored boxes
✅ Chart legend with colored boxes + patterns + labels
❌ Form validation using only red/green highlighting
✅ Form validation with color + icons + error text
```
### Visual Indicators
- Status indicators using only color
- Charts relying solely on color to differentiate data
- Form states indicated only by color changes
- Links distinguished only by color (not underline)
## Visual Analysis Workflow
When analyzing a screenshot for accessibility:
### Step 1: Text and Contrast
1. Check all text for sufficient contrast (4.5:1 for body, 3:1 for large)
2. Verify text is large enough (16px minimum)
3. Check line height and spacing for readability
### Step 2: Interactive Elements
1. Identify all buttons, links, form inputs
2. Verify they have sufficient size (44x44px on mobile)
3. Check for visible focus indicators (if focus state captured)
4. Ensure adequate spacing between targets
### Step 3: Form Elements
1. Check each input has visible label
2. Verify required fields clearly marked (not just color)
3. Look for error messages (should be near inputs)
### Step 4: Structure
1. Check heading hierarchy (visual size progression)
2. Verify logical content flow
3. Look for proper spacing and organization
### Step 5: Responsive Issues
1. Check for text truncation or cutoff
2. Look for overlapping elements
3. Verify no horizontal scroll
4. Ensure touch targets appropriate for viewport
### Step 6: Color Usage
1. Identify any color-only indicators
2. Verify status messages use icons or text too
3. Check charts/graphs have non-color differentiation
## Severity Levels
When reporting accessibility issues from screenshots:
### Critical (P0)
- Contrast ratio < 3:1 for any text
- Missing form labels
- Keyboard trap (if detectable)
- Content not accessible without horizontal scroll
### High (P1)
- Contrast ratio 3:1-4.4:1 for normal text
- Touch targets < 44x44px on mobile
- Heading hierarchy violations
- Color as sole indicator for critical info
### Medium (P2)
- Text size < 14px for body content
- Insufficient spacing between touch targets (< 8px)
- Inconsistent focus indicators
- Minor responsive issues
### Low (P3)
- Line height < 1.4 for long text blocks
- Decorative images possibly missing alt (can't confirm from screenshot)
- Minor visual hierarchy inconsistencies
## Example Analysis Output
```markdown
## Accessibility Issues Found
### Critical (1)
1. **Insufficient color contrast on form labels**
- Location: Contact form, all input labels
- Issue: Light gray #AAAAAA on white #FFFFFF (2.6:1 ratio)
- Requirement: 4.5:1 for normal text
- Fix: Use darker gray #595959 (7:1 ratio)
### High (2)
1. **Missing visible labels on inputs**
- Location: Email and password fields
- Issue: Only placeholder text, no persistent label
- Fix: Add visible <label> elements above inputs
2. **Touch targets too small on mobile**
- Location: Social media icons in footer
- Issue: Icons are 24x24px (below 44x44px minimum)
- Fix: Increase tap area to 44x44px with padding
### Medium (1)
1. **Body text too small**
- Location: Article content
- Issue: 14px font size (recommended 16px minimum)
- Fix: Increase base font size to 16px
```
## Tools for Automated Checking
While visual analysis is manual, recommend these tools for comprehensive checks:
```typescript
// Integrate axe-core in Playwright tests
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('should not have accessibility violations', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
```
---
**References:**
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
- [Axe DevTools](https://www.deque.com/axe/devtools/)

View File

@@ -0,0 +1,557 @@
# Common UI Bugs - Visual Analysis Guide
Patterns and indicators for identifying UI bugs from screenshots during LLM-powered visual analysis.
## Layout Issues
### 1. Overlapping Elements
**Visual Indicators:**
- Text overlapping other text
- Buttons overlapping images or other buttons
- Content extending beyond container boundaries
- Z-index issues causing incorrect stacking
**Examples:**
```
❌ Modal dialog overlapped by dropdown menu
❌ Footer content overlapping main content
❌ Notification banner covering navigation
❌ Search results hidden behind fixed header
```
**Screenshot Analysis:**
- Look for any elements that appear on top of others unexpectedly
- Check if all content is fully visible and not obscured
- Verify layering makes sense (modals on top, backgrounds behind)
### 2. Text Truncation / Overflow
**Visual Indicators:**
- Text cut off mid-word or mid-letter
- Ellipsis (...) in unexpected places
- Content extending outside visible area
- Horizontal scrollbars on text containers
**Examples:**
```
❌ Button text: "Continue to Chec..." (truncated on mobile)
❌ Table header: "Customer N..." instead of "Customer Name"
❌ Card title cut off at viewport edge
❌ Long email addresses broken into random positions
```
**Screenshot Analysis:**
- Check if all text is fully visible
- Look for truncation indicators (...)
- Verify important text isn't cut off
- Check if text wraps properly on smaller viewports
### 3. Broken Grid/Flexbox Layouts
**Visual Indicators:**
- Cards or items with inconsistent sizes
- Uneven spacing between elements
- Elements not aligned in columns/rows
- One element significantly larger/smaller than siblings
**Examples:**
```
❌ Product grid: 3 cards same height, 1 card twice as tall
❌ Navigation items: uneven spacing (10px, 20px, 15px)
❌ Form inputs: labels misaligned with inputs
❌ Cards: some with images, some without, causing height mismatch
```
**Screenshot Analysis:**
- Check if grid items are evenly sized
- Verify consistent spacing between elements
- Look for alignment issues in rows/columns
- Identify items breaking out of grid structure
### 4. Responsive Breakpoint Issues
**Visual Indicators (comparing viewport sizes):**
- Desktop layout on mobile (very small text, horizontal scroll)
- Mobile layout on desktop (everything too large, wasted space)
- Sudden jumps in layout between similar viewport sizes
- Media queries not triggering at expected breakpoints
**Examples:**
```
❌ Desktop: 3-column layout → Mobile: Still 3 columns (too cramped)
✅ Desktop: 3-column layout → Tablet: 2 columns → Mobile: 1 column
❌ Desktop: 16px text → Mobile: 16px text (too small on small screen)
✅ Desktop: 16px text → Mobile: 14px text with increased line height
❌ Fixed sidebar pushes main content off screen on tablet
✅ Sidebar collapses to hamburger menu on tablet
```
**Screenshot Analysis:**
- Compare same page across desktop (1280px+), tablet (768px), mobile (375px)
- Check if layout adapts appropriately at each size
- Verify no horizontal scrolling on mobile
- Ensure touch targets are 44x44px minimum on mobile
## Component-Specific Issues
### 5. Form Validation Problems
**Visual Indicators:**
- Error messages in wrong location (far from input)
- No visible error state (input looks normal despite error)
- Success state not clearly indicated
- Disabled buttons without indication why
**Examples:**
```
❌ Error message at top of page, input in middle (hard to associate)
✅ Error message directly below relevant input
❌ Invalid input: red border only (color-blind users miss it)
✅ Invalid input: red border + error icon + error text
❌ Submit button disabled, no explanation why
✅ Submit button disabled with tooltip "Complete required fields"
❌ Multiple validation errors shown as one generic message
✅ Each field shows its specific error message
```
**Screenshot Analysis:**
- Check if error states are clearly visible
- Verify error messages are near their inputs
- Look for validation indicators beyond just color
- Confirm required fields are clearly marked
### 6. Button States
**Visual Indicators:**
- Active/hover/focus states indistinguishable
- Disabled buttons look clickable
- Primary vs secondary buttons unclear
- Loading/submitting state not indicated
**Examples:**
```
❌ Disabled button: gray text on light gray (looks clickable)
✅ Disabled button: clear visual indication (opacity, cursor, label)
❌ Primary and secondary buttons identical appearance
✅ Primary: bold color, secondary: outline only
❌ Button clicked but no loading indicator (looks broken)
✅ Button shows spinner or "Loading..." text when clicked
❌ Hover state same as default state (no feedback)
✅ Hover state: darker background or subtle animation
```
**Screenshot Analysis:**
- Verify different button states are visually distinct
- Check if disabled buttons clearly look non-interactive
- Look for visual feedback on interactive states
- Ensure primary actions are visually prominent
### 7. Image Loading Issues
**Visual Indicators:**
- Broken image icons (usually a small icon or alt text)
- Missing images (blank space where image should be)
- Images with wrong aspect ratio (stretched/squashed)
- Low-resolution images appearing pixelated
**Examples:**
```
❌ Product image slot shows broken image icon
❌ Profile picture area: empty circle (image failed to load)
❌ Banner image: 16:9 image stretched to 1:1 (distorted)
❌ Thumbnail: tiny image scaled up 3x (pixelated)
```
**Screenshot Analysis:**
- Look for broken image indicators
- Check if all images loaded successfully
- Verify images maintain proper aspect ratios
- Identify pixelated or low-quality images
### 8. Table/List Issues
**Visual Indicators:**
- Headers not aligned with columns
- Inconsistent row heights
- Text overflow in cells
- Missing borders or inconsistent borders
- Poor mobile table handling (horizontal scroll)
**Examples:**
```
❌ Table headers offset from column data
❌ One row 2x height of others (content wrapping differently)
❌ Cell content: "john.doe@verylongemailaddr..." (truncated)
❌ Mobile table: requires horizontal scroll to see all columns
```
**Screenshot Analysis:**
- Verify column headers align with data
- Check for consistent row heights
- Look for text overflow in cells
- Ensure tables are readable on mobile (responsive design)
## Content Issues
### 9. Missing Content
**Visual Indicators:**
- Empty sections (just headers, no content)
- Placeholder text in production ("Lorem ipsum", "TBD", "Coming soon")
- Missing images or icons where expected
- Incomplete sentences or paragraphs
**Examples:**
```
❌ Section header "Latest Articles" with no articles below
❌ Product description: "Lorem ipsum dolor sit amet..."
❌ Icon placeholder: gray square instead of actual icon
❌ Bio section: ends mid-sentence
```
**Screenshot Analysis:**
- Identify any placeholder content
- Look for empty sections or containers
- Check if all expected content is present
- Verify no incomplete text
### 10. Inconsistent Spacing/Padding
**Visual Indicators:**
- Uneven margins between sections
- Inconsistent padding inside containers
- Elements touching edges (no breathing room)
- Random spacing that doesn't follow a system
**Examples:**
```
❌ Section 1: 40px margin → Section 2: 25px margin → Section 3: 35px
✅ All sections: consistent 40px margin
❌ Card padding: 16px top, 20px right, 14px bottom, 18px left
✅ Card padding: 16px all sides
❌ Button text touching button edge (no padding)
✅ Button: 12px vertical, 20px horizontal padding
```
**Screenshot Analysis:**
- Check for consistent spacing throughout page
- Verify elements have appropriate padding
- Look for crowded areas with insufficient spacing
- Identify spacing that breaks visual rhythm
## Typography Issues
### 11. Font Rendering Problems
**Visual Indicators:**
- Jagged or pixelated text
- Text weight too thin (hard to read)
- Inconsistent font families
- Line height too tight or too loose
**Examples:**
```
❌ Body text: font-weight 100 (barely visible)
✅ Body text: font-weight 400 (readable)
❌ Headings: Arial → Body: Times New Roman (inconsistent)
✅ All text: consistent font family
❌ Long paragraph: line-height 1.0 (text touching)
✅ Long paragraph: line-height 1.5 (readable spacing)
```
**Screenshot Analysis:**
- Check if text is crisp and readable
- Verify consistent font families
- Look for appropriate line height (1.4-1.6 for body text)
- Ensure font weights are accessible
### 12. Text Alignment Issues
**Visual Indicators:**
- Center-aligned paragraphs (hard to read)
- Inconsistent alignment within a section
- Right-aligned text in LTR layout without reason
- Justified text with large gaps
**Examples:**
```
❌ Long paragraph: center-aligned (hard to follow)
✅ Long paragraph: left-aligned
❌ Form: labels left-aligned, some center-aligned randomly
✅ Form: all labels consistently left-aligned
❌ Justified text: large gaps between words ("rivers")
✅ Left-aligned text with ragged right edge
```
**Screenshot Analysis:**
- Check if alignment aids readability
- Verify consistent alignment within sections
- Look for awkward gaps in justified text
- Ensure alignment makes sense for content type
## Interactive Element Issues
### 13. Hover/Focus States Missing
**Note:** Only detectable if screenshot captures focused/hovered state
**Visual Indicators:**
- Link looks identical to surrounding text (no underline, same color)
- Focused input indistinguishable from unfocused
- Hovered button shows no change
- Dropdown menu items don't highlight on hover
**Examples:**
```
❌ Link: blue text, no underline, same as normal text
✅ Link: blue text with underline, or different color
❌ Input focused: looks identical to unfocused state
✅ Input focused: blue border or outline appears
❌ Menu item hovered: no visual change
✅ Menu item hovered: background color change
```
**Screenshot Analysis (if interactive state captured):**
- Verify interactive elements show visual feedback
- Check if focused element has clear indicator
- Look for hover states that provide feedback
- Ensure keyboard focus is visible
### 14. Icon Issues
**Visual Indicators:**
- Icons misaligned with text
- Icons wrong size (too large or too small)
- Icons wrong color (low contrast, invisible)
- Icon-only buttons without labels or tooltips
**Examples:**
```
❌ Icon button: 16x16px icon in 48x48px button (looks lost)
✅ Icon button: 24x24px icon in 48x48px button (balanced)
❌ Icon: white on light gray background (barely visible)
✅ Icon: dark gray on light gray (clear contrast)
❌ Icon: baseline-aligned with text (appears raised)
✅ Icon: center-aligned with text
❌ Icon-only button with no label (unclear purpose)
✅ Icon button with aria-label or visible text label
```
**Screenshot Analysis:**
- Check if icons are appropriately sized
- Verify icons have sufficient contrast
- Look for proper alignment with adjacent text
- Ensure icon buttons have clear purpose
## Color and Theme Issues
### 15. Dark Mode Issues
**Visual Indicators (when comparing light/dark screenshots):**
- White text on light background (inverted incorrectly)
- Hard-coded colors not switching with theme
- Images/logos with wrong theme variant
- Insufficient contrast in dark mode
**Examples:**
```
❌ Dark mode: #333 text on #000 background (low contrast)
✅ Dark mode: #E0E0E0 text on #1A1A1A background
❌ Light mode logo on dark background (invisible)
✅ Dark mode variant logo displayed
❌ Input background: white in both modes (wrong in dark)
✅ Input background: white in light, #2A2A2A in dark
```
**Screenshot Analysis (compare light/dark if available):**
- Verify all colors invert appropriately
- Check contrast ratios in both modes
- Look for hard-coded colors that don't adapt
- Ensure images/logos have correct variants
### 16. Brand Color Misuse
**Visual Indicators:**
- Too many competing colors
- Brand colors used incorrectly (primary for everything)
- Status colors confusing (green for error, red for success)
- Inaccessible color combinations
**Examples:**
```
❌ All buttons primary color (no hierarchy)
✅ Primary button: brand color, secondary: gray/outline
❌ Success message in red, error in green (confusing)
✅ Success in green, error in red, warning in amber
❌ 8 different colors used on one page (chaotic)
✅ Consistent color palette: 2-3 main colors + neutrals
```
**Screenshot Analysis:**
- Check if color usage is consistent and meaningful
- Verify status colors match conventions (green=success, red=error)
- Look for excessive color variety
- Ensure brand colors used appropriately
## Animation and Transition Issues
**Note:** Difficult to detect from static screenshots, but can infer
### 17. Loading States
**Visual Indicators:**
- Content area completely empty (no skeleton/spinner)
- "Loading..." text with no visual indicator
- Sudden content appearance (jarring)
- Infinite loading (screenshot shows spinner forever)
**Examples:**
```
❌ Empty white space while loading (looks broken)
✅ Skeleton UI placeholders during load
❌ Just text "Loading..." (static, looks stuck)
✅ Animated spinner + "Loading..." text
❌ Screenshot from 30 seconds ago: still loading (timeout issue)
✅ Content loads within reasonable time (< 3 seconds)
```
**Screenshot Analysis:**
- Look for loading indicators
- Check if empty states have placeholders
- Identify potential timeout issues (loading too long)
## Mobile-Specific Issues
### 18. Fixed Positioning Problems
**Visual Indicators:**
- Fixed header covering content (not enough top padding)
- Fixed footer hiding interactive elements
- Input fields hidden behind keyboard (inferred)
- Fixed elements overlapping each other
**Examples:**
```
❌ Fixed header: covers first line of content
✅ Content has top padding equal to header height
❌ Fixed "Chat with us" button: covers form submit button
✅ Fixed button repositions when other content appears
❌ Input field: likely behind keyboard when focused
✅ Page scrolls input into view above keyboard
```
**Screenshot Analysis (mobile viewports):**
- Check if fixed headers leave room for content
- Verify fixed elements don't overlap important content
- Look for sufficient padding to account for fixed elements
### 19. Orientation Issues
**Visual Indicators (portrait vs landscape):**
- Content cut off in landscape mode
- Poor use of available space in landscape
- Fixed height elements that don't adapt
- Horizontal layout forced into vertical space
**Examples:**
```
❌ Portrait: shows full content → Landscape: content cut off
✅ Both orientations show full content
❌ Landscape: wide empty margins, cramped center content
✅ Landscape: content uses available width appropriately
```
**Screenshot Analysis (if both orientations available):**
- Compare same page in portrait and landscape
- Verify content adapts to available space
- Check if all content remains accessible
## Analysis Priority
When analyzing screenshots, prioritize issues by impact:
### Critical (Stop immediately)
1. Content completely missing or invisible
2. Major layout breaks (overlapping, off-screen)
3. Severe contrast violations (< 3:1)
4. Broken images or core UI elements
### High (Fix soon)
1. Text truncation losing important info
2. Form validation not visible
3. Responsive breakpoint failures
4. Touch targets too small (< 44px)
### Medium (Fix in next iteration)
1. Inconsistent spacing
2. Minor alignment issues
3. Missing hover/focus states
4. Moderate contrast issues (3:1-4.4:1)
### Low (Polish)
1. Minor typography inconsistencies
2. Slight spacing irregularities
3. Non-critical icon sizing
4. Subtle animation issues
## Generating Bug Reports
For each issue found, provide:
```markdown
### [Issue Title]
**Severity**: Critical | High | Medium | Low
**Location**: [Specific page/component where visible]
**Screenshot**: `path/to/screenshot.png` (timestamp: YYYY-MM-DD HH:MM:SS)
**Viewport**: Desktop 1280x720 | Tablet 768x1024 | Mobile 375x667
**Description**: [Clear description of what's wrong]
**Expected Behavior**: [What should appear instead]
**Likely Cause**: [Technical reason, e.g., "Missing max-width constraint", "Improper flexbox configuration"]
**Recommended Fix**:
- **File**: `src/components/Button.tsx`
- **Line**: 45
- **Current**: \`className="px-4 text-xl"\`
- **Fixed**: \`className="px-4 text-sm sm:text-base md:text-xl max-w-full"\`
- **Reasoning**: Text size needs responsive scaling and max-width to prevent overflow on mobile
```
---
**Remember**: Focus on bugs that impact **usability** and **accessibility**. Not every minor imperfection is critical. Prioritize issues that prevent users from completing tasks or accessing content.

View File

@@ -0,0 +1,415 @@
# Error Pattern Recovery Database
# Maps common error patterns to diagnosis and recovery steps
# This file is used for:
# 1. Pre-flight health checks - detect errors before running full test suite
# 2. Test failure analysis - provide actionable fixes when tests fail
# 3. User guidance - self-service troubleshooting
# ========== CSS & STYLING ERRORS ==========
css_errors:
tailwind_v4_syntax_mismatch:
pattern: "Cannot apply unknown utility class"
alternative_patterns:
- "Utilities must be known at build time"
- "Unknown utility class"
diagnosis: "Tailwind CSS v4 detected but v3 syntax used in CSS file"
severity: "critical"
category: "configuration"
root_cause: |
Tailwind CSS v4 changed from @tailwind directives to @import syntax.
Your CSS file likely still uses the old @tailwind directives.
detection_method: "console_error"
recovery_steps:
- step: "Identify your main CSS file"
details: "Usually src/index.css, src/App.css, or src/globals.css"
- step: "Update CSS directives"
from: |
@tailwind base;
@tailwind components;
@tailwind utilities;
to: |
@import "tailwindcss";
files_to_check:
- "src/index.css"
- "src/App.css"
- "src/globals.css"
- "src/styles/globals.css"
- step: "Update PostCSS configuration"
from: |
plugins: {
tailwindcss: {},
autoprefixer: {},
}
to: |
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
}
files_to_check:
- "postcss.config.js"
- "postcss.config.cjs"
- "postcss.config.mjs"
- step: "Restart dev server"
command: "npm run dev"
reason: "CSS changes require server restart"
- step: "Clear browser cache and reload"
details: "Hard refresh: Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac)"
prevention: |
The skill now detects Tailwind version and uses appropriate templates.
This error should not occur in new setups.
documentation: "https://tailwindcss.com/docs/upgrade-guide"
related_errors:
- "postcss_plugin_not_found"
postcss_plugin_not_found:
pattern: "Plugin tailwindcss not found"
alternative_patterns:
- "Cannot find module 'tailwindcss'"
- "postcss plugin tailwindcss not found"
diagnosis: "PostCSS configuration uses old Tailwind v3 plugin name with Tailwind v4"
severity: "critical"
category: "configuration"
root_cause: |
Tailwind CSS v4 renamed its PostCSS plugin from 'tailwindcss' to '@tailwindcss/postcss'.
Your postcss.config.js still references the old plugin name.
recovery_steps:
- step: "Update postcss.config.js"
from: |
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
to: |
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};
file: "postcss.config.js"
- step: "Verify @tailwindcss/postcss is installed"
command: "npm list @tailwindcss/postcss"
if_not_installed: "npm install -D @tailwindcss/postcss"
- step: "Restart dev server"
command: "npm run dev"
documentation: "https://tailwindcss.com/docs/upgrade-guide#migrating-from-v3"
# ========== ACCESSIBILITY ERRORS ==========
accessibility_errors:
heading_hierarchy_violation:
pattern: "heading-order - Heading levels should only increase by one"
alternative_patterns:
- "Heading levels should increase by one"
- "heading-order violation"
diagnosis: "WCAG heading hierarchy violation - skipped heading levels"
severity: "moderate"
category: "accessibility"
root_cause: |
HTML heading elements (h1-h6) must follow logical order without skipping levels.
For example: h1 → h2 → h3 is correct, but h1 → h3 (skipping h2) is incorrect.
recovery_steps:
- step: "Locate the problematic heading in test output"
details: "Playwright accessibility tests will show file and line number"
- step: "Check heading hierarchy in that component"
example: |
❌ Bad:
<h1>Page Title</h1>
<h3>Section</h3> <!-- Skips h2 -->
✅ Good:
<h1>Page Title</h1>
<h2>Section</h2>
- step: "Fix heading levels to follow order"
details: "Ensure each heading is only one level deeper than its parent"
- step: "Re-run accessibility tests"
command: "npm run test:e2e -- accessibility.spec.ts"
prevention: |
Always outline content structure before implementing:
- Page title: h1 (only one per page)
- Main sections: h2
- Subsections: h3
- Sub-subsections: h4
documentation: "https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html"
missing_form_labels:
pattern: "Form elements must have labels"
alternative_patterns:
- "label - Form elements must have labels"
- "Inputs must have associated labels"
diagnosis: "Form input missing associated label element"
severity: "high"
category: "accessibility"
root_cause: |
Every form input must have an associated <label> element for screen reader users.
Placeholder text alone is NOT sufficient.
recovery_steps:
- step: "Add label element to input"
from: |
<input type="email" placeholder="Email" />
to: |
<label htmlFor="email">Email Address</label>
<input id="email" type="email" placeholder="Email" />
- step: "Alternative: Use aria-label if visual label not desired"
example: |
<input
type="search"
aria-label="Search"
placeholder="Search..."
/>
- step: "Ensure label and input are properly associated"
details: "Use 'htmlFor' attribute matching input 'id'"
- step: "Re-run accessibility tests"
command: "npm run test:e2e -- accessibility.spec.ts"
documentation: "https://www.w3.org/WAI/tutorials/forms/labels/"
insufficient_color_contrast:
pattern: "color-contrast - Elements must meet minimum color contrast ratio"
alternative_patterns:
- "color contrast ratio"
- "contrast ratio"
diagnosis: "Text or UI element has insufficient color contrast (WCAG 2.1 AA violation)"
severity: "high"
category: "accessibility"
root_cause: |
WCAG 2.1 AA requires:
- Normal text: 4.5:1 contrast ratio
- Large text (18pt+ or 14pt+ bold): 3:1 contrast ratio
- UI components: 3:1 contrast ratio
recovery_steps:
- step: "Identify low-contrast element from test output"
details: "Test results show which element and current ratio"
- step: "Use WebAIM contrast checker"
url: "https://webaim.org/resources/contrastchecker/"
details: "Test current colors and find compliant alternatives"
- step: "Update color values"
example: |
❌ Bad: #AAAAAA on #FFFFFF (2.6:1 ratio)
✅ Good: #595959 on #FFFFFF (7:1 ratio)
For Tailwind:
❌ text-gray-400 → ✅ text-gray-700
- step: "Re-run accessibility tests"
command: "npm run test:e2e -- accessibility.spec.ts"
prevention: |
Use established color palettes with pre-tested contrast ratios.
Tailwind's default palette (500+ for text) generally meets WCAG AA.
documentation: "https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html"
# ========== BUILD & CONFIGURATION ERRORS ==========
build_errors:
vite_port_in_use:
pattern: "Port 5173 is in use"
alternative_patterns:
- "EADDRINUSE"
- "address already in use"
diagnosis: "Vite dev server port already in use by another process"
severity: "moderate"
category: "environment"
recovery_steps:
- step: "Find process using port 5173"
command: "lsof -i :5173"
platforms: ["mac", "linux"]
- step: "Kill the process"
command: "kill -9 <PID>"
details: "Replace <PID> with process ID from previous command"
- step: "Alternative: Use different port"
details: "Add to vite.config.ts:"
config: |
server: {
port: 3000
}
- step: "Restart dev server"
command: "npm run dev"
playwright_browsers_not_installed:
pattern: "Executable doesn't exist"
alternative_patterns:
- "browserType.launch: Executable doesn't exist"
- "Browser not found"
diagnosis: "Playwright browser binaries not installed"
severity: "critical"
category: "installation"
recovery_steps:
- step: "Install Playwright browsers"
command: "npx playwright install"
details: "Downloads Chromium, Firefox, and WebKit"
- step: "Install system dependencies (Linux only)"
command: "npx playwright install-deps"
platforms: ["linux"]
- step: "Verify installation"
command: "npx playwright test --list"
expected: "Should list available tests without errors"
typescript_strict_errors:
pattern: "TypeScript strict mode errors"
alternative_patterns:
- "TS2345"
- "TS2322"
- "Type 'any' is not assignable"
diagnosis: "TypeScript strict mode violations in generated code"
severity: "moderate"
category: "type_safety"
recovery_steps:
- step: "Review TypeScript errors in output"
details: "Each error shows file, line, and type issue"
- step: "Add explicit type annotations"
example: |
❌ const data = await fetch(url);
✅ const data: Response = await fetch(url);
- step: "Use type assertions cautiously"
example: |
const element = page.locator('#id') as Locator;
- step: "Fix null/undefined handling"
example: |
❌ const text = element.textContent();
✅ const text = await element.textContent() ?? '';
# ========== RESPONSIVE & LAYOUT ERRORS ==========
layout_errors:
horizontal_scroll_mobile:
pattern: "Horizontal scroll detected on mobile viewport"
diagnosis: "Content wider than viewport on mobile, causing horizontal scroll"
severity: "moderate"
category: "responsive"
root_cause: |
Fixed-width elements or overflow content exceeds mobile viewport width.
Common causes: large images, wide tables, fixed px widths.
recovery_steps:
- step: "Identify overflowing element"
details: "Use browser DevTools → Elements → Computed → scroll width"
- step: "Make element responsive"
example: |
❌ width: 800px;
✅ max-width: 100%;
❌ className="w-[800px]"
✅ className="w-full max-w-screen-lg"
- step: "Add overflow handling"
example: |
className="overflow-x-auto" // For tables
className="overflow-hidden" // For containers
- step: "Test on mobile viewport"
command: "npm run test:e2e -- --project=mobile-chrome"
# ========== RECOVERY STRATEGIES ==========
recovery_strategies:
incremental_fix:
description: "Fix errors one at a time, test after each fix"
when_to_use: "Multiple related errors"
steps:
- "Fix highest severity error first"
- "Run tests to verify fix"
- "Move to next error"
- "Repeat until all errors resolved"
clean_slate:
description: "Remove generated files and regenerate"
when_to_use: "Configuration completely broken"
steps:
- "Delete generated playwright.config.ts"
- "Delete tests/ directory"
- "Clear node_modules/.cache/"
- "Re-run skill"
version_rollback:
description: "Downgrade to last working version"
when_to_use: "Breaking change in newly installed package"
steps:
- "Check package.json for recently updated packages"
- "Install previous version: npm install package@previous-version"
- "Lock version in package.json"
- "Test to confirm working"
# ========== ERROR SEVERITY LEVELS ==========
severity_definitions:
critical:
impact: "App won't build or run"
response_time: "Fix immediately"
examples: ["Syntax errors", "Missing dependencies", "Config errors"]
high:
impact: "Major functionality broken"
response_time: "Fix within hours"
examples: ["Accessibility violations", "Failed test cases", "Type errors"]
moderate:
impact: "Reduced usability or performance"
response_time: "Fix within days"
examples: ["Layout issues", "Minor accessibility", "Warnings"]
low:
impact: "Cosmetic or minor issues"
response_time: "Fix when convenient"
examples: ["Code style", "Documentation", "Minor visual bugs"]

View File

@@ -0,0 +1,430 @@
# Framework Detection Patterns
# Used to automatically identify application type and determine optimal Playwright configuration
# Detection happens in phases:
# 1. Read package.json dependencies
# 2. Check for config files
# 3. Identify dev server command
# 4. Determine base URL and port
frameworks:
# ========== REACT + VITE ==========
react_vite:
name: "React + Vite"
priority: 1 # Check first (most common modern setup)
detection:
package_json_dependencies:
required:
- "react"
- "vite"
optional:
- "react-dom"
- "@vitejs/plugin-react"
config_files:
- "vite.config.ts"
- "vite.config.js"
- "vite.config.mjs"
indicators:
- "src/main.tsx exists"
- "src/main.jsx exists"
- "index.html with Vite script tag"
configuration:
base_url: "http://localhost:5173"
dev_server_command: "npm run dev"
build_command: "npm run build"
preview_command: "npm run preview"
playwright_config:
timeout: 30000
retries: 2
use:
viewport: { width: 1280, height: 720 }
screenshot: "only-on-failure"
video: "retain-on-failure"
projects:
- name: "chromium"
use:
browserName: "chromium"
- name: "mobile"
use:
...devices["iPhone 13"]
webServer:
command: "npm run dev"
url: "http://localhost:5173"
reuseExistingServer: true
timeout: 120000
test_generation:
entry_point: "src/App.tsx"
routing: "react-router-dom" # If detected in dependencies
state_management: ["redux", "zustand", "jotai"] # Check for these
common_pages:
- "/"
- "/about"
- "/contact"
- "/login"
- "/dashboard"
# ========== NEXT.JS ==========
nextjs:
name: "Next.js"
priority: 2
detection:
package_json_dependencies:
required:
- "next"
- "react"
optional:
- "react-dom"
config_files:
- "next.config.js"
- "next.config.mjs"
- "next.config.ts"
indicators:
- "app/ directory exists" # App Router
- "pages/ directory exists" # Pages Router
- ".next/ directory exists"
configuration:
base_url: "http://localhost:3000"
dev_server_command: "npm run dev"
build_command: "npm run build"
playwright_config:
timeout: 45000 # Next.js can be slower on first load
retries: 2
webServer:
command: "npm run dev"
url: "http://localhost:3000"
reuseExistingServer: true
timeout: 120000
test_generation:
router_type: "app" # or "pages" - detect from directory structure
entry_point: "app/page.tsx" # App Router
api_routes: "app/api/" # Check if API routes exist
common_pages:
- "/"
- "/about"
- "/blog"
- "/contact"
# ========== CREATE REACT APP ==========
create_react_app:
name: "Create React App"
priority: 3
detection:
package_json_dependencies:
required:
- "react"
- "react-scripts"
optional:
- "react-dom"
config_files:
- "public/index.html"
indicators:
- "src/index.js exists"
- "src/index.tsx exists"
- "package.json scripts.start includes react-scripts"
configuration:
base_url: "http://localhost:3000"
dev_server_command: "npm start"
build_command: "npm run build"
playwright_config:
timeout: 30000
retries: 2
webServer:
command: "npm start"
url: "http://localhost:3000"
reuseExistingServer: true
timeout: 120000
test_generation:
entry_point: "src/App.js"
routing: "react-router-dom"
# ========== NODE.JS + EXPRESS ==========
express:
name: "Node.js + Express"
priority: 4
detection:
package_json_dependencies:
required:
- "express"
optional:
- "ejs"
- "pug"
- "handlebars"
indicators:
- "app.js exists"
- "server.js exists"
- "index.js exists"
- "views/ directory exists"
configuration:
base_url: "http://localhost:3000" # Default, check process.env.PORT
dev_server_command: "npm run dev"
alternative_commands:
- "npm start"
- "node server.js"
- "nodemon server.js"
playwright_config:
timeout: 30000
retries: 2
webServer:
command: "npm run dev"
url: "http://localhost:3000"
reuseExistingServer: true
test_generation:
entry_point: "server.js"
template_engine: "ejs" # Detect from dependencies
api_endpoints: true # Generate API tests
common_routes:
- "/"
- "/api/health"
- "/api/users"
# ========== STATIC HTML/CSS/JS ==========
static:
name: "Static HTML/CSS/JS"
priority: 10 # Check last (fallback)
detection:
indicators:
- "index.html exists in root"
- "No package.json"
- "No build tools detected"
configuration:
base_url: "http://localhost:8080"
dev_server_command: "npx serve ."
install_dev_server: "npm install -g serve" # Install if needed
playwright_config:
timeout: 15000 # Faster, no build step
retries: 1
webServer:
command: "npx serve . -l 8080"
url: "http://localhost:8080"
reuseExistingServer: true
test_generation:
entry_point: "index.html"
detect_pages_from:
- "HTML files in root"
- "Links in index.html"
# ========== ASTRO ==========
astro:
name: "Astro"
priority: 5
detection:
package_json_dependencies:
required:
- "astro"
config_files:
- "astro.config.mjs"
indicators:
- "src/pages/ directory exists"
configuration:
base_url: "http://localhost:4321"
dev_server_command: "npm run dev"
playwright_config:
webServer:
command: "npm run dev"
url: "http://localhost:4321"
reuseExistingServer: true
# ========== SVELTE + VITE ==========
svelte_vite:
name: "Svelte + Vite"
priority: 6
detection:
package_json_dependencies:
required:
- "svelte"
- "vite"
optional:
- "@sveltejs/vite-plugin-svelte"
config_files:
- "svelte.config.js"
- "vite.config.js"
configuration:
base_url: "http://localhost:5173"
dev_server_command: "npm run dev"
# ========== VUE + VITE ==========
vue_vite:
name: "Vue + Vite"
priority: 7
detection:
package_json_dependencies:
required:
- "vue"
- "vite"
optional:
- "@vitejs/plugin-vue"
config_files:
- "vite.config.ts"
- "vite.config.js"
configuration:
base_url: "http://localhost:5173"
dev_server_command: "npm run dev"
# ========== NUXT ==========
nuxt:
name: "Nuxt"
priority: 8
detection:
package_json_dependencies:
required:
- "nuxt"
config_files:
- "nuxt.config.ts"
- "nuxt.config.js"
configuration:
base_url: "http://localhost:3000"
dev_server_command: "npm run dev"
# ========== DETECTION WORKFLOW ==========
detection_workflow:
steps:
- name: "Check for package.json"
action: "Read package.json if exists"
output: "dependencies and devDependencies lists"
- name: "Match framework patterns"
action: "Compare dependencies against framework patterns"
priority: "Use priority field to determine order"
output: "List of matching frameworks (highest priority first)"
- name: "Verify with config files"
action: "Check if expected config files exist"
output: "Confirm framework match"
- name: "Check additional indicators"
action: "Verify directory structure and entry points"
output: "Final framework identification"
- name: "Determine dev server"
action: "Read package.json scripts section"
fallback: "Use framework's default dev_server_command"
output: "Command to start dev server"
- name: "Detect port"
action: "Check if server is already running"
fallback: "Use framework's default base_url port"
output: "Base URL for tests"
# ========== PORT DETECTION ==========
port_detection:
methods:
- name: "Check running processes"
command: "lsof -i :3000 -i :5173 -i :8080 -i :4321"
description: "See if common ports are in use"
- name: "Check package.json scripts"
pattern: "--port (\\d+)"
description: "Extract port from dev script"
- name: "Check config files"
files:
- "vite.config.ts": "server.port"
- "next.config.js": "devServer.port"
- ".env": "PORT"
- name: "Attempt connection"
ports: [3000, 5173, 8080, 4321, 8000, 4200]
description: "Try common ports in order"
# ========== MULTI-APP DETECTION ==========
fullstack_detection:
patterns:
monorepo:
indicators:
- "package.json with workspaces field"
- "packages/ or apps/ directory"
- "Lerna or Nx configuration"
strategy:
- "Detect each package separately"
- "Generate tests for each app"
- "Configure multiple web servers if needed"
separate_frontend_backend:
indicators:
- "client/ and server/ directories"
- "frontend/ and backend/ directories"
- "Multiple package.json files"
strategy:
- "Detect frontend framework"
- "Detect backend framework"
- "Start both servers for e2e tests"
- "Configure API proxy if needed"
# ========== FALLBACK STRATEGY ==========
fallback:
when_no_match:
- "Prompt user to specify framework manually"
- "List detected files and ask for clarification"
- "Suggest generic static server approach"
generic_config:
base_url: "http://localhost:8080"
dev_server: "npx serve . -l 8080"
timeout: 30000
# ========== VALIDATION ==========
validation:
checks:
- name: "Server starts successfully"
test: "Run dev server command, wait for port to open"
timeout: 120000
failure: "Ask user to start server manually"
- name: "Base URL accessible"
test: "HTTP GET to base_url returns 200"
timeout: 30000
failure: "Check if port is different, ask user"
- name: "Page renders content"
test: "Page has visible content (not blank)"
timeout: 10000
failure: "Possible SPA routing issue, check entry point"

View File

@@ -0,0 +1,303 @@
# Framework Version Compatibility Database
# Used for version-aware template selection and configuration generation
# This file maps framework versions to their syntax, configuration requirements,
# and breaking changes. When the skill detects installed package versions, it
# consults this database to select appropriate templates and warn about issues.
# ========== TAILWIND CSS ==========
tailwindcss:
v3:
version_range: ">=3.0.0 <4.0.0"
detection_priority: 2
syntax:
css_directives:
- "@tailwind base;"
- "@tailwind components;"
- "@tailwind utilities;"
postcss_plugin: "tailwindcss"
config_file: "tailwind.config.js"
templates:
css: "templates/css/tailwind-v3.css"
postcss_config: "templates/configs/postcss-tailwind-v3.js"
tailwind_config: "templates/configs/tailwind-v3.config.js"
notes: "Stable version with @tailwind directive syntax"
v4:
version_range: ">=4.0.0"
detection_priority: 1 # Check first (latest)
syntax:
css_directives:
- "@import \"tailwindcss\";"
postcss_plugin: "@tailwindcss/postcss"
config_file: "tailwind.config.ts" # Now TypeScript by default
templates:
css: "templates/css/tailwind-v4.css"
postcss_config: "templates/configs/postcss-tailwind-v4.js"
tailwind_config: "templates/configs/tailwind-v4.config.ts"
breaking_changes:
- "CSS syntax changed from @tailwind directives to @import"
- "PostCSS plugin renamed from 'tailwindcss' to '@tailwindcss/postcss'"
- "Configuration file now TypeScript by default"
- "Some utility classes restructured (check migration guide)"
migration_guide: "https://tailwindcss.com/docs/upgrade-guide"
notes: "Major rewrite with new @import syntax and improved PostCSS integration"
# ========== REACT ==========
react:
v17:
version_range: ">=17.0.0 <18.0.0"
detection_priority: 2
features:
jsx_transform: "classic" # Requires React import
concurrent_features: false
automatic_batching: false
templates:
component: "templates/react/component-v17.tsx"
notes: "Classic JSX transform, requires 'import React from react'"
v18:
version_range: ">=18.0.0 <19.0.0"
detection_priority: 1
features:
jsx_transform: "automatic" # No React import needed
concurrent_features: true
automatic_batching: true
use_client_directive: false # Not yet (that's React 19)
templates:
component: "templates/react/component-v18.tsx"
breaking_changes:
- "Automatic batching may affect state update timing"
- "Concurrent features require opt-in (via createRoot)"
- "IE11 no longer supported"
migration_guide: "https://react.dev/blog/2022/03/08/react-18-upgrade-guide"
notes: "New JSX transform, concurrent features, automatic batching"
v19:
version_range: ">=19.0.0"
detection_priority: 1
features:
jsx_transform: "automatic"
concurrent_features: true
automatic_batching: true
use_client_directive: true # Server Components
actions: true # Server Actions
templates:
component: "templates/react/component-v19.tsx"
breaking_changes:
- "'use client' directive required for client components in RSC apps"
- "ref is now a regular prop (no forwardRef needed)"
- "Context.Provider shorthand removed (use <Context> directly)"
migration_guide: "https://react.dev/blog/2024/04/25/react-19-upgrade-guide"
notes: "Server Components, Actions, ref as prop, improved performance"
# ========== NEXT.JS ==========
nextjs:
v13:
version_range: ">=13.0.0 <14.0.0"
detection_priority: 2
features:
app_router: true # Optional
pages_router: true
turbopack: "beta"
server_actions: "alpha"
router_detection:
app_router: "app/ directory exists"
pages_router: "pages/ directory exists"
templates:
config: "templates/nextjs/next-v13.config.js"
page_app: "templates/nextjs/page-v13-app.tsx"
page_pages: "templates/nextjs/page-v13-pages.tsx"
notes: "App Router introduced alongside Pages Router"
v14:
version_range: ">=14.0.0 <15.0.0"
detection_priority: 1
features:
app_router: true
pages_router: true
turbopack: "stable"
server_actions: "stable"
partial_prerendering: "experimental"
router_detection:
app_router: "app/ directory exists"
pages_router: "pages/ directory exists"
templates:
config: "templates/nextjs/next-v14.config.js"
page_app: "templates/nextjs/page-v14-app.tsx"
page_pages: "templates/nextjs/page-v14-pages.tsx"
breaking_changes:
- "Server Actions stable (syntax changes from v13)"
- "Turbopack stable for dev (replaces webpack in dev mode)"
- "Minimum Node.js version: 18.17"
migration_guide: "https://nextjs.org/docs/app/building-your-application/upgrading/version-14"
notes: "Stable Server Actions and Turbopack"
# ========== VITE ==========
vite:
v4:
version_range: ">=4.0.0 <5.0.0"
detection_priority: 2
features:
default_port: 5173
rollup_version: 3
css_code_split: true
templates:
config: "templates/vite/vite-v4.config.ts"
notes: "Stable Vite 4 with Rollup 3"
v5:
version_range: ">=5.0.0 <6.0.0"
detection_priority: 1
features:
default_port: 5173
rollup_version: 4
css_code_split: true
improved_hmr: true
templates:
config: "templates/vite/vite-v5.config.ts"
breaking_changes:
- "Rollup 4 (plugin compatibility check needed)"
- "Minimum Node.js version: 18.0"
- "Some deprecated options removed"
migration_guide: "https://vitejs.dev/guide/migration"
notes: "Vite 5 with Rollup 4 and improved HMR"
# ========== PLAYWRIGHT ==========
playwright:
v1_40_plus:
version_range: ">=1.40.0"
detection_priority: 1
features:
component_testing: true
trace_viewer: true
codegen: true
auto_waiting: true
templates:
config: "templates/playwright.config.template.ts"
test_spec: "templates/test-spec.template.ts"
page_object: "templates/page-object.template.ts"
notes: "Modern Playwright with full feature set"
# ========== POSTCSS ==========
postcss:
v8:
version_range: ">=8.0.0"
detection_priority: 1
notes: "Standard PostCSS v8 - most common version"
# ========== VERSION DETECTION STRATEGIES ==========
detection_strategies:
priority_order:
description: "Check versions in priority order (higher priority first)"
example: "For Tailwind: check v4 rules before v3 rules"
semver_matching:
description: "Use semver.satisfies() for version range matching"
library: "semver npm package"
fallback:
description: "If no version matches, warn and use sensible defaults"
default_strategy: "Use latest stable templates with warning"
multi_framework:
description: "Detect multiple frameworks simultaneously"
example: "React 18 + Vite 5 + Tailwind 4 + Playwright 1.40"
# ========== COMMON BREAKING CHANGE PATTERNS ==========
breaking_change_categories:
syntax_changes:
examples:
- "Tailwind v3→v4: @tailwind → @import"
- "React v17→v18: Optional JSX import"
impact: "Critical - app won't build/run"
detection: "Parse error, syntax error in build output"
configuration_changes:
examples:
- "Tailwind v4: PostCSS plugin rename"
- "Next.js v14: next.config.js options"
impact: "High - build fails or misconfigured"
detection: "Build error, plugin not found"
api_changes:
examples:
- "React v19: ref as prop"
- "Next.js v13: getServerSideProps in App Router"
impact: "Medium - runtime errors or warnings"
detection: "TypeScript errors, runtime warnings"
deprecation_warnings:
examples:
- "React v18: ReactDOM.render → createRoot"
- "Vite: Legacy options removed"
impact: "Low - works but will break in future"
detection: "Console warnings, deprecation notices"
# ========== USAGE NOTES ==========
usage:
detection_flow:
- "Read package.json dependencies and devDependencies"
- "For each framework, iterate through versions in priority order"
- "Use semver.satisfies(installedVersion, versionRange)"
- "First match wins (highest priority)"
- "If no match, use fallback and warn"
template_selection:
- "Based on detected version, select appropriate template files"
- "Templates use version-specific syntax and best practices"
- "Combine multiple framework templates (e.g., React + Tailwind + Vite)"
error_prevention:
- "Check for known breaking changes before generating config"
- "Warn user if mixing incompatible versions"
- "Provide migration guides for major version differences"
maintenance:
- "Add new versions as they're released"
- "Update breaking_changes based on real-world issues"
- "Keep migration_guide links current"
- "Test template compatibility regularly"

View File

@@ -0,0 +1,456 @@
# Playwright Best Practices
Official best practices for Playwright test automation, optimized for LLM-assisted development.
## Test Structure
### Use Page Object Models (POM)
**Why**: Separates page structure from test logic, improves maintainability
```typescript
// Good: Page Object Model
// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.page.getByLabel('Username').fill(username);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign in' }).click();
}
}
// specs/login.spec.ts
test('user can login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
```
### Use Semantic Selectors
**Priority order** (most stable → least stable):
1. **getByRole** - Accessible role (button, heading, textbox, etc.)
2. **getByLabel** - Form inputs with associated labels
3. **getByPlaceholder** - Input placeholder text
4. **getByText** - User-visible text content
5. **getByTestId** - data-testid attributes (last resort)
```typescript
// Best: Role-based (accessible and stable)
await page.getByRole('button', { name: 'Submit' }).click();
// Good: Label-based (for forms)
await page.getByLabel('Email address').fill('user@example.com');
// Acceptable: Text-based
await page.getByText('Continue to checkout').click();
// Avoid: CSS selectors (brittle)
await page.click('.btn-primary'); // ❌ Breaks if class changes
// Last resort: Test IDs (when semantic selectors don't work)
await page.getByTestId('checkout-button').click();
```
## Screenshot Best Practices
### When to Capture Screenshots
1. **Initial page load** - Baseline visual state
2. **Before interaction** - Pre-state for comparison
3. **After interaction** - Result of user action
4. **Error states** - When validation fails or errors occur
5. **Success states** - Confirmation screens, success messages
6. **Test failures** - Automatic capture for debugging
### Screenshot Naming Convention
```typescript
// Pattern: {test-name}-{viewport}-{state}-{timestamp}.png
await page.screenshot({
path: `screenshots/current/login-desktop-initial-${Date.now()}.png`,
fullPage: true
});
await page.screenshot({
path: `screenshots/current/checkout-mobile-error-${Date.now()}.png`,
fullPage: true
});
```
### Full-Page vs Element Screenshots
```typescript
// Full-page: For layout and overall UI analysis
await page.screenshot({
path: 'homepage-full.png',
fullPage: true // Captures entire scrollable page
});
// Element-specific: For component testing
const button = page.getByRole('button', { name: 'Submit' });
await button.screenshot({
path: 'submit-button.png'
});
```
## Waiting and Timing
### Auto-Waiting
Playwright automatically waits for:
- Element to be attached to DOM
- Element to be visible
- Element to be stable (not animating)
- Element to receive events (not obscured)
- Element to be enabled
```typescript
// This automatically waits for button to be clickable
await page.getByRole('button', { name: 'Submit' }).click();
```
### Explicit Waits (when needed)
```typescript
// Wait for navigation
await page.waitForURL('/dashboard');
// Wait for network idle (good before screenshots)
await page.waitForLoadState('networkidle');
// Wait for specific element
await page.waitForSelector('img[alt="Profile picture"]');
// Wait for custom condition
await page.waitForFunction(() => window.scrollY === 0);
```
### Avoid Fixed Timeouts
```typescript
// Bad: Arbitrary delays
await page.waitForTimeout(3000); // ❌ Flaky, slow
// Good: Wait for specific condition
await expect(page.getByText('Success')).toBeVisible(); // ✅ Fast and reliable
```
## Test Isolation
### Independent Tests
Each test should be completely independent:
```typescript
// Good: Test is self-contained
test('user can add item to cart', async ({ page }) => {
// Set up: Create user, log in
await page.goto('/');
await login(page, 'user@example.com', 'password');
// Action: Add to cart
await page.getByRole('button', { name: 'Add to cart' }).click();
// Assert: Item in cart
await expect(page.getByTestId('cart-count')).toHaveText('1');
// Cleanup happens automatically with new page context
});
// Bad: Depends on previous test state
test('user can checkout', async ({ page }) => {
// ❌ Assumes cart already has items from previous test
await page.goto('/checkout');
// ...
});
```
### Use test.beforeEach for Common Setup
```typescript
test.describe('Shopping cart', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await login(page, 'user@example.com', 'password');
});
test('can add item to cart', async ({ page }) => {
// Setup already done
await page.getByRole('button', { name: 'Add to cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});
test('can remove item from cart', async ({ page }) => {
// Setup already done, fresh state
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByRole('button', { name: 'Remove' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('0');
});
});
```
## Visual Regression Testing
### Snapshot Testing
```typescript
// Basic snapshot
await expect(page).toHaveScreenshot('homepage.png');
// With threshold (allow minor differences)
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixelRatio: 0.05 // Allow 5% difference
});
// Element snapshot
const card = page.getByRole('article').first();
await expect(card).toHaveScreenshot('product-card.png');
```
### Updating Baselines
```bash
# Update all snapshots
npx playwright test --update-snapshots
# Update specific test
npx playwright test login.spec.ts --update-snapshots
```
### Baseline Management
- **Store baselines in git** - Commit to repository for consistency
- **Review diffs carefully** - Not all changes are bugs
- **Update deliberately** - Only update when changes are intentional
- **Use CI checks** - Fail pipeline on unexpected visual changes
## Configuration Best Practices
### playwright.config.ts Essentials
```typescript
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
// Timeout for each test
timeout: 30 * 1000,
// Global setup/teardown
globalSetup: require.resolve('./tests/setup/global-setup.ts'),
// Fail fast on CI, retry locally
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
// Parallel execution
workers: process.env.CI ? 1 : undefined,
// Reporter
reporter: process.env.CI ? 'github' : 'html',
use: {
// Base URL
baseURL: 'http://localhost:5173',
// Screenshot on failure
screenshot: 'only-on-failure',
// Trace on first retry
trace: 'on-first-retry',
// Video on failure
video: 'retain-on-failure',
},
// Projects for multi-browser testing
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 13'] },
},
],
// Web server for dev
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});
```
## Debugging
### Playwright Inspector
```bash
# Debug specific test
npx playwright test --debug login.spec.ts
# Debug from specific line
npx playwright test --debug --grep "user can login"
```
### VS Code Debugger
```json
// .vscode/launch.json
{
"configurations": [
{
"name": "Debug Playwright Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/@playwright/test/cli.js",
"args": ["test", "--headed", "${file}"],
"console": "integratedTerminal"
}
]
}
```
### Trace Viewer
```bash
# Run with trace
npx playwright test --trace on
# View trace
npx playwright show-trace trace.zip
```
## Performance Optimization
### Parallel Execution
```typescript
// Run tests in parallel (default)
test.describe.configure({ mode: 'parallel' });
// Run tests serially (when needed)
test.describe.configure({ mode: 'serial' });
```
### Reuse Authentication State
```typescript
// global-setup.ts
import { chromium } from '@playwright/test';
export default async function globalSetup() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('http://localhost:5173/login');
await page.getByLabel('Username').fill('admin');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Save authentication state
await page.context().storageState({ path: 'auth.json' });
await browser.close();
}
// Use in tests
test.use({ storageState: 'auth.json' });
```
## Common Pitfalls to Avoid
### 1. Not Waiting for Network Idle Before Screenshots
```typescript
// Bad: Screenshot may capture loading state
await page.goto('/dashboard');
await page.screenshot({ path: 'dashboard.png' });
// Good: Wait for content to load
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
await page.screenshot({ path: 'dashboard.png' });
```
### 2. Using Non-Stable Selectors
```typescript
// Bad: Position-based (breaks if order changes)
await page.locator('button').nth(2).click();
// Good: Content-based
await page.getByRole('button', { name: 'Submit' }).click();
```
### 3. Not Handling Dynamic Content
```typescript
// Bad: Assumes content is already loaded
const text = await page.getByTestId('user-name').textContent();
// Good: Wait for element first
await expect(page.getByTestId('user-name')).toBeVisible();
const text = await page.getByTestId('user-name').textContent();
```
### 4. Overly Broad Assertions
```typescript
// Bad: Fails on any minor change
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0 });
// Good: Allow reasonable tolerance
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.02 });
```
## Summary Checklist
- [ ] Use Page Object Models for test organization
- [ ] Prefer semantic selectors (getByRole, getByLabel)
- [ ] Capture screenshots at key interaction points
- [ ] Wait for network idle before screenshots
- [ ] Use auto-waiting instead of fixed timeouts
- [ ] Make tests independent and isolated
- [ ] Configure proper retry logic (2-3 retries in CI)
- [ ] Store authentication state for reuse
- [ ] Use trace viewer for debugging
- [ ] Review visual diffs before updating baselines
- [ ] Run tests in parallel for performance
- [ ] Enable screenshot/video on failure
- [ ] Store baselines in version control
- [ ] Use meaningful screenshot names with timestamps
- [ ] Configure appropriate visual diff thresholds
---
**References:**
- [Playwright Official Docs](https://playwright.dev/)
- [Best Practices Guide](https://playwright.dev/docs/best-practices)
- [Locators Guide](https://playwright.dev/docs/locators)

View File

@@ -0,0 +1,91 @@
import { Page, Locator } from '@playwright/test';
/**
* HomePage Page Object Model
*
* Example POM for a React + Vite application homepage
* Demonstrates best practices for locator selection
*/
export class HomePage {
readonly page: Page;
// Locators - Using semantic selectors (priority: getByRole > getByLabel > getByText > getByTestId)
readonly welcomeMessage: Locator;
readonly aboutLink: Locator;
readonly contactLink: Locator;
readonly navbar: Locator;
readonly heroSection: Locator;
readonly ctaButton: Locator;
readonly featureCards: Locator;
constructor(page: Page) {
this.page = page;
// Initialize locators with semantic selectors
this.navbar = page.getByRole('navigation');
this.welcomeMessage = page.getByRole('heading', { name: /welcome/i });
this.aboutLink = page.getByRole('link', { name: /about/i });
this.contactLink = page.getByRole('link', { name: /contact/i });
this.heroSection = page.getByRole('banner');
this.ctaButton = page.getByRole('button', { name: /get started/i });
this.featureCards = page.getByRole('article');
}
/**
* Navigate to homepage
*/
async goto() {
await this.page.goto('/');
await this.page.waitForLoadState('networkidle');
}
/**
* Wait for page to be fully loaded and ready
*/
async waitForReady() {
await this.welcomeMessage.waitFor({ state: 'visible' });
await this.navbar.waitFor({ state: 'visible' });
}
/**
* Navigate to About page
*/
async goToAbout() {
await this.aboutLink.click();
await this.page.waitForURL('**/about');
}
/**
* Navigate to Contact page
*/
async goToContact() {
await this.contactLink.click();
await this.page.waitForURL('**/contact');
}
/**
* Click the main CTA button
*/
async clickCTA() {
await this.ctaButton.click();
}
/**
* Get count of feature cards
*/
async getFeatureCardCount(): Promise<number> {
return await this.featureCards.count();
}
/**
* Take screenshot of homepage
*/
async screenshot(name: string = 'homepage') {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
await this.page.screenshot({
path: `screenshots/current/${name}-${timestamp}.png`,
fullPage: true,
});
}
}

View File

@@ -0,0 +1,149 @@
import { test, expect } from '@playwright/test';
import { HomePage } from '../pages/home.page';
import { captureWithContext } from '../utils/screenshot-helper';
/**
* Example Playwright Test for React + Vite Application
*
* This demonstrates best practices for e2e testing with screenshot capture
*/
test.describe('Homepage', () => {
let homePage: HomePage;
test.beforeEach(async ({ page }) => {
homePage = new HomePage(page);
await homePage.goto();
// Capture initial page load
await captureWithContext(page, 'homepage-initial-load', 'Homepage loaded successfully');
});
test('should display welcome message', async ({ page }) => {
// Arrange: Page is already loaded in beforeEach
// Act: No action needed, just checking initial state
await captureWithContext(page, 'homepage-welcome-check', 'Checking for welcome message');
// Assert: Welcome message is visible
await expect(homePage.welcomeMessage).toBeVisible();
await expect(homePage.welcomeMessage).toContainText('Welcome');
});
test('should navigate to about page when clicking About link', async ({ page }) => {
// Arrange: Page loaded
await captureWithContext(page, 'homepage-before-nav', 'Before clicking About link');
// Act: Click About link
await homePage.aboutLink.click();
// Capture after navigation
await page.waitForURL('**/about');
await captureWithContext(page, 'about-page-loaded', 'About page after navigation');
// Assert: URL changed and about page content visible
expect(page.url()).toContain('/about');
await expect(page.getByRole('heading', { name: 'About' })).toBeVisible();
});
test('should submit contact form successfully', async ({ page }) => {
// Arrange: Navigate to contact page
await page.goto('/contact');
await captureWithContext(page, 'contact-form-initial', 'Contact form initial state');
// Act: Fill out form
await page.getByLabel('Name').fill('John Doe');
await page.getByLabel('Email').fill('john@example.com');
await page.getByLabel('Message').fill('This is a test message');
await captureWithContext(page, 'contact-form-filled', 'Form filled before submission');
await page.getByRole('button', { name: 'Send Message' }).click();
// Wait for success message
await page.waitForSelector('[data-testid="success-message"]', { state: 'visible' });
await captureWithContext(page, 'contact-form-success', 'Success message displayed');
// Assert: Success message appears
await expect(page.getByTestId('success-message')).toBeVisible();
await expect(page.getByTestId('success-message')).toContainText('Message sent successfully');
});
test('should validate required fields', async ({ page }) => {
// Arrange: Navigate to contact page
await page.goto('/contact');
await captureWithContext(page, 'contact-form-validation-init', 'Before validation check');
// Act: Try to submit empty form
await page.getByRole('button', { name: 'Send Message' }).click();
await captureWithContext(page, 'contact-form-validation-errors', 'Validation errors displayed');
// Assert: Error messages appear
await expect(page.getByText('Name is required')).toBeVisible();
await expect(page.getByText('Email is required')).toBeVisible();
await expect(page.getByText('Message is required')).toBeVisible();
});
test('should not have accessibility violations', async ({ page }) => {
const AxeBuilder = (await import('@axe-core/playwright')).default;
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
await captureWithContext(
page,
'homepage-accessibility-check',
`Found ${accessibilityScanResults.violations.length} accessibility violations`
);
// Log violations for review
if (accessibilityScanResults.violations.length > 0) {
console.log('\n⚠ Accessibility Violations:');
accessibilityScanResults.violations.forEach((violation) => {
console.log(`\n- ${violation.id}: ${violation.description}`);
console.log(` Impact: ${violation.impact}`);
console.log(` Nodes: ${violation.nodes.length}`);
});
}
// Fail on critical violations only (for this example)
const criticalViolations = accessibilityScanResults.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
);
expect(criticalViolations).toEqual([]);
});
test('should display correctly across viewports', async ({ page }) => {
const viewports = [
{ name: 'desktop', width: 1280, height: 720 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'mobile', width: 375, height: 667 },
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.waitForTimeout(500); // Let responsive changes settle
await captureWithContext(
page,
`homepage-responsive-${viewport.name}`,
`${viewport.width}x${viewport.height} viewport`
);
// Verify no horizontal scroll on mobile/tablet
if (viewport.name !== 'desktop') {
const scrollWidth = await page.evaluate(() => document.body.scrollWidth);
const clientWidth = await page.evaluate(() => document.body.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1); // Allow 1px tolerance
}
// Verify main navigation is accessible
const nav = page.getByRole('navigation');
await expect(nav).toBeVisible();
}
});
});

View File

@@ -0,0 +1,592 @@
# Fix Recommendations
**Generated**: 2025-11-01 16:35:22
**Based On**: visual-analysis-report.md
**Issues Addressed**: 8
**Estimated Effort**: 4-6 hours
---
## How to Use This Report
Each fix includes:
- **File location** with line numbers (when identifiable)
- **Current code** showing the problematic implementation
- **Recommended fix** with specific code changes
- **Reasoning** explaining why this fix works
- **Testing steps** to validate the fix
Apply fixes in priority order: Critical → High → Medium → Low
---
## Critical Fixes (Implement Immediately)
### Fix #1: Increase Form Label Contrast
**Issue**: Insufficient color contrast on form labels (2.6:1, requires 4.5:1)
**Location**: `src/components/ContactForm.tsx:45-52`
**Current Code**:
```tsx
<label htmlFor="name" className="block text-gray-400 text-sm mb-1">
Name
</label>
<input
id="name"
type="text"
className="w-full px-4 py-2 border border-gray-300 rounded"
placeholder="Enter your name"
/>
```
**Recommended Fix**:
```tsx
<label htmlFor="name" className="block text-gray-700 text-sm font-medium mb-1">
Name
</label>
<input
id="name"
type="text"
className="w-full px-4 py-2 border border-gray-300 rounded"
placeholder="Enter your name"
aria-required="true"
/>
```
**Changes Made**:
- `text-gray-400``text-gray-700` (changes color from #AAAAAA to #374151)
- Added `font-medium` for improved readability
- Added `aria-required="true"` for accessibility
**Reasoning**:
- `text-gray-700` (#374151) on white (#FFFFFF) = 9.7:1 contrast ratio ✅
- Exceeds WCAG 2.1 AA requirement (4.5:1)
- `font-medium` improves readability without affecting contrast
- `aria-required` helps screen reader users identify required fields
**Testing**:
1. Visual check: Labels should be clearly readable
2. Contrast tool: Verify 9.7:1 ratio at https://webaim.org/resources/contrastchecker/
3. Accessibility audit: Run axe-core, verify no contrast violations
4. Screen reader: Test with NVDA/VoiceOver, verify required field announcement
**Impact**: Fixes critical WCAG 2.1 violation, improves usability for low-vision users
---
### Fix #2: Responsive Button Text Sizing
**Issue**: Button text truncated on mobile (shows "Send Mes...")
**Location**: `src/components/ContactForm.tsx:78`
**Current Code**:
```tsx
<button
type="submit"
className="w-full px-6 py-3 text-xl font-bold bg-blue-600 text-white rounded"
>
Send Message
</button>
```
**Recommended Fix**:
```tsx
<button
type="submit"
className="w-full px-4 py-2 text-sm sm:text-base md:text-lg font-bold bg-blue-600 text-white rounded whitespace-nowrap overflow-visible"
>
Send Message
</button>
```
**Changes Made**:
- `px-6``px-4` (reduced padding to allow more text space)
- `py-3``py-2` (slightly reduced vertical padding)
- `text-xl``text-sm sm:text-base md:text-lg` (responsive text sizing)
- Added `whitespace-nowrap` (prevent text wrapping)
- Added `overflow-visible` (ensure text isn't hidden)
**Reasoning**:
- Mobile (375px): 14px font (text-sm) fits comfortably
- Tablet (768px): 16px font (text-base) for better readability
- Desktop (1280px): 18px font (text-lg) for prominence
- Reduced padding provides more space for text
- `whitespace-nowrap` prevents awkward line breaks
**Testing**:
1. Mobile (375px viewport): Verify full text "Send Message" visible
2. Tablet (768px): Check font size scales appropriately
3. Desktop (1280px): Ensure button looks proportional
4. Accessibility: Verify button is tappable (min 44x44px)
**Impact**: Fixes broken user experience on mobile, ensures button purpose is clear
---
## High Priority Fixes
### Fix #3: Prevent Navigation Overlap on Tablet
**Issue**: Nav items overlap on tablet breakpoint (768px)
**Location**: `src/components/Header.tsx:32-45`
**Current Code**:
```tsx
<nav className="flex space-x-6">
<a href="/" className="text-gray-700 hover:text-blue-600">
Home
</a>
<a href="/about" className="text-gray-700 hover:text-blue-600">
About
</a>
<a href="/contact" className="text-gray-700 hover:text-blue-600">
Contact
</a>
<a href="/blog" className="text-gray-700 hover:text-blue-600">
Blog
</a>
</nav>
```
**Recommended Fix**:
```tsx
<nav className="flex flex-col md:flex-row md:space-x-6 space-y-2 md:space-y-0">
<a href="/" className="text-gray-700 hover:text-blue-600 py-2 md:py-0">
Home
</a>
<a href="/about" className="text-gray-700 hover:text-blue-600 py-2 md:py-0">
About
</a>
<a href="/contact" className="text-gray-700 hover:text-blue-600 py-2 md:py-0">
Contact
</a>
<a href="/blog" className="text-gray-700 hover:text-blue-600 py-2 md:py-0">
Blog
</a>
</nav>
```
**Alternative Fix** (if horizontal menu required):
```tsx
<nav className="flex space-x-3 md:space-x-6 text-sm md:text-base">
<a href="/" className="text-gray-700 hover:text-blue-600 whitespace-nowrap">
Home
</a>
<a href="/about" className="text-gray-700 hover:text-blue-600 whitespace-nowrap">
About
</a>
<a href="/contact" className="text-gray-700 hover:text-blue-600 whitespace-nowrap">
Contact
</a>
<a href="/blog" className="text-gray-700 hover:text-blue-600 whitespace-nowrap">
Blog
</a>
</nav>
```
**Reasoning**:
- **Option 1**: Stack links vertically on tablet/mobile, horizontal on desktop
- More reliable, works with any link text length
- Better for mobile usability
- **Option 2**: Reduce spacing and font size on smaller screens
- Maintains horizontal layout
- Risk: May still overflow with longer link text
**Recommendation**: Use Option 1 for reliability
**Testing**:
1. Tablet (768px): Verify links stack vertically or have adequate spacing
2. Desktop (1024px+): Verify links display horizontally
3. Check all breakpoints: 640px, 768px, 1024px, 1280px
4. Test with longer link text (e.g., "Our Services" instead of "Blog")
**Impact**: Fixes navigation usability on tablet devices
---
### Fix #4: Prevent Hero Image Distortion
**Issue**: Hero background image stretched on mobile viewport
**Location**: `src/components/Hero.tsx:15-25`
**Current Code**:
```tsx
<div
className="hero-section h-96 bg-cover bg-center"
style={{
backgroundImage: "url('/images/hero-bg.jpg')",
}}
>
<div className="container mx-auto h-full flex items-center">
<h1 className="text-4xl font-bold text-white">Welcome to Our Site</h1>
</div>
</div>
```
**Recommended Fix**:
```tsx
<div className="hero-section h-96 relative overflow-hidden">
<img
src="/images/hero-bg.jpg"
alt=""
className="absolute inset-0 w-full h-full object-cover object-center"
aria-hidden="true"
/>
<div className="container mx-auto h-full flex items-center relative z-10">
<h1 className="text-4xl font-bold text-white drop-shadow-lg">
Welcome to Our Site
</h1>
</div>
</div>
```
**Changes Made**:
- Replaced CSS background image with `<img>` tag
- Added `object-cover` to maintain aspect ratio while filling container
- Added `object-center` for centered focal point
- Made container `relative` with image `absolute` for layering
- Added `drop-shadow-lg` to h1 for better text visibility
- Added `aria-hidden="true"` since image is decorative
**Reasoning**:
- `object-cover` scales image proportionally to fill container
- Crops excess rather than stretching to fit
- Maintains image quality and recognizability
- Works consistently across all viewport sizes
**Testing**:
1. Mobile (375px): Verify image not stretched, focal point visible
2. Tablet (768px): Check image scales appropriately
3. Desktop (1280px): Ensure full image coverage
4. Test with different aspect ratio images (16:9, 4:3, 1:1)
**Impact**: Professional appearance maintained across all devices
---
### Fix #5: Add Visible Error Messages
**Issue**: Form validation errors indicated only by red border (no text)
**Location**: `src/components/ContactForm.tsx:55-95`
**Current Code**:
```tsx
<input
id="email"
type="email"
className={`w-full px-4 py-2 border rounded ${
errors.email ? 'border-red-500' : 'border-gray-300'
}`}
/>
```
**Recommended Fix**:
```tsx
<div className="mb-4">
<label htmlFor="email" className="block text-gray-700 text-sm font-medium mb-1">
Email Address <span className="text-red-600" aria-label="required">*</span>
</label>
<input
id="email"
type="email"
className={`w-full px-4 py-2 border rounded ${
errors.email
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-blue-500'
}`}
aria-invalid={errors.email ? 'true' : 'false'}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<div
id="email-error"
className="mt-1 text-sm text-red-600 flex items-center"
role="alert"
>
<svg
className="w-4 h-4 mr-1 flex-shrink-0"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
{errors.email}
</div>
)}
</div>
```
**Changes Made**:
- Added required indicator (*) with `aria-label`
- Added `aria-invalid` attribute for screen readers
- Added `aria-describedby` linking to error message
- Added visible error message below input
- Added error icon for visual reinforcement
- Added `role="alert"` to announce errors to screen readers
**Reasoning**:
- Error text provides specific guidance (not just "there's an error")
- Icon + color + text = multiple indicators (not color alone)
- ARIA attributes ensure screen reader compatibility
- Error message ID allows programmatic association with input
**Testing**:
1. Visual: Submit empty form, verify error text appears below inputs
2. Screen reader: Verify error messages are announced
3. Keyboard: Tab to input, verify error is read aloud
4. Contrast: Verify error text meets 4.5:1 ratio (red-600 on white)
**Impact**: Makes form errors accessible to all users, improves error recovery
---
## Medium Priority Fixes
### Fix #6: Standardize Feature Card Heights
**Issue**: Feature cards have inconsistent heights
**Location**: `src/components/FeatureSection.tsx:28-42`
**Current Code**:
```tsx
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{features.map((feature) => (
<div key={feature.id} className="bg-white p-6 rounded shadow">
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
<p className="text-gray-600">{feature.description}</p>
</div>
))}
</div>
```
**Recommended Fix**:
```tsx
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 items-start">
{features.map((feature) => (
<div key={feature.id} className="bg-white p-6 rounded shadow flex flex-col h-full">
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
<p className="text-gray-600 flex-grow">{feature.description}</p>
{feature.link && (
<a
href={feature.link}
className="mt-4 text-blue-600 hover:text-blue-700 font-medium"
>
Learn more
</a>
)}
</div>
))}
</div>
```
**Changes Made**:
- Added `items-start` to grid (align cards to top)
- Added `flex flex-col h-full` to card (flexbox layout)
- Added `flex-grow` to description (fills available space)
- Positioned link at bottom with `mt-4` (consistent spacing)
**Reasoning**:
- `h-full` makes all cards same height (tallest card determines height)
- `flex-grow` on description pushes "Learn more" link to bottom
- Creates visual consistency across grid
- Maintains readability while looking polished
**Testing**:
1. Desktop (3 columns): Verify all cards same height
2. Test with varying description lengths
3. Ensure "Learn more" links align at bottom
4. Mobile (1 column): Verify cards still look good stacked
**Impact**: More professional, polished appearance
---
### Fix #7: Increase Footer Link Spacing on Mobile
**Issue**: Footer links only 4-6px apart on mobile, difficult to tap
**Location**: `src/components/Footer.tsx:45-58`
**Current Code**:
```tsx
<div className="flex flex-col space-y-1">
<a href="/about" className="text-gray-600 hover:text-gray-900">
About
</a>
<a href="/contact" className="text-gray-600 hover:text-gray-900">
Contact
</a>
<a href="/privacy" className="text-gray-600 hover:text-gray-900">
Privacy Policy
</a>
</div>
```
**Recommended Fix**:
```tsx
<div className="flex flex-col space-y-3">
<a
href="/about"
className="text-gray-600 hover:text-gray-900 py-2 -my-2 inline-block"
>
About
</a>
<a
href="/contact"
className="text-gray-600 hover:text-gray-900 py-2 -my-2 inline-block"
>
Contact
</a>
<a
href="/privacy"
className="text-gray-600 hover:text-gray-900 py-2 -my-2 inline-block"
>
Privacy Policy
</a>
</div>
```
**Changes Made**:
- `space-y-1``space-y-3` (increased spacing from ~4px to ~12px)
- Added `py-2` (8px vertical padding, expanding tap area)
- Added `-my-2` (negative margin to maintain visual spacing)
- Added `inline-block` (allow vertical padding on inline element)
**Reasoning**:
- `space-y-3` provides minimum 8px spacing (WCAG recommendation)
- `py-2` creates 44px minimum tap target height (8px padding + ~28px text)
- Negative margin prevents excessive visual spacing
- Easier to tap accurately on mobile devices
**Testing**:
1. Mobile (375px): Verify 44x44px minimum tap target
2. Test tapping each link with finger (not stylus)
3. Ensure no accidental mis-taps to adjacent links
4. Check visual spacing looks appropriate
**Impact**: Improved mobile usability, reduces user frustration
---
## Low Priority Fixes
### Fix #8: Improve Heading Size Hierarchy
**Issue**: H2 and H3 appear same size, reducing visual hierarchy
**Location**: `src/styles/globals.css:15-25` OR Tailwind config
**Current Code** (CSS):
```css
h2, h3 {
font-size: 1.25rem; /* 20px */
font-weight: 700;
}
```
**Recommended Fix** (CSS):
```css
h2 {
font-size: 1.5rem; /* 24px */
font-weight: 700;
margin-bottom: 0.75rem;
}
h3 {
font-size: 1.25rem; /* 20px */
font-weight: 600;
margin-bottom: 0.5rem;
}
```
**OR** (Tailwind utility classes):
Replace `text-xl` on H2s with `text-2xl`, keep `text-xl` on H3s:
```tsx
<h2 className="text-2xl font-bold mb-3">Section Heading</h2>
<h3 className="text-xl font-semibold mb-2">Subsection Heading</h3>
```
**Reasoning**:
- H2 (24px) → H3 (20px) creates clear hierarchy
- Progressively lighter font weights reinforce hierarchy
- Proper heading sizes aid content scanning
- Improves semantic structure perception
**Testing**:
1. Visual check: H1 > H2 > H3 size progression
2. Compare before/after screenshots
3. Test with screen reader: Verify heading navigation still works
4. Check across different pages for consistency
**Impact**: Improved content scanability and professional appearance
---
## Implementation Checklist
### Critical (Do First)
- [ ] Fix #1: Form label contrast
- [ ] Fix #2: Button text sizing
### High (This Sprint)
- [ ] Fix #3: Navigation overlap
- [ ] Fix #4: Hero image aspect ratio
- [ ] Fix #5: Visible error messages
### Medium (Next Iteration)
- [ ] Fix #6: Card height consistency
- [ ] Fix #7: Footer link spacing
### Low (Backlog)
- [ ] Fix #8: Heading hierarchy
---
## Testing After Implementation
1. **Re-run Playwright tests**:
```bash
npm run test:e2e
```
2. **Capture new screenshots**:
```bash
npm run test:e2e -- --update-snapshots
```
3. **Run accessibility audit**:
```bash
npm run test:e2e -- accessibility.spec.ts
```
4. **Manual testing**:
- Test on real devices (iPhone, Android, iPad)
- Test with screen reader (VoiceOver on iOS, TalkBack on Android)
- Verify color contrast with browser DevTools
5. **Compare screenshots**:
- Before: `screenshots/baselines/`
- After: `screenshots/current/`
- Ensure visual improvements visible
---
**Generated by**: playwright-e2e-automation skill
**Estimated Total Time**: 4-6 hours
**Confidence Level**: High (fixes based on standard patterns)
All fixes follow React + Tailwind CSS best practices and maintain existing code structure.

View File

@@ -0,0 +1,266 @@
# Visual Analysis Report
**Generated**: 2025-11-01 16:30:45
**Test Run**: Homepage e2e tests
**Screenshots Analyzed**: 23
**Issues Found**: 8 (2 Critical, 3 High, 2 Medium, 1 Low)
---
## Executive Summary
Analyzed 23 screenshots across 3 viewports (desktop 1280x720, tablet 768x1024, mobile 375x667). Found 8 UI/UX issues requiring attention, including 2 critical accessibility violations and 3 high-priority layout bugs.
### Issue Breakdown by Category
- **Layout Issues**: 4
- **Accessibility Violations**: 2
- **Typography Problems**: 1
- **Responsive Design Issues**: 1
### Issue Breakdown by Severity
- **Critical** (P0): 2 issues - Fix immediately
- **High** (P1): 3 issues - Fix within sprint
- **Medium** (P2): 2 issues - Address in next iteration
- **Low** (P3): 1 issue - Polish/enhancement
---
## Critical Issues (P0)
### 1. Insufficient Color Contrast on Form Labels
**Severity**: Critical
**Category**: Accessibility
**Viewport**: All viewports
**Screenshot**: `screenshots/current/contact-form-initial-2025-11-01T16-28-32.png`
**Description**:
Form input labels use light gray (#AAAAAA) on white background (#FFFFFF), resulting in a contrast ratio of only 2.6:1. WCAG 2.1 AA requires 4.5:1 for normal text.
**User Impact**:
Users with low vision or color blindness cannot read form labels, making the contact form unusable for accessibility-dependent users. Fails WCAG 2.1 criterion 1.4.3 (Contrast Minimum).
**Visual Evidence**:
In the screenshot, the "Name", "Email", and "Message" labels appear very faint and difficult to read against the white background.
**Affected Elements**:
- Name label
- Email label
- Message label
**Recommended Fix**: See fix-recommendations.md #1
---
### 2. Button Text Truncated on Mobile Viewport
**Severity**: Critical
**Category**: Layout / Responsive
**Viewport**: Mobile (375x667)
**Screenshot**: `screenshots/current/contact-form-filled-mobile-2025-11-01T16-29-15.png`
**Description**:
The "Send Message" button text is cut off mid-word on mobile viewport, displaying "Send Mes..." due to fixed width and large font size.
**User Impact**:
Users cannot see the full button text, creating confusion about the button's purpose and reducing trust in the interface.
**Visual Evidence**:
The submit button shows truncated text with an ellipsis, indicating the button width is insufficient for the text content at the current font size.
**Recommended Fix**: See fix-recommendations.md #2
---
## High Priority Issues (P1)
### 3. Navigation Menu Items Overlap on Tablet Viewport
**Severity**: High
**Category**: Layout
**Viewport**: Tablet (768x1024)
**Screenshot**: `screenshots/current/homepage-responsive-tablet-2025-11-01T16-27-45.png`
**Description**:
Navigation menu items in the header overlap each other at tablet breakpoint (768px), causing "About" and "Contact" links to partially obscure each other.
**User Impact**:
Users cannot click on navigation links reliably, potentially clicking the wrong link or missing links entirely.
**Visual Evidence**:
Screenshot shows "About" and "Contact" link text overlapping in the header navigation bar.
**Recommended Fix**: See fix-recommendations.md #3
---
### 4. Hero Section Image Stretched on Mobile
**Severity**: High
**Category**: Responsive / Layout
**Viewport**: Mobile (375x667)
**Screenshot**: `screenshots/current/homepage-responsive-mobile-2025-11-01T16-27-52.png`
**Description**:
Hero section background image appears stretched and distorted on mobile viewport. The 16:9 image is forced into a narrow vertical space, causing visible distortion.
**User Impact**:
Unprofessional appearance reduces user trust and brand perception. Image content may be unrecognizable when distorted.
**Visual Evidence**:
The hero image shows obvious stretching, with circular elements appearing oval-shaped and text in the image appearing compressed vertically.
**Recommended Fix**: See fix-recommendations.md #4
---
### 5. Missing Error State Indication
**Severity**: High
**Category**: Accessibility / UX
**Viewport**: All viewports
**Screenshot**: `screenshots/current/contact-form-validation-errors-2025-11-01T16-29-45.png`
**Description**:
Form validation errors are indicated only by a red border around inputs. No error text is visible, and there's no icon or other non-color indicator.
**User Impact**:
Users relying on screen readers won't hear error messages. Color-blind users may not notice the red border. Error messages are essential for understanding what went wrong.
**Visual Evidence**:
Screenshot shows inputs with red borders but no visible error text below them explaining what the error is.
**Recommended Fix**: See fix-recommendations.md #5
---
## Medium Priority Issues (P2)
### 6. Inconsistent Card Heights in Feature Section
**Severity**: Medium
**Category**: Layout
**Viewport**: Desktop (1280x720)
**Screenshot**: `screenshots/current/homepage-initial-load-2025-11-01T16-27-18.png`
**Description**:
Feature cards have varying heights due to different content lengths. The grid layout doesn't maintain consistent card heights, creating a jagged appearance.
**User Impact**:
Visually inconsistent and less professional. Makes the page feel unpolished.
**Visual Evidence**:
Three feature cards visible - first card is noticeably taller than the second, and third is somewhere in between, creating uneven rows.
**Recommended Fix**: See fix-recommendations.md #6
---
### 7. Footer Links Too Close Together on Mobile
**Severity**: Medium
**Category**: Responsive / Touch Targets
**Viewport**: Mobile (375x667)
**Screenshot**: `screenshots/current/homepage-responsive-mobile-2025-11-01T16-27-52.png`
**Description**:
Footer navigation links are spaced only 4-6px apart vertically on mobile, making them difficult to tap accurately. WCAG 2.1 recommends minimum 44x44px touch targets with 8px spacing.
**User Impact**:
Users frequently mis-tap links, requiring multiple attempts to navigate. Particularly frustrating for users with motor impairments or large fingers.
**Visual Evidence**:
Footer links appear very close together with minimal spacing between each link.
**Recommended Fix**: See fix-recommendations.md #7
---
## Low Priority Issues (P3)
### 8. Heading Sizes Not Progressively Smaller
**Severity**: Low
**Category**: Typography / Visual Hierarchy
**Viewport**: All viewports
**Screenshot**: `screenshots/current/about-page-loaded-2025-11-01T16-28-05.png`
**Description**:
H2 and H3 headings appear to be the same size (approximately 20px), reducing visual hierarchy and making it harder to scan content structure.
**User Impact**:
Minor impact on content scanability. Users may not immediately recognize the content hierarchy.
**Visual Evidence**:
Page title (H1) is clearly larger, but H2 section headings and H3 subsection headings are visually identical in size.
**Recommended Fix**: See fix-recommendations.md #8
---
## Summary Statistics
### By Severity
| Severity | Count | Percentage |
|----------|-------|------------|
| Critical | 2 | 25% |
| High | 3 | 37.5% |
| Medium | 2 | 25% |
| Low | 1 | 12.5% |
### By Category
| Category | Count |
|----------------|-------|
| Layout | 4 |
| Accessibility | 2 |
| Typography | 1 |
| Responsive | 1 |
### By Viewport
| Viewport | Issues |
|----------|--------|
| Mobile | 5 |
| Tablet | 1 |
| Desktop | 1 |
| All | 3 |
---
## Recommended Actions
1. **Immediate (Critical)**:
- Fix form label contrast (#1)
- Fix button text truncation on mobile (#2)
2. **This Sprint (High)**:
- Fix navigation overlap on tablet (#3)
- Fix hero image stretching (#4)
- Add visible error messages (#5)
3. **Next Iteration (Medium)**:
- Standardize feature card heights (#6)
- Increase footer link spacing on mobile (#7)
4. **Backlog (Low)**:
- Adjust heading size hierarchy (#8)
---
## Testing Recommendations
After fixes are implemented:
1. Re-run Playwright test suite to capture updated screenshots
2. Compare new screenshots with current baseline
3. Run accessibility audit with axe-core
4. Test on real devices (iOS Safari, Android Chrome)
5. Validate color contrast with WebAIM tool
6. Test with screen reader (VoiceOver, NVDA)
---
**Generated by**: playwright-e2e-automation skill
**Analysis Method**: LLM-powered visual screenshot analysis
**Reference Guides**: accessibility-checks.md, common-ui-bugs.md

View File

@@ -0,0 +1,134 @@
# CI/CD Integration
## GitHub Actions Example
```yaml
name: Playwright E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test:e2e
- name: Upload screenshots
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-screenshots
path: screenshots/
- name: Upload HTML report
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
```
## Baseline Management in CI
### 1. Store baselines in repository
```bash
git add screenshots/baselines/
git commit -m "chore: update visual regression baselines"
```
### 2. Update baselines on approval
1. Run tests locally: `npm run test:e2e`
2. Review diffs: `npx playwright show-report`
3. Update baselines: `npm run test:e2e:update-snapshots`
4. Commit updated baselines
### 3. Fail CI on visual regressions
- Configure threshold in playwright.config.ts
- Tests fail if diffs exceed threshold
- Review in CI artifacts before merging
## GitLab CI Example
```yaml
e2e-tests:
image: mcr.microsoft.com/playwright:v1.40.0-jammy
stage: test
script:
- npm ci
- npm run test:e2e
artifacts:
when: always
paths:
- screenshots/
- playwright-report/
expire_in: 7 days
```
## CircleCI Example
```yaml
version: 2.1
jobs:
e2e-tests:
docker:
- image: mcr.microsoft.com/playwright:v1.40.0-jammy
steps:
- checkout
- run: npm ci
- run: npm run test:e2e
- store_artifacts:
path: screenshots
- store_artifacts:
path: playwright-report
```
## Best Practices
### Screenshot Artifact Storage
- Always upload screenshots as artifacts
- Keep artifacts for at least 7 days
- Consider cloud storage for long-term retention
### Parallel Execution
```yaml
# Run tests across multiple shards
- name: Run Playwright tests
run: npx playwright test --shard=${{ matrix.shard }}/${{ strategy.job-total }}
strategy:
matrix:
shard: [1, 2, 3, 4]
```
### Caching
```yaml
- name: Cache Playwright browsers
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('package-lock.json') }}
```
### Notifications
Configure notifications for test failures:
- Slack integration
- Email alerts
- PR comments with screenshot diffs

View File

@@ -0,0 +1,186 @@
# Troubleshooting Guide
## Framework Version Errors
### Tailwind CSS v4 Syntax Mismatch
**Symptom**: Console error "Cannot apply unknown utility class" or "Utilities must be known at build time"
**Cause**: Tailwind v4 installed but CSS uses old v3 `@tailwind` directive syntax
**Root Cause**: Breaking change in Tailwind v4 - changed from `@tailwind` to `@import` syntax
**Detection**: Pre-flight health check catches this before running tests
**Auto-fix Available**: Yes - skill detects version and uses correct template
**Manual Fix**:
```css
// Old (v3):
@tailwind base;
@tailwind components;
@tailwind utilities;
// New (v4):
@import "tailwindcss";
```
**Also Update**: `postcss.config.js` - change `tailwindcss: {}` to `'@tailwindcss/postcss': {}`
**Prevention**: Skill consults `data/framework-versions.yaml` and selects appropriate template
**Documentation**: https://tailwindcss.com/docs/upgrade-guide
---
### PostCSS Plugin Not Found
**Symptom**: Build error "Plugin tailwindcss not found" or "Cannot find module 'tailwindcss'"
**Cause**: Tailwind v4 renamed PostCSS plugin but config uses old name
**Root Cause**: PostCSS plugin changed from `tailwindcss` to `@tailwindcss/postcss` in v4
**Detection**: Pre-flight check or build error
**Auto-fix Available**: Yes - version detection selects correct PostCSS template
**Manual Fix**:
```javascript
// postcss.config.js
// Old (v3):
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
// New (v4):
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};
```
**Verification**: Run `npm list @tailwindcss/postcss` to confirm installation
**Prevention**: Skill uses `templates/configs/postcss-tailwind-v4.js` for Tailwind v4
---
### Version Incompatibility Warning
**Symptom**: Skill warns "Unknown version detected" or "Version outside known ranges"
**Cause**: Framework version not in compatibility database
**Impact**: Skill may use outdated templates or incorrect syntax
**Solution**:
1. Check `data/framework-versions.yaml` for supported versions
2. If version is newer, skill uses latest known template (may need manual adjustment)
3. If version is older, skill may suggest upgrading
**Reporting**: Please report unknown versions as GitHub issues to improve skill
**Workaround**: Manually specify template paths if needed
---
## Common Issues
### Application not detected
**Cause**: Unrecognized framework or missing package.json
**Solution**: Ask user to specify app type and dev server command manually
**Fallback**: Use generic static site configuration
---
### Dev server not running
**Cause**: Application not started before running tests
**Solution**: Attempt to start server automatically using detected script (npm run dev)
**Fallback**: Prompt user to start server manually
---
### Playwright installation fails
**Cause**: Network issues, permissions, incompatible Node version
**Solution**:
- Check Node version (>=16)
- Retry with --force
- Suggest manual installation
**Debugging**: Show full error output, check npm logs
---
### Screenshot capture fails
**Cause**: Timeout waiting for page load, element not found, navigation error
**Solution**:
- Increase timeout
- Add explicit waits
- Capture partial screenshot on failure
**Recovery**: Continue with other tests, report failure with details
---
### No baselines exist for comparison
**Cause**: First test run, baselines deleted
**Solution**: Current screenshots become baselines automatically
**Message**: "No baselines found. Current screenshots saved as baselines."
---
### Visual analysis fails
**Cause**: LLM API error, screenshot file corruption, unsupported format
**Solution**:
- Retry analysis
- Skip corrupted images
- Validate PNG format
**Fallback**: Provide raw screenshots for manual inspection
---
## Performance Characteristics
### Execution Times (Typical React App)
| Phase | Time |
|-------|------|
| Application detection | ~5 seconds |
| Playwright installation | ~2-3 minutes (one-time) |
| Configuration generation | ~10 seconds |
| Test generation | ~30 seconds |
| Test execution (5 tests) | ~30-60 seconds |
| Screenshot capture | ~1-2 seconds per screenshot |
| Visual analysis (10 screenshots) | ~1-2 minutes |
| Regression comparison | ~10 seconds |
| Fix generation | ~30 seconds |
**Total end-to-end time**: ~5-8 minutes (excluding Playwright install)
### Resource Usage
- **Disk space**: ~500MB (Playwright browsers)
- **Memory**: ~500MB during test execution
- **Screenshots**: ~1-2MB per full-page screenshot

View File

@@ -0,0 +1,294 @@
# Helper Scripts
These scripts provide reference implementations for automating Playwright e2e testing workflows. They are meant to guide LLM implementation, not to be executed directly.
## Script Overview
### 1. `setup-playwright.sh`
**Purpose**: Automate Playwright installation and initial configuration
**What it does**:
- Checks Node.js version compatibility
- Installs Playwright via npm
- Installs browser binaries (Chromium, Firefox, WebKit)
- Generates initial playwright.config.ts
- Creates test directory structure
**Usage Pattern** (for LLM to implement):
```bash
#!/bin/bash
# Detect if Playwright is already installed
# If not, run: npm init playwright@latest -- --yes
# Create directory structure
# Generate config based on detected framework
```
### 2. `detect-framework.ts`
**Purpose**: Identify application framework and configuration
**What it does**:
- Reads package.json dependencies
- Checks for config files (vite.config.ts, next.config.js, etc.)
- Determines dev server command and port
- Returns framework metadata for test generation
**Usage Pattern**:
```typescript
// Read package.json
// Match against patterns in framework-detection-patterns.yaml
// Return framework object with baseURL, devCommand, etc.
```
### 3. `generate-tests.ts`
**Purpose**: Generate Playwright test specifications from templates
**What it does**:
- Takes framework metadata and user journeys as input
- Populates test templates with appropriate selectors
- Adds screenshot capture points
- Generates Page Object Models
- Creates test helper utilities
**Usage Pattern**:
```typescript
// Load templates from ../templates/
// Populate with framework-specific values
// Generate .spec.ts files in tests/specs/
// Generate .page.ts files in tests/pages/
```
### 4. `capture-screenshots.ts`
**Purpose**: Execute tests and capture organized screenshots
**What it does**:
- Runs Playwright test suite
- Captures screenshots at defined points
- Organizes by test name, viewport, timestamp
- Generates metadata JSON for each screenshot
**Usage Pattern**:
```typescript
// Run: npx playwright test
// Hook into test lifecycle to capture screenshots
// Save to screenshots/current/{test-name}-{viewport}-{state}-{timestamp}.png
// Generate screenshots/metadata.json
```
### 5. `analyze-visual.ts`
**Purpose**: LLM-powered visual analysis of screenshots
**What it does**:
- Reads all screenshots from current run
- Sends each to LLM vision API with analysis prompts
- Categorizes findings (UI bugs, accessibility, layout)
- Generates structured issue reports
- Assigns severity levels
**Usage Pattern**:
```typescript
// Read screenshots/current/*.png
// For each screenshot:
// - Send to LLM with prompts from ../data/common-ui-bugs.md
// - Extract identified issues
// - Categorize and rate severity
// Generate visual-analysis-report.md
```
### 6. `compare-regression.ts`
**Purpose**: Compare current screenshots with baselines
**What it does**:
- Loads baseline screenshots
- Performs pixel-level comparison with current screenshots
- Generates diff images highlighting changes
// Calculates difference percentages
- Classifies changes (expected, suspicious, critical)
**Usage Pattern**:
```typescript
// Load screenshots/baselines/*.png
// Load screenshots/current/*.png
// Use Playwright's comparison utilities
// Generate screenshots/diffs/*.png
// Create regression-report.md
```
### 7. `generate-fixes.ts`
**Purpose**: Generate code fix recommendations from visual issues
**What it does**:
- Takes visual analysis results as input
- Maps issues to source code locations
- Generates specific fix recommendations
- Provides before/after code snippets
- Prioritizes by severity
**Usage Pattern**:
```typescript
// Read visual-analysis-report.md
// For each issue:
// - Infer likely source file (component, styles)
// - Generate fix recommendation
// - Provide file:line reference
// - Create code snippet
// Generate fix-recommendations.md
```
### 8. `export-test-suite.ts`
**Purpose**: Package test suite for production use
**What it does**:
- Copies generated tests to project's tests/ directory
- Adds npm scripts to package.json
- Generates README with usage instructions
- Creates CI/CD workflow examples
- Validates everything works
**Usage Pattern**:
```typescript
// Copy tests/specs/ to project/tests/e2e/
// Copy tests/pages/ to project/tests/e2e/pages/
// Update project/package.json with test scripts
// Generate project/tests/e2e/README.md
// Validate: run tests to ensure they work
```
## Implementation Guidelines
### For LLMs Implementing These Scripts
**Don't execute scripts directly** - Instead, implement the logic inline:
```typescript
// Example: Instead of calling setup-playwright.sh
// Implement the logic directly in your response:
async function setupPlaywright() {
// 1. Check Node version
const nodeVersion = await execCommand('node --version');
if (!nodeVersion.startsWith('v16') && !nodeVersion.startsWith('v18')) {
throw new Error('Node 16+ required');
}
// 2. Install Playwright
await execCommand('npm init playwright@latest -- --yes');
// 3. Create directory structure
await createDirectories([
'tests/setup',
'tests/pages',
'tests/specs',
'tests/utils',
'screenshots/baselines',
'screenshots/current',
'screenshots/diffs',
]);
// 4. Generate config (using templates)
const config = await generatePlaywrightConfig(framework);
await writeFile('playwright.config.ts', config);
}
```
### Script Dependencies
All scripts should be standalone reference implementations:
- No external dependencies beyond Playwright
- Clear, commented code
- Error handling included
- TypeScript for type safety
### Data Flow Between Scripts
```
1. detect-framework.ts
↓ (framework metadata)
2. setup-playwright.sh
↓ (Playwright installed, config generated)
3. generate-tests.ts
↓ (test files created)
4. capture-screenshots.ts
↓ (screenshots captured)
5. analyze-visual.ts
↓ (issues identified)
6. compare-regression.ts
↓ (regressions detected)
7. generate-fixes.ts
↓ (fix recommendations created)
8. export-test-suite.ts
↓ (production-ready test suite)
```
## Testing the Scripts
To validate script logic (for human developers):
```bash
# 1. Framework detection
npx ts-node scripts/detect-framework.ts
# Should output: { framework: 'react_vite', baseURL: 'http://localhost:5173', ... }
# 2. Test generation
npx ts-node scripts/generate-tests.ts --framework react_vite
# Should create: tests/specs/*.spec.ts
# 3. Screenshot capture
npx ts-node scripts/capture-screenshots.ts
# Should create: screenshots/current/*.png
# 4. Visual analysis
npx ts-node scripts/analyze-visual.ts
# Should create: visual-analysis-report.md
# 5. Regression comparison
npx ts-node scripts/compare-regression.ts
# Should create: screenshots/diffs/*.png, regression-report.md
# 6. Fix generation
npx ts-node scripts/generate-fixes.ts
# Should create: fix-recommendations.md
# 7. Export
npx ts-node scripts/export-test-suite.ts
# Should copy files to project/tests/e2e/
```
## Error Handling
All scripts should handle common errors:
```typescript
try {
// Script logic
} catch (error) {
if (error.code === 'ENOENT') {
console.error('File not found. Check paths.');
} else if (error.message.includes('npm')) {
console.error('npm command failed. Check if npm is installed.');
} else {
console.error('Unexpected error:', error.message);
}
process.exit(1);
}
```
## Performance Considerations
- **Parallel execution**: Run tests in parallel where possible
- **Incremental screenshots**: Only capture screenshots for changed tests
- **Caching**: Cache framework detection results
- **Batch processing**: Process multiple screenshots in batches for LLM analysis
## Future Enhancements
Potential scripts to add:
- `update-baselines.ts` - Interactive baseline approval
- `optimize-selectors.ts` - Suggest better selectors
- `generate-performance-tests.ts` - Add Core Web Vitals checks
- `create-ci-config.ts` - Generate GitHub Actions workflow
- `analyze-flaky-tests.ts` - Detect and fix flaky tests
---
**Remember**: These are reference implementations to guide LLM development. The actual execution happens through LLM-generated code, not by running these scripts directly.

View File

@@ -0,0 +1,16 @@
/**
* PostCSS Configuration for Tailwind CSS v3
*
* Compatible with: Tailwind CSS >=3.0.0 <4.0.0
* Generated by: playwright-e2e-automation skill
*/
export default {
plugins: {
// Tailwind CSS v3 uses 'tailwindcss' as plugin name
tailwindcss: {},
// Autoprefixer adds vendor prefixes for browser compatibility
autoprefixer: {},
},
};

View File

@@ -0,0 +1,24 @@
/**
* PostCSS Configuration for Tailwind CSS v4
*
* Compatible with: Tailwind CSS >=4.0.0
*
* Breaking changes from v3:
* - Plugin name changed from 'tailwindcss' to '@tailwindcss/postcss'
* - Improved performance with new architecture
* - Better integration with build tools
*
* Migration guide: https://tailwindcss.com/docs/upgrade-guide
* Generated by: playwright-e2e-automation skill
*/
export default {
plugins: {
// Tailwind CSS v4 uses '@tailwindcss/postcss' as plugin name
// This is a BREAKING CHANGE from v3
'@tailwindcss/postcss': {},
// Autoprefixer adds vendor prefixes for browser compatibility
autoprefixer: {},
},
};

View File

@@ -0,0 +1,20 @@
/**
* Tailwind CSS v3 Syntax
*
* This template uses the classic @tailwind directive syntax
* Compatible with: Tailwind CSS >=3.0.0 <4.0.0
*
* Generated by: playwright-e2e-automation skill
*/
/* Base styles, CSS resets, and browser normalization */
@tailwind base;
/* Component classes and utilities */
@tailwind components;
/* Utility classes (spacing, colors, typography, etc.) */
@tailwind utilities;
/* ========== CUSTOM STYLES ========== */
/* Add your custom CSS below this line */

View File

@@ -0,0 +1,38 @@
/**
* Tailwind CSS v4 Syntax
*
* This template uses the new @import syntax introduced in Tailwind v4
* Compatible with: Tailwind CSS >=4.0.0
*
* Breaking changes from v3:
* - @tailwind directives replaced with single @import
* - PostCSS plugin changed to @tailwindcss/postcss
* - Improved performance and smaller bundle size
*
* Migration guide: https://tailwindcss.com/docs/upgrade-guide
* Generated by: playwright-e2e-automation skill
*/
/* Import Tailwind CSS (base, components, utilities all included) */
@import "tailwindcss";
/* ========== CUSTOM STYLES ========== */
/* Add your custom CSS below this line */
/* Example: Custom utility classes using @layer */
/*
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
*/
/* Example: Custom components using @layer */
/*
@layer components {
.btn-primary {
@apply px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700;
}
}
*/

View File

@@ -0,0 +1,171 @@
/**
* Vanilla CSS Template (No CSS Framework)
*
* This template is used when no CSS framework is detected
* or when Tailwind CSS is not installed.
*
* Generated by: playwright-e2e-automation skill
*/
/* ========== CSS RESET ========== */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ========== BASE STYLES ========== */
:root {
/* Color palette */
--color-primary: #3b82f6;
--color-primary-dark: #2563eb;
--color-text: #1f2937;
--color-text-light: #6b7280;
--color-background: #ffffff;
--color-border: #e5e7eb;
/* Spacing scale */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
/* Typography */
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: 'Courier New', monospace;
}
body {
font-family: var(--font-sans);
color: var(--color-text);
background-color: var(--color-background);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ========== TYPOGRAPHY ========== */
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 700;
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
h1 {
font-size: 2.25rem;
}
h2 {
font-size: 1.875rem;
}
h3 {
font-size: 1.5rem;
}
h4 {
font-size: 1.25rem;
}
p {
margin-bottom: var(--spacing-md);
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
color: var(--color-primary-dark);
text-decoration: underline;
}
/* ========== FORM ELEMENTS ========== */
button,
input,
select,
textarea {
font-family: inherit;
font-size: 100%;
}
button {
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--color-primary);
color: white;
border: none;
border-radius: 0.375rem;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--color-primary-dark);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
input,
textarea,
select {
padding: var(--spacing-sm);
border: 1px solid var(--color-border);
border-radius: 0.375rem;
width: 100%;
}
input:focus,
textarea:focus,
select:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* ========== UTILITY CLASSES ========== */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.text-center {
text-align: center;
}
.mt-1 {
margin-top: var(--spacing-xs);
}
.mt-2 {
margin-top: var(--spacing-sm);
}
.mt-3 {
margin-top: var(--spacing-md);
}
.mt-4 {
margin-top: var(--spacing-lg);
}
.mb-1 {
margin-bottom: var(--spacing-xs);
}
.mb-2 {
margin-bottom: var(--spacing-sm);
}
.mb-3 {
margin-bottom: var(--spacing-md);
}
.mb-4 {
margin-bottom: var(--spacing-lg);
}
/* ========== CUSTOM STYLES ========== */
/* Add your custom CSS below this line */

View File

@@ -0,0 +1,127 @@
import { chromium, FullConfig } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
/**
* Global Setup
* Runs once before all tests
*
* Responsibilities:
* - Ensure dev server is ready
* - Create screenshot directories
* - Perform any global authentication
* - Set up test database (if needed)
*/
async function globalSetup(config: FullConfig) {
console.log('\n🚀 Starting global setup...\n');
// 1. Create screenshot directories
createScreenshotDirectories();
// 2. Verify dev server is accessible (webServer config handles startup)
const baseURL = config.projects[0].use.baseURL || 'http://localhost:{{PORT}}';
await verifyServer(baseURL);
// 3. Optional: Perform global authentication
// await performAuthentication(config);
console.log('✅ Global setup complete\n');
}
/**
* Create necessary screenshot directories
*/
function createScreenshotDirectories() {
const directories = [
'screenshots/current',
'screenshots/baselines',
'screenshots/diffs',
'test-results',
];
for (const dir of directories) {
const dirPath = path.join(process.cwd(), dir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`📁 Created directory: ${dir}`);
}
}
}
/**
* Verify dev server is accessible
*/
async function verifyServer(baseURL: string, maxRetries = 30) {
console.log(`🔍 Verifying server at ${baseURL}...`);
for (let i = 0; i < maxRetries; i++) {
try {
const browser = await chromium.launch();
const page = await browser.newPage();
const response = await page.goto(baseURL, { timeout: 5000 });
if (response && response.ok()) {
console.log(`✅ Server is ready at ${baseURL}`);
await browser.close();
return;
}
await browser.close();
} catch (error) {
// Server not ready yet, wait and retry
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, 1000));
} else {
throw new Error(
`Server at ${baseURL} is not accessible after ${maxRetries} attempts`
);
}
}
}
}
/**
* Optional: Perform global authentication
* Saves authentication state to be reused across all tests
*/
async function performAuthentication(config: FullConfig) {
// Only run if authentication is needed
if (!process.env.AUTH_USERNAME || !process.env.AUTH_PASSWORD) {
console.log('⏭️ Skipping authentication (no credentials provided)');
return;
}
console.log('🔐 Performing global authentication...');
const baseURL = config.projects[0].use.baseURL || 'http://localhost:{{PORT}}';
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
// Navigate to login page
await page.goto(`${baseURL}/login`);
// Fill in credentials
await page.getByLabel('Username').fill(process.env.AUTH_USERNAME);
await page.getByLabel('Password').fill(process.env.AUTH_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();
// Wait for successful login (adjust selector as needed)
await page.waitForURL(`${baseURL}/dashboard`, { timeout: 10000 });
// Save authentication state
await context.storageState({ path: 'auth.json' });
console.log('✅ Authentication successful, state saved to auth.json');
} catch (error) {
console.error('❌ Authentication failed:', error);
throw error;
} finally {
await browser.close();
}
}
export default globalSetup;

View File

@@ -0,0 +1,64 @@
import { FullConfig } from '@playwright/test';
import { generateManifest } from '../utils/screenshot-helper';
/**
* Global Teardown
* Runs once after all tests complete
*
* Responsibilities:
* - Generate screenshot manifest
* - Clean up temporary files (if needed)
* - Generate summary report
* - Perform any cleanup tasks
*/
async function globalTeardown(config: FullConfig) {
console.log('\n🏁 Starting global teardown...\n');
// 1. Generate screenshot manifest
try {
generateManifest();
} catch (error) {
console.error('⚠️ Failed to generate screenshot manifest:', error);
}
// 2. Generate test summary
generateSummary();
// 3. Optional: Clean up temporary files
// cleanupTempFiles();
console.log('\n✅ Global teardown complete\n');
}
/**
* Generate test execution summary
*/
function generateSummary() {
console.log('\n📊 Test Execution Summary:');
console.log('─'.repeat(50));
// Test results are available through Playwright's built-in reporters
// This is just a placeholder for custom summary logic
console.log('✅ Check playwright-report/ for detailed results');
console.log('✅ Screenshots available in screenshots/current/');
console.log('✅ Test results available in test-results/');
console.log('\n💡 Next steps:');
console.log(' 1. Review screenshots for visual issues');
console.log(' 2. Compare with baselines if available');
console.log(' 3. Run visual analysis: npm run analyze:visual');
console.log(' 4. Generate fix recommendations if issues found');
}
/**
* Optional: Clean up temporary files
*/
function cleanupTempFiles() {
// Add cleanup logic here if needed
// For example: remove old screenshots, clear cache, etc.
console.log('🧹 Cleaning up temporary files...');
}
export default globalTeardown;

View File

@@ -0,0 +1,84 @@
import { Page, Locator } from '@playwright/test';
/**
* {{PAGE_NAME}} Page Object Model
*
* Represents: {{PAGE_DESCRIPTION}}
* URL: {{PAGE_URL}}
* Generated: {{GENERATED_DATE}}
*/
export class {{PAGE_CLASS_NAME}} {
readonly page: Page;
// Locators - Using semantic selectors (getByRole, getByLabel, getByText)
{{#LOCATORS}}
readonly {{LOCATOR_NAME}}: Locator;
{{/LOCATORS}}
constructor(page: Page) {
this.page = page;
// Initialize locators
{{#LOCATORS}}
this.{{LOCATOR_NAME}} = page.{{SELECTOR}};
{{/LOCATORS}}
}
/**
* Navigate to this page
*/
async goto() {
await this.page.goto('{{PAGE_URL}}');
await this.page.waitForLoadState('networkidle');
}
/**
* Wait for page to be ready
*/
async waitForReady() {
await this.page.waitForLoadState('domcontentloaded');
{{#READY_INDICATORS}}
await this.{{INDICATOR}}.waitFor({ state: 'visible' });
{{/READY_INDICATORS}}
}
{{#METHODS}}
/**
* {{METHOD_DESCRIPTION}}
{{#PARAMS}}
* @param {{PARAM_NAME}} - {{PARAM_DESCRIPTION}}
{{/PARAMS}}
*/
async {{METHOD_NAME}}({{PARAMS_SIGNATURE}}) {
{{METHOD_BODY}}
}
{{/METHODS}}
/**
* Get page title
*/
async getTitle(): Promise<string> {
return await this.page.title();
}
/**
* Get current URL
*/
async getCurrentUrl(): Promise<string> {
return this.page.url();
}
/**
* Take screenshot of this page
* @param name - Screenshot filename
*/
async screenshot(name: string) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
await this.page.screenshot({
path: `screenshots/current/{{PAGE_NAME_KEBAB}}-${name}-${timestamp}.png`,
fullPage: true,
});
}
}

View File

@@ -0,0 +1,139 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Playwright Configuration
* Generated by playwright-e2e-automation skill
*
* Framework: {{FRAMEWORK_NAME}}
* Base URL: {{BASE_URL}}
* Generated: {{GENERATED_DATE}}
*/
export default defineConfig({
testDir: './tests/specs',
/**
* Maximum time one test can run for
*/
timeout: {{TIMEOUT}},
/**
* Test execution settings
*/
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
/**
* Reporter configuration
* CI: GitHub Actions reporter
* Local: HTML reporter with screenshots
*/
reporter: process.env.CI
? 'github'
: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'test-results/results.json' }],
],
/**
* Shared settings for all projects
*/
use: {
/* Base URL for navigation */
baseURL: '{{BASE_URL}}',
/* Collect trace on first retry */
trace: 'on-first-retry',
/* Screenshot settings */
screenshot: {
mode: 'only-on-failure',
fullPage: true,
},
/* Video settings */
video: 'retain-on-failure',
/* Maximum time each action can take */
actionTimeout: 10000,
/* Navigation timeout */
navigationTimeout: 30000,
},
/**
* Browser and device configurations
*/
projects: [
{
name: 'chromium-desktop',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1280, height: 720 },
},
},
{
name: 'firefox-desktop',
use: {
...devices['Desktop Firefox'],
viewport: { width: 1280, height: 720 },
},
},
{
name: 'webkit-desktop',
use: {
...devices['Desktop Safari'],
viewport: { width: 1280, height: 720 },
},
},
{
name: 'mobile-chrome',
use: {
...devices['Pixel 5'],
},
},
{
name: 'mobile-safari',
use: {
...devices['iPhone 13'],
},
},
{
name: 'tablet',
use: {
...devices['iPad Pro'],
},
},
],
/**
* Web server configuration
* Starts dev server before running tests
*/
webServer: {
command: '{{DEV_SERVER_COMMAND}}',
url: '{{BASE_URL}}',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
stdout: 'pipe',
stderr: 'pipe',
},
/**
* Output directories
*/
outputDir: 'test-results',
/**
* Global setup/teardown
*/
globalSetup: require.resolve('./tests/setup/global-setup.ts'),
globalTeardown: require.resolve('./tests/setup/global-teardown.ts'),
});

View File

@@ -0,0 +1,236 @@
import { Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
/**
* Screenshot Helper Utilities
* Provides consistent screenshot capture with metadata
*/
export interface ScreenshotMetadata {
path: string;
context: string;
timestamp: string;
viewport: {
width: number;
height: number;
};
url: string;
testName?: string;
}
/**
* Capture screenshot with context metadata
*
* @param page - Playwright page object
* @param name - Screenshot name (will be kebab-cased)
* @param context - Description of what the screenshot shows
* @returns Metadata about the captured screenshot
*/
export async function captureWithContext(
page: Page,
name: string,
context: string
): Promise<ScreenshotMetadata> {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const viewport = page.viewportSize() || { width: 1280, height: 720 };
const url = page.url();
// Ensure screenshots directory exists
const screenshotDir = path.join(process.cwd(), 'screenshots', 'current');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir, { recursive: true });
}
// Generate filename
const filename = `${name}-${timestamp}.png`;
const screenshotPath = path.join(screenshotDir, filename);
// Wait for network idle before capturing
await page.waitForLoadState('networkidle');
// Capture screenshot
await page.screenshot({
path: screenshotPath,
fullPage: true,
});
// Create metadata
const metadata: ScreenshotMetadata = {
path: screenshotPath,
context,
timestamp: new Date().toISOString(),
viewport,
url,
testName: process.env.PLAYWRIGHT_TEST_NAME,
};
// Save metadata alongside screenshot
const metadataPath = screenshotPath.replace('.png', '.json');
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
console.log(`📸 Screenshot captured: ${filename}`);
console.log(` Context: ${context}`);
return metadata;
}
/**
* Capture element screenshot with context
*
* @param page - Playwright page object
* @param selector - Element selector
* @param name - Screenshot name
* @param context - Description
*/
export async function captureElement(
page: Page,
selector: string,
name: string,
context: string
): Promise<ScreenshotMetadata> {
const element = page.locator(selector);
await element.waitFor({ state: 'visible' });
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const viewport = page.viewportSize() || { width: 1280, height: 720 };
const url = page.url();
const screenshotDir = path.join(process.cwd(), 'screenshots', 'current');
if (!fs.existsSync(screenshotDir)) {
fs.mkdirSync(screenshotDir, { recursive: true });
}
const filename = `${name}-element-${timestamp}.png`;
const screenshotPath = path.join(screenshotDir, filename);
await element.screenshot({
path: screenshotPath,
});
const metadata: ScreenshotMetadata = {
path: screenshotPath,
context: `${context} (element: ${selector})`,
timestamp: new Date().toISOString(),
viewport,
url,
testName: process.env.PLAYWRIGHT_TEST_NAME,
};
const metadataPath = screenshotPath.replace('.png', '.json');
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
console.log(`📸 Element screenshot captured: ${filename}`);
return metadata;
}
/**
* Capture comparison screenshots (before/after)
*
* @param page - Playwright page object
* @param name - Base name for screenshots
* @param actionCallback - Action to perform between screenshots
*/
export async function captureComparison(
page: Page,
name: string,
actionCallback: () => Promise<void>
): Promise<{ before: ScreenshotMetadata; after: ScreenshotMetadata }> {
const before = await captureWithContext(page, `${name}-before`, 'State before action');
await actionCallback();
const after = await captureWithContext(page, `${name}-after`, 'State after action');
return { before, after };
}
/**
* Capture screenshots across multiple viewports
*
* @param page - Playwright page object
* @param name - Base name for screenshots
* @param viewports - Array of viewport configurations
*/
export async function captureViewports(
page: Page,
name: string,
viewports: Array<{ name: string; width: number; height: number }>
): Promise<ScreenshotMetadata[]> {
const screenshots: ScreenshotMetadata[] = [];
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
// Wait for responsive changes to settle
await page.waitForTimeout(500);
const metadata = await captureWithContext(
page,
`${name}-${viewport.name}`,
`${viewport.width}x${viewport.height} viewport`
);
screenshots.push(metadata);
}
return screenshots;
}
/**
* Generate screenshot manifest
* Collects all screenshots and their metadata into a single manifest file
*/
export function generateManifest(): void {
const screenshotDir = path.join(process.cwd(), 'screenshots', 'current');
if (!fs.existsSync(screenshotDir)) {
console.log('No screenshots directory found');
return;
}
const files = fs.readdirSync(screenshotDir);
const metadataFiles = files.filter((f) => f.endsWith('.json'));
const manifest = metadataFiles.map((file) => {
const content = fs.readFileSync(path.join(screenshotDir, file), 'utf-8');
return JSON.parse(content);
});
const manifestPath = path.join(screenshotDir, 'manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
console.log(`\n📋 Screenshot manifest generated: ${manifestPath}`);
console.log(` Total screenshots: ${manifest.length}`);
}
/**
* Compare screenshot with baseline
*
* @param currentPath - Path to current screenshot
* @param baselinePath - Path to baseline screenshot
* @param diffPath - Path to save diff image
* @param threshold - Difference threshold (0-1, default 0.2 = 20%)
*/
export async function compareWithBaseline(
currentPath: string,
baselinePath: string,
diffPath: string,
threshold: number = 0.2
): Promise<{ match: boolean; diffPercentage: number }> {
// Note: This requires pixelmatch or Playwright's built-in comparison
// For now, this is a placeholder showing the interface
console.log(`🔍 Comparing screenshots:`);
console.log(` Current: ${currentPath}`);
console.log(` Baseline: ${baselinePath}`);
// Implementation would use Playwright's toHaveScreenshot comparison
// or a library like pixelmatch for pixel-level comparison
return {
match: true,
diffPercentage: 0,
};
}

View File

@@ -0,0 +1,120 @@
import { test, expect } from '@playwright/test';
import { {{PAGE_OBJECT_CLASS}} } from '../pages/{{PAGE_OBJECT_FILE}}';
import { captureWithContext } from '../utils/screenshot-helper';
/**
* {{TEST_SUITE_NAME}}
*
* Tests: {{TEST_DESCRIPTION}}
* Generated: {{GENERATED_DATE}}
*/
test.describe('{{TEST_SUITE_NAME}}', () => {
let page: {{PAGE_OBJECT_CLASS}};
test.beforeEach(async ({ page: testPage }) => {
page = new {{PAGE_OBJECT_CLASS}}(testPage);
await page.goto();
// Capture initial page load
await captureWithContext(
testPage,
'{{TEST_SUITE_NAME_KEBAB}}-initial-load',
'Page loaded successfully'
);
});
{{#TESTS}}
test('{{TEST_NAME}}', async ({ page: testPage }) => {
// Arrange: {{ARRANGE_DESCRIPTION}}
{{#ARRANGE_STEPS}}
{{STEP}}
{{/ARRANGE_STEPS}}
// Capture pre-action state
await captureWithContext(
testPage,
'{{TEST_SUITE_NAME_KEBAB}}-{{TEST_NAME_KEBAB}}-before',
'Before {{ACTION_DESCRIPTION}}'
);
// Act: {{ACTION_DESCRIPTION}}
{{ACTION_CODE}}
// Capture post-action state
await captureWithContext(
testPage,
'{{TEST_SUITE_NAME_KEBAB}}-{{TEST_NAME_KEBAB}}-after',
'After {{ACTION_DESCRIPTION}}'
);
// Assert: {{ASSERT_DESCRIPTION}}
{{#ASSERTIONS}}
await expect({{SELECTOR}}).{{MATCHER}};
{{/ASSERTIONS}}
// Final state screenshot
await captureWithContext(
testPage,
'{{TEST_SUITE_NAME_KEBAB}}-{{TEST_NAME_KEBAB}}-final',
'{{FINAL_STATE_DESCRIPTION}}'
);
});
{{/TESTS}}
test('should not have accessibility violations', async ({ page: testPage }) => {
// Run accessibility audit
const AxeBuilder = (await import('@axe-core/playwright')).default;
const accessibilityScanResults = await new AxeBuilder({ page: testPage })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
// Capture page with any violations highlighted
await captureWithContext(
testPage,
'{{TEST_SUITE_NAME_KEBAB}}-accessibility-check',
`Found ${accessibilityScanResults.violations.length} accessibility violations`
);
// Fail if there are critical violations
const criticalViolations = accessibilityScanResults.violations.filter(
(v) => v.impact === 'critical' || v.impact === 'serious'
);
expect(criticalViolations).toEqual([]);
});
test('should display correctly across viewports', async ({ page: testPage }) => {
const viewports = [
{ name: 'desktop', width: 1280, height: 720 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'mobile', width: 375, height: 667 },
];
for (const viewport of viewports) {
await testPage.setViewportSize(viewport);
// Wait for any responsive changes to settle
await testPage.waitForTimeout(500);
// Capture screenshot for each viewport
await captureWithContext(
testPage,
`{{TEST_SUITE_NAME_KEBAB}}-responsive-${viewport.name}`,
`${viewport.width}x${viewport.height} viewport`
);
// Basic responsive checks
await expect(testPage.locator('body')).toBeVisible();
// No horizontal scroll on mobile/tablet
if (viewport.name !== 'desktop') {
const scrollWidth = await testPage.evaluate(() => document.body.scrollWidth);
const clientWidth = await testPage.evaluate(() => document.body.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1); // Allow 1px tolerance
}
}
});
});

View File

@@ -0,0 +1,72 @@
# Phase 1: Application Discovery & Version Detection
**Purpose**: Understand the application architecture, detect framework versions, and determine optimal Playwright setup
## Steps
### 1. Detect application type and versions
- Read package.json to identify frameworks (React, Vite, Next.js, Express, etc.)
- Check for common files (vite.config.ts, next.config.js, app.js, index.html)
- Identify build tools and dev server configuration
- Extract installed package versions for version-aware configuration
### 2. Consult version compatibility database
- Load `data/framework-versions.yaml` compatibility rules
- Match installed versions against version ranges using semver
- Determine appropriate templates for each framework version
- Identify potential breaking changes or incompatibilities
- **Example**: Tailwind v4 detected → use `@import` syntax, not `@tailwind`
### 3. Validate application access
- Check if dev server is running (ports 3000, 5173, 8080, etc.)
- If not running, determine how to start it (npm run dev, npm start, etc.)
- Verify application loads successfully
### 4. Map critical user journeys
- Identify key pages/routes from routing configuration
- Detect authentication flows
- Find form submissions and interactive elements
- Locate API integrations
## Version Detection Logic
```typescript
// Load compatibility database
const versionDb = parseYAML('data/framework-versions.yaml');
// Detect versions
const detectedVersions = {
tailwind: detectVersion(deps.tailwindcss, versionDb.tailwindcss),
react: detectVersion(deps.react, versionDb.react),
vite: detectVersion(deps.vite, versionDb.vite),
};
// Select appropriate templates
const templates = {
css: detectedVersions.tailwind?.templates.css || 'templates/css/vanilla.css',
postcss: detectedVersions.tailwind?.templates.postcss_config,
playwright: 'templates/playwright.config.template.ts',
};
```
## Output
Application profile with:
- Framework type and versions
- URLs and ports
- Test targets
- Selected templates
## Common Issues
**Unrecognized framework**
- Ask user to specify app type and dev server command manually
- Use generic static site configuration as fallback
**Missing package.json**
- Check for other indicators (index.html, etc.)
- Prompt user for application details
## Transition
Proceed to Phase 2 (Playwright Installation) with version-aware configuration

View File

@@ -0,0 +1,72 @@
# Phase 2: Playwright Installation & Setup
**Purpose**: Install Playwright and generate optimal configuration
## Steps
### 1. Install Playwright
```bash
npm init playwright@latest -- --yes
# Installs Playwright, test runners, and browsers (Chromium, Firefox, WebKit)
```
### 2. Generate playwright.config.ts
Configure based on app type:
- Set base URL (http://localhost:5173 for Vite, etc.)
- Configure viewport sizes:
- Desktop: 1280x720
- Tablet: 768x1024
- Mobile: 375x667
- Set screenshot directory: `screenshots/{test-name}/{timestamp}/`
- Enable trace on failure for debugging
- Configure retries (2 attempts) and timeout (30s)
### 3. Set up directory structure
```
tests/
├── setup/
│ └── global-setup.ts # Start dev server
├── pages/
│ └── *.page.ts # Page object models
├── specs/
│ └── *.spec.ts # Test specifications
└── utils/
└── screenshot-helper.ts
screenshots/
├── baselines/ # Reference images
├── current/ # Latest test run
└── diffs/ # Visual comparisons
```
### 4. Integrate with existing test setup
- Add playwright scripts to package.json
- Configure alongside Vitest/Jest (no conflicts)
- Set up TypeScript types for Playwright
## Output
Fully configured Playwright environment with version-appropriate templates
## Performance
~2-3 minutes for installation and setup (one-time)
## Common Issues
**Installation fails**
- Check Node version (>=16)
- Retry with --force
- Suggest manual installation if network issues
**Browser download fails**
- Check disk space (~500MB needed)
- Try installing specific browser: `npx playwright install chromium`
## Transition
Proceed to Phase 2.5 (Pre-flight Health Check)

View File

@@ -0,0 +1,113 @@
# Phase 2.5: Pre-flight Health Check
**Purpose**: Validate app loads correctly before running full test suite - catches configuration errors early
## Steps
### 1. Launch browser and attempt to load app
```typescript
const browser = await chromium.launch();
const page = await browser.newPage();
try {
const response = await page.goto(baseURL, { timeout: 30000 });
if (!response || !response.ok()) {
throw new Error(`App returned ${response?.status()}`);
}
} catch (error) {
// Analyze error and provide guidance
}
```
### 2. Monitor console for critical errors
- Listen for console errors during page load
- Collect all error messages for pattern analysis
- Wait 2-3 seconds to let errors surface
### 3. Analyze errors against known patterns
Load `data/error-patterns.yaml` error database and match against known patterns:
**Example patterns detected**:
- Tailwind v4 syntax mismatch: "Cannot apply unknown utility class"
- PostCSS plugin error: "Plugin tailwindcss not found"
- Missing dependencies: "Module not found"
### 4. Provide actionable diagnostics
```
❌ Pre-flight check failed: Critical errors detected
Issue: Tailwind CSS v4 syntax mismatch
Root cause: CSS file uses @tailwind directives but v4 requires @import
Fix:
1. Update src/index.css (or globals.css):
Change from: @tailwind base; @tailwind components; @tailwind utilities;
Change to: @import "tailwindcss";
2. Update postcss.config.js:
Change from: plugins: { tailwindcss: {} }
Change to: plugins: { '@tailwindcss/postcss': {} }
3. Restart dev server: npm run dev
Documentation: https://tailwindcss.com/docs/upgrade-guide
```
### 5. Auto-fix if possible, otherwise halt with guidance
- For known issues with clear fixes, offer to fix automatically
- For ambiguous issues, halt and require user intervention
- Prevent running 10+ tests that will all fail due to one config issue
## Error Pattern Analysis
```typescript
function analyzeErrors(consoleErrors) {
const errorPatterns = parseYAML('data/error-patterns.yaml');
const issues = [];
for (const error of consoleErrors) {
for (const [name, pattern] of Object.entries(errorPatterns.css_errors)) {
if (pattern.pattern.test(error) ||
pattern.alternative_patterns?.some(alt => alt.test(error))) {
issues.push({
name,
severity: pattern.severity,
diagnosis: pattern.diagnosis,
recovery_steps: pattern.recovery_steps,
documentation: pattern.documentation,
});
}
}
}
return {
critical: issues.filter(i => i.severity === 'critical'),
allIssues: issues,
};
}
```
## Benefits
- **Fast feedback**: 2-3 seconds vs 30+ seconds for full test suite
- **Clear guidance**: Specific fix steps, not generic "tests failed"
- **Prevents cascade failures**: One config error won't fail all 10 tests
- **Educational**: Explains what went wrong and why
## Output
Health check passed, or detailed error diagnostics with fix steps
## Performance
~2-5 seconds
## Transition
If health check passes, proceed to Phase 3 (Test Generation). If fails, provide fixes and halt.

View File

@@ -0,0 +1,68 @@
# Phase 3: Test Generation
**Purpose**: Create screenshot-enabled test suite covering critical workflows
## Steps
### 1. Generate page object models
- Create POM classes for each major page/component
- Define locators using best practices (getByRole, getByLabel, getByText)
- Add screenshot capture methods to each POM
### 2. Create test specifications
Generate tests for each critical user journey with screenshot capture at key points:
- Initial page load
- Before interaction (button click, form fill)
- After interaction
- Error states
- Success states
### 3. Add accessibility checks
- Integrate axe-core for automated a11y testing
- Capture accessibility violations in screenshots
- Generate accessibility reports
### 4. Set up screenshot helpers
```typescript
// templates/screenshot-helper.ts
export async function captureWithContext(
page: Page,
name: string,
context?: string
) {
const timestamp = new Date().toISOString();
const path = `screenshots/current/${name}-${timestamp}.png`;
await page.screenshot({ path, fullPage: true });
return { path, context, timestamp };
}
```
## Output
Complete test suite with screenshot automation
## Test Coverage
Aim for critical user journeys (80/20 rule):
- Core functionality tests
- Authentication flows
- Form submissions
- Key interactions
## Common Issues
**Too many tests generated**
- Focus on critical paths
- Prioritize user journeys over edge cases
**Locators not found**
- Use semantic locators (getByRole, getByLabel)
- Add test IDs as last resort
## Transition
Proceed to Phase 4 (Screenshot Capture & Execution)

View File

@@ -0,0 +1,65 @@
# Phase 4: Screenshot Capture & Execution
**Purpose**: Run tests and capture comprehensive visual data
## Steps
### 1. Execute test suite
```bash
npx playwright test --project=chromium --headed=false
```
### 2. Capture screenshots systematically
- Full-page screenshots for layout analysis
- Element-specific screenshots for component testing
- Different viewports (desktop, tablet, mobile)
- Different states (hover, focus, active, disabled)
### 3. Organize screenshot artifacts
- Group by test name
- Add timestamp and viewport metadata
- Generate index file for easy navigation
### 4. Handle failures gracefully
On test failure:
- Capture additional debug screenshots
- Save page HTML snapshot
- Record network activity
- Generate Playwright trace for replay
## Output
Organized screenshot directory with metadata:
```
screenshots/
├── current/
│ ├── home-page-load-2024-01-15T10-30-00.png
│ ├── home-page-after-click-2024-01-15T10-30-05.png
│ └── index.json (metadata)
└── ...
```
## Performance
~30-60 seconds for typical app (5-10 tests)
## Common Issues
**Screenshot capture fails**
- Increase timeout
- Add explicit waits
- Capture partial screenshot on failure
**Tests timeout**
- Check dev server is running
- Increase test timeout
- Add explicit wait conditions
## Transition
Proceed to Phase 5 (Visual Analysis)

View File

@@ -0,0 +1,67 @@
# Phase 5: Visual Analysis
**Purpose**: Use LLM vision capabilities to analyze screenshots and identify issues
## Steps
### 1. Batch screenshot analysis
Read all captured screenshots and ask LLM to identify:
- UI bugs (broken layouts, overlapping elements, cut-off text)
- Accessibility issues (low contrast, missing labels, improper heading hierarchy)
- Responsive problems (elements not scaling, overflow issues)
- Missing or misaligned elements
- Unexpected visual artifacts
### 2. Categorize findings
- **Critical**: App is broken/unusable (crashes, white screen, no content)
- **High**: Major UI bugs affecting core functionality
- **Medium**: Visual inconsistencies that impact UX
- **Low**: Minor alignment or styling issues
### 3. Generate issue descriptions
For each issue:
- Natural language description
- Screenshot reference with highlighted problem area
- Affected viewport/browser if relevant
- User impact assessment
## Output
Structured list of visual issues with severity ratings:
```markdown
## Visual Issues Found
### Critical (1)
- White screen on mobile viewport - App fails to render
### High (2)
- Button text cut off at 375px width
- Form labels overlap input fields
### Medium (3)
- Header alignment inconsistent
- ...
```
## Performance
~5-10 seconds per screenshot for LLM analysis
## Common Issues
**Analysis fails**
- Retry analysis
- Skip corrupted images
- Validate PNG format
**Too many false positives**
- Adjust analysis prompts
- Focus on critical issues first
## Transition
Proceed to Phase 6 (Regression Detection)

View File

@@ -0,0 +1,77 @@
# Phase 6: Regression Detection
**Purpose**: Compare current screenshots against baselines to detect changes
## Steps
### 1. Load baseline images
- Check if baselines exist in screenshots/baselines/
- If first run, current screenshots become baselines
- If baselines exist, proceed to comparison
### 2. Perform pixel-level comparison
```typescript
import { compareScreenshots } from 'playwright-core/lib/utils';
const diff = await compareScreenshots(
baselinePath,
currentPath,
diffPath,
{ threshold: 0.2 } // 20% difference threshold
);
```
### 3. Generate visual diff reports
- Create side-by-side comparison images
- Highlight changed regions in red
- Calculate difference percentage
- Classify changes:
- **Expected**: Intentional changes (new features, fixes)
- **Suspicious**: Unintended changes requiring review
- **Critical**: Major regressions (broken features)
### 4. Update baselines if approved
Ask user: "Accept these changes as new baseline?"
- If yes, copy current → baselines
- If no, flag as regressions needing fixes
## Output
Visual regression report with diff images:
```markdown
## Regression Report
### Changed Screenshots (3)
| Screenshot | Diff % | Classification |
|------------|--------|----------------|
| home-page | 5% | Expected |
| form-page | 25% | Suspicious |
| mobile-nav | 45% | Critical |
See screenshots/diffs/ for visual comparisons.
```
## Performance
~1-2 seconds per image pair
## Common Issues
**No baselines exist**
- Current screenshots become baselines automatically
- Message: "No baselines found. Current screenshots saved as baselines."
**False positive diffs**
- Adjust threshold (default 20%)
- Ignore dynamic content areas
- Use stable test data
## Transition
Proceed to Phase 7 (Fix Recommendation Generation)

View File

@@ -0,0 +1,72 @@
# Phase 7: Fix Recommendation Generation
**Purpose**: Map visual issues to source code and generate actionable fixes
## Steps
### 1. Correlate issues with source code
- Use test file metadata to identify component under test
- Search codebase for relevant files (component, styles, layout)
- Match visual issues to likely code locations
### 2. Generate fix recommendations
For each issue, provide:
- **Issue description**: Natural language explanation
- **File location**: `src/components/Button.tsx:45`
- **Current code**: Snippet showing problematic code
- **Recommended fix**: Specific code change
- **Reasoning**: Why this fix addresses the issue
### 3. Prioritize fixes
- Sort by severity (critical → low)
- Group related fixes (same component, same file)
- Estimate complexity (simple CSS tweak vs. complex refactor)
### 4. Format as actionable report
```markdown
# Visual Bug Fix Recommendations
## Critical Issues (2)
### 1. Button text cut off on mobile viewport
**Location**: `src/components/Button.tsx:45`
**Screenshot**: `screenshots/current/button-mobile-1234.png`
**Current Code**:
```tsx
<button className="px-4 py-2 text-lg">
{children}
</button>
```
**Recommended Fix**:
```tsx
<button className="px-4 py-2 text-sm sm:text-lg truncate max-w-full">
{children}
</button>
```
**Reasoning**: Fixed width and font size cause overflow on narrow viewports. Added responsive text sizing and truncation.
```
## Output
fix-recommendations.md with prioritized, actionable fixes
## Common Issues
**Can't find source file**
- Ask user for component location
- Search by component name patterns
**Multiple possible fixes**
- Present options with trade-offs
- Recommend simplest solution
## Transition
Proceed to Phase 8 (Test Suite Export)

View File

@@ -0,0 +1,82 @@
# Phase 8: Test Suite Export
**Purpose**: Provide production-ready test suite for ongoing use
## Steps
### 1. Export test files
- Copy generated tests to project's tests/ directory
- Ensure proper TypeScript types and imports
- Add comments explaining test purpose
### 2. Create README documentation
```markdown
# Playwright E2E Test Suite
## Running Tests
```bash
npm run test:e2e # Run all e2e tests
npm run test:e2e:headed # Run with browser UI
npm run test:e2e:debug # Run with Playwright Inspector
```
## Screenshot Management
- Baselines: `screenshots/baselines/`
- Current: `screenshots/current/`
- Diffs: `screenshots/diffs/`
## Updating Baselines
```bash
npm run test:e2e:update-snapshots
```
```
### 3. Add npm scripts
```json
{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:debug": "playwright test --debug",
"test:e2e:update-snapshots": "playwright test --update-snapshots"
}
}
```
### 4. Document CI/CD integration
- Provide GitHub Actions workflow example
- Explain screenshot artifact storage
- Show how to update baselines in CI
- Configure Playwright HTML reporter for CI
See `reference/ci-cd-integration.md` for complete examples.
## Output
Complete, documented test suite ready for development workflow:
- Tests in `tests/` directory
- README with usage instructions
- npm scripts configured
- CI/CD documentation
## Common Issues
**Tests don't run after export**
- Check TypeScript types
- Verify imports are correct
- Ensure Playwright is in dependencies
**CI/CD integration issues**
- See `reference/ci-cd-integration.md`
- Check browser installation in CI
## Success Criteria
- [ ] Test suite exported to project
- [ ] All tests executable via npm run test:e2e
- [ ] README includes usage instructions
- [ ] CI/CD guidance documented

View File

@@ -0,0 +1,14 @@
# Changelog
## 0.2.0
- Refactored to Anthropic progressive disclosure pattern
- Updated description with "Use PROACTIVELY when..." format
- Removed version/author/category/tags from frontmatter
## 0.1.0
- Initial release with RED-GREEN-REFACTOR cycle enforcement
- Automated TDD workflow with git hooks and npm scripts
- CPU usage prevention safeguards
- Integration with Testing Trophy methodology

View File

@@ -0,0 +1,340 @@
# TDD Red-Green-Refactor Automation
Automated TDD enforcement for LLM-assisted development. This skill installs infrastructure that guides Claude Code to automatically follow TDD workflow without manual intervention.
## Version
**0.2.0** - Stable release with comprehensive safety features
## What This Skill Does
Transforms your project so that when you ask Claude to implement features, it **automatically**:
1. 🔴 **RED Phase** - Writes failing tests FIRST
2. 🟢 **GREEN Phase** - Implements minimal code to pass tests
3. 🔵 **REFACTOR Phase** - Improves code quality while keeping tests green
**No slash commands. No manual prompting. Just say "implement X" and TDD happens.**
## Installation
```bash
# From claudex marketplace
/plugin install tdd-automation
# Or directly invoke the skill
tdd-automation
```
## What Gets Installed
### 1. CLAUDE.md Configuration
- Adds TDD requirements to project's `.claude/CLAUDE.md`
- **Safety**: Backs up existing files, merges cleanly, fully reversible
- Configures LLM to automatically follow TDD workflow
### 2. npm Scripts
```json
{
"test:tdd": "vitest --run",
"test:tdd:watch": "vitest",
"test:red": "vitest --run --reporter=verbose",
"test:green": "vitest --run --reporter=verbose",
"validate:tdd": "node .tdd-automation/scripts/validate-tdd.js",
"generate:test": "node .tdd-automation/scripts/generate-test.js"
}
```
### 3. Git Hooks
- **Pre-commit hook**: Validates tests exist before allowing commits
- Prevents TDD violations at the version control level
- Can be bypassed with `--no-verify` if needed
### 4. Helper Scripts
```
.tdd-automation/
├── scripts/
│ ├── generate-test.js # Create test templates
│ ├── validate-tdd.js # Check compliance
│ ├── rollback-tdd.js # Restore previous state
│ └── remove-tdd-section.js # Clean uninstall
└── templates/
└── test-template.js # Test file template
```
## Usage Examples
### Before Installation
```
You: "Add user authentication"
Claude: *writes implementation code directly* ❌
```
### After Installation
```
You: "Add user authentication"
Claude: "I'll implement user authentication following TDD red-green-refactor.
Let me create a todo list to track the phases:
- [ ] RED: Write failing test for user authentication
- [ ] Verify test fails with expected error
- [ ] GREEN: Implement minimal authentication logic
- [ ] Verify test passes
- [ ] REFACTOR: Improve code quality
Starting RED phase - I'll create the test file first..."
*Creates src/auth/authenticate.test.ts*
*Writes failing test*
*Runs: npm run test:red*
*Verifies failure*
"Test is failing as expected. Moving to GREEN phase..."
*Implements minimal code*
*Runs: npm run test:green*
*Verifies pass*
"All tests passing. Moving to REFACTOR phase..."
*Improves code structure*
*Runs: npm run test:tdd*
"✅ Feature complete with TDD workflow followed."
```
## Available Commands
### Validation
```bash
# Check if project follows TDD practices
npm run validate:tdd
```
### Test Generation
```bash
# Generate test template for implementation file
npm run generate:test src/features/auth/login.ts
```
### Maintenance
```bash
# Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
# Remove only TDD section from CLAUDE.md
node .tdd-automation/scripts/remove-tdd-section.js
# View available backups
ls -lh .claude/CLAUDE.md.backup.*
```
## Safety Features
### ✅ Non-Destructive Installation
- **Automatic backups** before any file modification
- **Merge strategy** preserves all existing content
- **Clear markers** for easy identification of additions
- **Rollback capability** to restore previous state
### ✅ Validation Before Installation
- Detects existing TDD automation (skips if present)
- Checks for existing CLAUDE.md (merges safely)
- Validates project structure
- Reports warnings for missing dependencies
### ✅ Clean Uninstallation
- Remove TDD section without affecting other content
- Restore from timestamped backups
- No orphaned files or configurations
## Configuration
### Enforcement Level
By default, TDD is **strictly enforced**. To customize:
Edit `.claude/CLAUDE.md` and modify:
```yaml
tdd-enforcement-level: strict # or: advisory, optional
```
- **strict**: LLM must follow TDD (default)
- **advisory**: LLM suggests TDD but allows flexibility
- **optional**: TDD is available but not required
### Test Framework
The skill auto-detects your test framework:
- Vitest (default, recommended)
- Jest
- Mocha
- AVA
To override, edit `.tdd-automation/config.json`:
```json
{
"testCommand": "jest",
"testExtension": ".test.ts"
}
```
## Troubleshooting
### Issue: LLM not following TDD
**Solution:**
1. Check CLAUDE.md has TDD configuration:
```bash
grep -A 5 "TDD_AUTOMATION" .claude/CLAUDE.md
```
2. Verify npm scripts installed:
```bash
npm run test:tdd
```
3. Validate installation:
```bash
npm run validate:tdd
```
### Issue: Git hook blocking commits
**Solution:**
1. Ensure tests exist for implementation files
2. Commit tests before implementation:
```bash
git add src/**/*.test.ts
git commit -m "Add tests for feature X"
git add src/**/!(*test).ts
git commit -m "Implement feature X"
```
3. Or bypass hook temporarily:
```bash
git commit --no-verify
```
### Issue: Want to remove TDD automation
**Solution:**
```bash
# Option 1: Remove only CLAUDE.md section
node .tdd-automation/scripts/remove-tdd-section.js
# Option 2: Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
# Option 3: Manual removal
# 1. Edit .claude/CLAUDE.md
# 2. Delete section between <!-- TDD_AUTOMATION_START --> and <!-- TDD_AUTOMATION_END -->
# 3. Remove .tdd-automation/ directory
# 4. Remove npm scripts from package.json
```
## Technical Details
### Architecture
```
┌─────────────────────────────────────────┐
│ User Request │
│ "Implement feature X" │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ CLAUDE.md (LLM Configuration) │
│ • TDD workflow requirements │
│ • Phase-by-phase instructions │
│ • TodoWrite templates │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ Claude Code (LLM) │
│ 1. Creates test file │
│ 2. Writes failing test (RED) │
│ 3. Runs: npm run test:red │
│ 4. Implements code (GREEN) │
│ 5. Runs: npm run test:green │
│ 6. Refactors (REFACTOR) │
│ 7. Runs: npm run test:tdd │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ Git Pre-Commit Hook │
│ • Validates tests exist │
│ • Blocks commits without tests │
│ • Ensures test-first compliance │
└─────────────────────────────────────────┘
```
### File Structure
```
project/
├── .claude/
│ ├── CLAUDE.md # TDD configuration (modified)
│ ├── CLAUDE.md.backup.* # Safety backups
│ └── hooks/
│ └── tdd-auto-enforcer.sh # Pre-prompt hook (optional)
├── .git/
│ └── hooks/
│ └── pre-commit # TDD validation (modified)
├── .tdd-automation/ # Installed by skill
│ ├── scripts/
│ │ ├── generate-test.js
│ │ ├── validate-tdd.js
│ │ ├── rollback-tdd.js
│ │ └── remove-tdd-section.js
│ ├── templates/
│ │ └── test-template.js
│ └── README.md
└── package.json # npm scripts added
```
## Success Metrics
Track TDD adherence:
```bash
npm run validate:tdd
```
**Target Metrics:**
- Test-first adherence: >95%
- RED-GREEN-REFACTOR cycles: >90%
- Defect escape rate: <2%
- Test coverage: >80%
## Contributing
This skill was auto-generated by the Pattern Suggestion Pipeline from 37 successful TDD applications with 86% success rate.
**Issues or improvements:**
- GitHub: https://github.com/cskiro/claudex/issues
- Pattern source: Issue #13
## License
MIT
## Version History
- **0.2.0** (2025-11-02)
- Stable release with full implementation
- Comprehensive safety features (backup, rollback, merge)
- Automatic LLM TDD enforcement via CLAUDE.md
- Git hooks for pre-commit validation
- Helper scripts (generate-test, validate-tdd)
- Multi-framework support (Vitest, Jest, Mocha, AVA)
- Complete documentation and troubleshooting guide
- **0.1.0** (2025-11-02)
- Initial proof-of-concept
- Auto-generated by Pattern Pipeline
- Basic skill structure and manifest

View File

@@ -0,0 +1,235 @@
---
name: tdd-automation
description: Use PROACTIVELY when starting new projects requiring strict TDD adherence, or when user wants to enforce test-first workflow. Automates red-green-refactor cycle for LLM-assisted development with git hooks, npm scripts, and CLAUDE.md configuration. Not for prototypes or projects without test requirements.
---
# TDD Red-Green-Refactor Automation
## Overview
This skill transforms any project to automatically enforce TDD (Test-Driven Development) workflow when using LLMs for code generation. Once installed, Claude Code will automatically follow the red-green-refactor cycle without manual prompting.
**Version:** 0.2.0
**Status:** Stable
**Success Rate:** 86% (based on 37 pattern applications)
**Source:** Pattern Suggestion Pipeline (Issue #13)
## What This Skill Does
**Automatic TDD Enforcement** - When you ask Claude to implement a feature, it will:
1. 🔴 **RED Phase**: Write failing test FIRST
2. 🟢 **GREEN Phase**: Implement minimal code to pass test
3. 🔵 **REFACTOR Phase**: Improve code quality while keeping tests green
**No manual steps required** - The LLM is automatically configured through CLAUDE.md to follow this workflow.
## Installation Process
When you invoke this skill, it will:
1. **Detect project configuration**
- Identify test framework (Vitest, Jest, Mocha, AVA)
- Check for TypeScript/JavaScript
- Validate git repository exists
2. **Configure CLAUDE.md safely**
- Create automatic backup before any changes
- Merge TDD requirements (preserves existing content)
- Add clear section markers for identification
3. **Install npm scripts**
- `test:tdd` - Run all tests once
- `test:tdd:watch` - Run tests in watch mode
- `test:red` - Verify test fails (RED phase)
- `test:green` - Verify test passes (GREEN phase)
- `validate:tdd` - Check TDD compliance
- `generate:test` - Create test template
4. **Install git hooks**
- Pre-commit hook validates tests exist
- Prevents commits without tests
- Enforces test-first workflow
5. **Create helper scripts**
- Test template generator
- TDD compliance validator
- Rollback utility
- Section removal utility
## When to Use This Skill
**Use this skill when:**
- Starting a new project that requires strict TDD adherence
- Migrating existing project to TDD workflow
- Training team members on TDD practices
- Ensuring consistent quality in LLM-generated code
- Working on mission-critical code that needs high test coverage
**Don't use this skill if:**
- Project doesn't have or need tests
- Working on prototypes or experiments
- TDD workflow conflicts with existing practices
## Trigger Phrases
The skill can be invoked by:
- Direct invocation: `tdd-automation`
- Natural language: "Set up TDD automation"
- Natural language: "Configure automatic TDD workflow"
- Natural language: "Install TDD enforcement"
## Behavior After Installation
Once installed, when you say something like:
```
"Implement user authentication"
```
Claude will **automatically**:
```
1. Create todo list with TDD phases:
- [ ] RED: Write failing test for user authentication
- [ ] Verify test fails with expected error
- [ ] GREEN: Implement minimal authentication logic
- [ ] Verify test passes
- [ ] REFACTOR: Improve code quality
- [ ] Verify all tests still pass
2. Create test file FIRST:
src/auth/authenticate.test.ts
3. Write failing test with clear description
4. Run test and verify RED state:
npm run test:red -- src/auth/authenticate.test.ts
5. Implement minimal code:
src/auth/authenticate.ts
6. Run test and verify GREEN state:
npm run test:green -- src/auth/authenticate.test.ts
7. Refactor if needed while keeping tests green
8. Final validation:
npm run test:tdd
```
## Safety Features
### Non-Destructive Installation
- Automatic backups before file modifications
- Merge strategy preserves all existing CLAUDE.md content
- Clear markers (`<!-- TDD_AUTOMATION_START/END -->`) for identification
- Rollback capability to restore previous state
### Validation Before Installation
- Detects if TDD automation already installed (skips if present)
- Checks for existing CLAUDE.md (merges safely)
- Validates project structure
- Reports warnings for missing dependencies
### Clean Uninstallation
- Remove only TDD section without affecting other content
- Restore from timestamped backups
- Utility scripts for easy maintenance
## Response Style
After installation, this skill guides Claude Code to:
- **Be explicit**: Always state which TDD phase is active
- **Be thorough**: Verify each phase completion before proceeding
- **Be traceable**: Use TodoWrite to track progress
- **Be safe**: Run tests to confirm RED/GREEN states
- **Be helpful**: Provide clear error messages if TDD is violated
## Files Modified/Created
### Modified Files
- `.claude/CLAUDE.md` - TDD workflow configuration added (backed up)
- `package.json` - npm scripts added (backed up)
- `.git/hooks/pre-commit` - TDD validation added (backed up if exists)
### Created Files
```
.tdd-automation/
├── scripts/
│ ├── generate-test.js # Test template generator
│ ├── validate-tdd.js # Compliance checker
│ ├── rollback-tdd.js # Restore previous state
│ └── remove-tdd-section.js # Clean uninstall
├── templates/
│ └── test-template.js # Test file template
└── README.md # Usage documentation
.claude/
├── CLAUDE.md.backup.* # Timestamped backups
└── hooks/
└── tdd-auto-enforcer.sh # Pre-prompt hook (optional)
```
## Success Criteria
Installation succeeds when:
1. ✅ CLAUDE.md contains TDD configuration
2. ✅ npm scripts added to package.json
3. ✅ Git pre-commit hook installed (if git repo exists)
4. ✅ Helper scripts created in .tdd-automation/
5. ✅ Backups created for all modified files
6. ✅ Validation passes: `npm run validate:tdd`
## Troubleshooting
### Issue: LLM not following TDD
**Check:**
```bash
# Verify CLAUDE.md has TDD configuration
grep "TDD_AUTOMATION" .claude/CLAUDE.md
# Verify npm scripts
npm run test:tdd
# Run validation
npm run validate:tdd
```
### Issue: Git hook blocking commits
**Solution:**
1. Ensure tests exist for implementation files
2. Commit tests before implementation
3. Or bypass temporarily: `git commit --no-verify`
### Issue: Want to remove TDD automation
**Options:**
```bash
# Option 1: Remove CLAUDE.md section only
node .tdd-automation/scripts/remove-tdd-section.js
# Option 2: Rollback to previous CLAUDE.md
node .tdd-automation/scripts/rollback-tdd.js
```
## Generated By
🤖 **Pattern Suggestion Pipeline**
- Detected from 37 successful TDD applications
- Success rate: 86%
- Source: GitHub Issue #13
- Generated: 2025-11-02
- Enhanced with full implementation: 2025-11-02
## Version History
- **0.2.0** (2025-11-02) - Stable release with full implementation
- **0.1.0** (2025-11-02) - Initial proof-of-concept
## Additional Resources
- Full documentation: `README.md`
- Helper scripts: `.tdd-automation/scripts/`
- Examples: See README.md "Usage Examples" section

View File

417
skills/tdd-automation/index.js Executable file
View File

@@ -0,0 +1,417 @@
#!/usr/bin/env node
/**
* TDD Red-Green-Refactor Automation Skill
*
* Main orchestrator that installs TDD automation infrastructure in a project.
* Configures CLAUDE.md, git hooks, npm scripts, and helper utilities to enforce
* TDD workflow automatically for LLM-assisted development.
*
* @author Pattern Suggestion Pipeline
* @version 0.2.0
*/
const fs = require('fs');
const path = require('path');
// Import utilities
const ClaudeMdValidator = require('./utils/validate-claude-md');
const ClaudeMdMerger = require('./utils/merge-claude-md');
const ProjectDetector = require('./utils/detect-project-type');
const PackageJsonUpdater = require('./utils/update-package-json');
const HookInstaller = require('./utils/install-hooks');
class TddAutomationSkill {
constructor() {
this.projectRoot = process.cwd();
this.skillRoot = __dirname;
this.version = '0.2.0';
}
/**
* Main installation workflow
*/
async install() {
console.log('╔════════════════════════════════════════════════════════════════╗');
console.log('║ TDD Red-Green-Refactor Automation ║');
console.log('║ Version 0.2.0 ║');
console.log('╚════════════════════════════════════════════════════════════════╝\n');
const results = {
success: false,
steps: [],
errors: [],
warnings: []
};
try {
// Step 1: Detect project type
console.log('🔍 Step 1: Detecting project configuration...\n');
const projectInfo = await this.detectProject(results);
// Step 2: Validate and backup CLAUDE.md
console.log('\n🔍 Step 2: Validating CLAUDE.md...\n');
const validation = await this.validateClaudeMd(results);
if (validation.strategy === 'skip') {
console.log('✅ TDD automation already installed');
console.log(' No changes needed.\n');
return results;
}
// Step 3: Install/merge CLAUDE.md configuration
console.log('\n📝 Step 3: Configuring CLAUDE.md...\n');
await this.configureClaudeMd(validation, results);
// Step 4: Update package.json with TDD scripts
console.log('\n📦 Step 4: Adding npm scripts...\n');
await this.updatePackageJson(projectInfo, results);
// Step 5: Install hooks
console.log('\n🪝 Step 5: Installing hooks...\n');
await this.installHooks(results);
// Step 6: Verify installation
console.log('\n✅ Step 6: Verifying installation...\n');
await this.verifyInstallation(results);
// Success summary
results.success = results.errors.length === 0;
this.printSummary(results, projectInfo);
} catch (error) {
console.error('\n❌ Installation failed:', error.message);
console.error('\n Stack trace:', error.stack);
results.errors.push(error.message);
results.success = false;
}
return results;
}
/**
* Detect project type and configuration
*/
async detectProject(results) {
const detector = new ProjectDetector(this.projectRoot);
const projectInfo = detector.detect();
console.log(` Project: ${projectInfo.projectName}`);
console.log(` Type: ${projectInfo.type}`);
if (projectInfo.testFramework !== 'unknown') {
console.log(` Test Framework: ${projectInfo.testFramework}`);
}
if (projectInfo.hasTypeScript) {
console.log(' Language: TypeScript');
}
if (projectInfo.hasGit) {
console.log(' ✓ Git repository detected');
} else {
results.warnings.push('No git repository - git hooks will not be installed');
}
if (projectInfo.recommendations.length > 0) {
console.log('\n Recommendations:');
projectInfo.recommendations.forEach(rec => {
console.log(`${rec}`);
results.warnings.push(rec);
});
}
results.steps.push('Project detection completed');
return projectInfo;
}
/**
* Validate CLAUDE.md state
*/
async validateClaudeMd(results) {
const validator = new ClaudeMdValidator(this.projectRoot);
const validation = validator.validate();
console.log(` Location: ${validation.path}`);
console.log(` Exists: ${validation.exists ? 'Yes' : 'No'}`);
if (validation.exists) {
console.log(` Size: ${this.formatBytes(validation.size)}`);
console.log(` Has Content: ${validation.hasExistingContent ? 'Yes' : 'No'}`);
}
console.log(` Strategy: ${validation.strategy.toUpperCase()}`);
if (validation.warnings.length > 0) {
console.log('\n Notes:');
validation.warnings.forEach(warning => {
console.log(`${warning}`);
});
}
results.steps.push(`CLAUDE.md validation: ${validation.strategy}`);
return validation;
}
/**
* Configure CLAUDE.md with TDD automation
*/
async configureClaudeMd(validation, results) {
const validator = new ClaudeMdValidator(this.projectRoot);
if (validation.strategy === 'merge') {
// Existing content - merge safely
console.log(' Creating backup...');
const backup = validator.createBackup();
if (backup.success) {
console.log(` ✓ Backup created: ${path.basename(backup.path)}`);
results.steps.push(`Backup created: ${backup.path}`);
} else {
throw new Error(`Backup failed: ${backup.reason}`);
}
// Read existing content
const existingContent = fs.readFileSync(validator.claudeMdPath, 'utf-8');
// Merge with TDD section
console.log(' Merging TDD configuration...');
const merger = new ClaudeMdMerger(existingContent);
const mergedContent = merger.merge();
// Write merged content
fs.writeFileSync(validator.claudeMdPath, mergedContent, 'utf-8');
const newSize = mergedContent.length;
const added = newSize - validation.size;
console.log(` ✓ CLAUDE.md updated`);
console.log(` Original: ${this.formatBytes(validation.size)}`);
console.log(` New: ${this.formatBytes(newSize)} (+${this.formatBytes(added)})`);
results.steps.push('CLAUDE.md merged with TDD configuration');
} else {
// No existing content or empty - create new
console.log(' Creating new CLAUDE.md with TDD configuration...');
const claudeDir = path.join(this.projectRoot, '.claude');
if (!fs.existsSync(claudeDir)) {
fs.mkdirSync(claudeDir, { recursive: true });
}
const content = ClaudeMdMerger.createNew();
fs.writeFileSync(validator.claudeMdPath, content, 'utf-8');
console.log(` ✓ CLAUDE.md created`);
console.log(` Size: ${this.formatBytes(content.length)}`);
results.steps.push('CLAUDE.md created with TDD configuration');
}
}
/**
* Update package.json with TDD scripts
*/
async updatePackageJson(projectInfo, results) {
if (!projectInfo.hasPackageJson) {
console.log(' ⚠️ No package.json found - skipping npm scripts');
results.warnings.push('No package.json - npm scripts not added');
return;
}
const updater = new PackageJsonUpdater(this.projectRoot);
// Check if already installed
if (updater.hasTddScripts()) {
console.log(' ✓ TDD scripts already installed');
results.steps.push('TDD npm scripts already present');
return;
}
// Determine test command based on project
const testCommand = projectInfo.testFramework === 'vitest'
? 'vitest --run'
: projectInfo.testFramework === 'jest'
? 'jest'
: 'npm test';
const result = updater.addTddScripts(testCommand);
if (result.success) {
console.log(' ✓ Added TDD scripts to package.json');
if (result.added.length > 0) {
console.log('\n Scripts added:');
result.added.forEach(script => {
console.log(`${script}`);
});
}
if (result.skipped.length > 0) {
console.log('\n Scripts skipped (already exist):');
result.skipped.forEach(script => {
console.log(`${script}`);
});
}
results.steps.push(`Added ${result.added.length} npm scripts`);
} else {
results.warnings.push(`Failed to update package.json: ${result.reason}`);
}
}
/**
* Install git and Claude hooks
*/
async installHooks(results) {
const installer = new HookInstaller(this.projectRoot, this.skillRoot);
const result = installer.installAll();
if (result.installed.length > 0) {
console.log(' Installed:');
result.installed.forEach(item => {
console.log(`${item}`);
});
}
if (result.skipped.length > 0) {
console.log('\n Skipped:');
result.skipped.forEach(item => {
console.log(`${item}`);
});
}
if (result.failed.length > 0) {
console.log('\n Failed:');
result.failed.forEach(item => {
console.log(`${item}`);
results.warnings.push(`Hook installation failed: ${item}`);
});
}
results.steps.push(`Installed ${result.installed.length} hooks`);
}
/**
* Verify installation was successful
*/
async verifyInstallation(results) {
const checks = [];
// Check CLAUDE.md
const claudeMdPath = path.join(this.projectRoot, '.claude', 'CLAUDE.md');
if (fs.existsSync(claudeMdPath)) {
const content = fs.readFileSync(claudeMdPath, 'utf-8');
if (content.includes('TDD_AUTOMATION')) {
checks.push('✓ CLAUDE.md configured');
} else {
checks.push('✗ CLAUDE.md missing TDD configuration');
}
} else {
checks.push('✗ CLAUDE.md not found');
}
// Check .tdd-automation directory
const tddDir = path.join(this.projectRoot, '.tdd-automation');
if (fs.existsSync(tddDir)) {
checks.push('✓ .tdd-automation directory created');
}
// Check package.json scripts
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
if (packageJson.scripts && packageJson.scripts['test:tdd']) {
checks.push('✓ npm scripts added');
}
}
checks.forEach(check => console.log(` ${check}`));
results.steps.push('Installation verified');
}
/**
* Print installation summary
*/
printSummary(results, projectInfo) {
console.log('\n╔════════════════════════════════════════════════════════════════╗');
console.log('║ Installation Summary ║');
console.log('╚════════════════════════════════════════════════════════════════╝\n');
if (results.success) {
console.log('✅ TDD automation installed successfully!\n');
} else {
console.log('⚠️ TDD automation installed with warnings\n');
}
console.log('📦 What was installed:\n');
console.log(' • CLAUDE.md: TDD workflow configuration for LLM');
console.log(' • npm scripts: test:tdd, validate:tdd, generate:test');
console.log(' • Git hooks: pre-commit TDD validation');
console.log(' • Helper scripts: .tdd-automation/scripts/');
console.log(' • Templates: Test file generators\n');
console.log('🚀 Next steps:\n');
console.log(' 1. Test the automation:');
console.log(' Say: "Implement user authentication"\n');
console.log(' 2. The LLM will automatically:');
console.log(' • Follow TDD red-green-refactor workflow');
console.log(' • Create tests BEFORE implementation');
console.log(' • Run tests to verify each phase');
console.log(' • Use TodoWrite to track progress\n');
console.log('📚 Available commands:\n');
console.log(' npm run test:tdd # Run all tests');
console.log(' npm run validate:tdd # Check TDD compliance');
console.log(' npm run generate:test <file> # Create test template\n');
console.log('🔧 Maintenance:\n');
console.log(' node .tdd-automation/scripts/rollback-tdd.js');
console.log(' node .tdd-automation/scripts/remove-tdd-section.js\n');
if (results.warnings.length > 0) {
console.log('⚠️ Warnings:\n');
results.warnings.forEach(warning => {
console.log(`${warning}`);
});
console.log('');
}
console.log('═══════════════════════════════════════════════════════════════\n');
}
/**
* Format bytes for display
*/
formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
}
/**
* Main entry point
*/
async function main() {
const skill = new TddAutomationSkill();
const results = await skill.install();
if (!results.success && results.errors.length > 0) {
console.error('Installation completed with errors');
process.exit(1);
}
process.exit(0);
}
// Run if called directly
if (require.main === module) {
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = TddAutomationSkill;

View File

View File

@@ -0,0 +1,198 @@
#!/usr/bin/env node
/**
* Test Template Generator
*
* Generates test file templates based on implementation files.
* Analyzes project structure and test framework to create appropriate templates.
*/
const fs = require('fs');
const path = require('path');
function generateTest() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: npm run generate:test <implementation-file>');
console.error('');
console.error('Example:');
console.error(' npm run generate:test src/features/auth/login.ts');
process.exit(1);
}
const implFile = args[0];
if (!fs.existsSync(implFile)) {
console.error(`❌ Error: File not found: ${implFile}`);
process.exit(1);
}
// Determine test file path
const ext = path.extname(implFile);
const testFile = implFile.replace(ext, `.test${ext}`);
if (fs.existsSync(testFile)) {
console.error(`❌ Error: Test file already exists: ${testFile}`);
console.error(' Use a different name or edit the existing file');
process.exit(1);
}
// Get implementation file info
const filename = path.basename(implFile, ext);
const implContent = fs.readFileSync(implFile, 'utf-8');
// Detect test framework
const testFramework = detectTestFramework();
// Generate appropriate template
const template = generateTemplate(filename, implFile, implContent, testFramework);
// Write test file
fs.writeFileSync(testFile, template, 'utf-8');
console.log('✅ Test file created:', testFile);
console.log('');
console.log('📝 Next steps:');
console.log(' 1. Edit the test file to add specific test cases');
console.log(' 2. Run tests to verify RED phase:');
console.log(` npm run test:red -- ${testFile}`);
console.log(' 3. Implement the feature');
console.log(' 4. Run tests to verify GREEN phase:');
console.log(` npm run test:green -- ${testFile}`);
}
function detectTestFramework() {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return 'vitest'; // default
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
if (allDeps['vitest']) return 'vitest';
if (allDeps['jest']) return 'jest';
if (allDeps['@jest/globals']) return 'jest';
if (allDeps['mocha']) return 'mocha';
return 'vitest'; // default
} catch (error) {
return 'vitest';
}
}
function generateTemplate(filename, implFile, implContent, testFramework) {
const relativePath = path.relative(path.dirname(implFile), implFile);
const importPath = './' + path.basename(implFile, path.extname(implFile));
// Extract potential functions/classes to test
const entities = extractEntities(implContent);
let template = '';
// Add imports based on test framework
if (testFramework === 'vitest') {
template += `import { describe, it, expect, beforeEach, afterEach } from 'vitest';\n`;
} else if (testFramework === 'jest') {
template += `import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';\n`;
} else {
template += `const { describe, it, expect, beforeEach, afterEach } = require('${testFramework}');\n`;
}
// Add import for implementation
template += `import * as impl from '${importPath}';\n`;
template += `\n`;
// Add main describe block
template += `describe('${filename}', () => {\n`;
template += ` // TODO: Add setup and teardown if needed\n`;
template += ` // beforeEach(() => {\n`;
template += ` // // Setup before each test\n`;
template += ` // });\n`;
template += `\n`;
template += ` // afterEach(() => {\n`;
template += ` // // Cleanup after each test\n`;
template += ` // });\n`;
template += `\n`;
if (entities.length > 0) {
// Generate test stubs for each entity
entities.forEach(entity => {
template += ` describe('${entity}', () => {\n`;
template += ` it('should [behavior] when [condition]', () => {\n`;
template += ` // Arrange\n`;
template += ` // TODO: Set up test data and dependencies\n`;
template += `\n`;
template += ` // Act\n`;
template += ` // TODO: Call the function/method being tested\n`;
template += ` // const result = impl.${entity}();\n`;
template += `\n`;
template += ` // Assert\n`;
template += ` // TODO: Verify the expected behavior\n`;
template += ` expect(true).toBe(false); // Replace with actual assertion\n`;
template += ` });\n`;
template += `\n`;
template += ` // TODO: Add more test cases:\n`;
template += ` // it('should handle edge case when [condition]', () => { ... });\n`;
template += ` // it('should throw error when [invalid input]', () => { ... });\n`;
template += ` });\n`;
template += `\n`;
});
} else {
// Generic test template
template += ` it('should [behavior] when [condition]', () => {\n`;
template += ` // Arrange\n`;
template += ` // TODO: Set up test data and dependencies\n`;
template += `\n`;
template += ` // Act\n`;
template += ` // TODO: Call the function/method being tested\n`;
template += `\n`;
template += ` // Assert\n`;
template += ` // TODO: Verify the expected behavior\n`;
template += ` expect(true).toBe(false); // Replace with actual assertion\n`;
template += ` });\n`;
template += `\n`;
template += ` // TODO: Add more test cases for different scenarios\n`;
}
template += `});\n`;
return template;
}
function extractEntities(content) {
const entities = [];
// Extract exported functions
const functionMatches = content.matchAll(/export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)/g);
for (const match of functionMatches) {
entities.push(match[1]);
}
// Extract exported classes
const classMatches = content.matchAll(/export\s+class\s+([a-zA-Z0-9_]+)/g);
for (const match of classMatches) {
entities.push(match[1]);
}
// Extract exported const functions (arrow functions)
const constMatches = content.matchAll(/export\s+const\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\(/g);
for (const match of constMatches) {
entities.push(match[1]);
}
return entities;
}
// Run if called directly
if (require.main === module) {
generateTest();
}
module.exports = { generateTest };

View File

@@ -0,0 +1,125 @@
#!/usr/bin/env node
/**
* TDD Section Removal Utility
*
* Cleanly removes only the TDD automation section from CLAUDE.md
* while preserving all other content.
*/
const fs = require('fs');
const path = require('path');
// Import utilities
const projectRoot = process.cwd();
const skillRoot = path.join(__dirname, '..');
// Try to load ClaudeMdValidator
let ClaudeMdValidator;
try {
ClaudeMdValidator = require(path.join(skillRoot, 'utils', 'validate-claude-md.js'));
} catch {
try {
ClaudeMdValidator = require(path.join(projectRoot, '.tdd-automation', 'utils', 'validate-claude-md.js'));
} catch {
console.error('❌ Error: ClaudeMdValidator not found');
process.exit(1);
}
}
async function removeTddSection() {
console.log('🗑️ TDD Section Removal Utility\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const validator = new ClaudeMdValidator(projectRoot);
// Check if CLAUDE.md exists
if (!fs.existsSync(validator.claudeMdPath)) {
console.log('❌ CLAUDE.md not found');
console.log(` Expected location: ${validator.claudeMdPath}`);
console.log('');
process.exit(1);
}
// Check if TDD section exists
const content = fs.readFileSync(validator.claudeMdPath, 'utf-8');
if (!validator.detectTddSection(content)) {
console.log('✅ No TDD section found in CLAUDE.md');
console.log(' Nothing to remove');
console.log('');
process.exit(0);
}
console.log('🔍 TDD section detected in CLAUDE.md\n');
// Show what will be removed
const startMarker = '<!-- TDD_AUTOMATION_START -->';
const endMarker = '<!-- TDD_AUTOMATION_END -->';
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx !== -1 && endIdx !== -1) {
const sectionSize = endIdx - startIdx + endMarker.length;
console.log(` Section size: ${formatBytes(sectionSize)}`);
console.log(` Total file size: ${formatBytes(content.length)}`);
console.log(` After removal: ${formatBytes(content.length - sectionSize)}\n`);
}
// Perform removal
console.log('🔒 Creating backup before removal...');
const result = validator.removeTddSection();
if (result.success) {
console.log(`✅ Backup created: ${path.basename(result.backup)}\n`);
console.log('✅ TDD section removed successfully!\n');
console.log(' Statistics:');
console.log(` • Original size: ${formatBytes(result.originalSize)}`);
console.log(` • New size: ${formatBytes(result.newSize)}`);
console.log(` • Removed: ${formatBytes(result.removed)}\n`);
console.log(' Backup available at:');
console.log(` ${result.backup}\n`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('⚠️ TDD automation configuration removed from CLAUDE.md\n');
console.log(' Other TDD automation components remain:');
console.log(' • .tdd-automation/ directory');
console.log(' • npm scripts (test:tdd, validate:tdd, etc.)');
console.log(' • git hooks (.git/hooks/pre-commit)');
console.log(' • Claude hooks (.claude/hooks/tdd-auto-enforcer.sh)\n');
console.log(' To remove all components:');
console.log(' node .tdd-automation/scripts/uninstall-tdd.js\n');
console.log(' To restore this section:');
console.log(` node .tdd-automation/scripts/rollback-tdd.js\n`);
} else {
console.error('❌ Removal failed:', result.reason);
console.error('');
console.error(' Possible issues:');
console.error(' • Section markers not found (manual edit may be needed)');
console.error(' • File permissions issue');
console.error(' • Disk space issue');
console.error('');
console.error(' Manual removal:');
console.error(' 1. Open .claude/CLAUDE.md in editor');
console.error(' 2. Find <!-- TDD_AUTOMATION_START -->');
console.error(' 3. Delete everything until <!-- TDD_AUTOMATION_END -->');
console.error(' 4. Save file');
console.error('');
process.exit(1);
}
}
function formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
// Run if called directly
if (require.main === module) {
removeTddSection().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { removeTddSection };

View File

@@ -0,0 +1,141 @@
#!/usr/bin/env node
/**
* TDD Automation Rollback Utility
*
* Restores previous CLAUDE.md from backup and optionally removes all TDD automation.
*/
const fs = require('fs');
const path = require('path');
// Import utilities from parent directory
const projectRoot = process.cwd();
const skillRoot = path.join(__dirname, '..');
// Try to load ClaudeMdValidator from skill location
let ClaudeMdValidator;
try {
ClaudeMdValidator = require(path.join(skillRoot, 'utils', 'validate-claude-md.js'));
} catch {
// If skill utils not available, use local copy in .tdd-automation
try {
ClaudeMdValidator = require(path.join(projectRoot, '.tdd-automation', 'utils', 'validate-claude-md.js'));
} catch {
console.error('❌ Error: ClaudeMdValidator not found');
console.error(' This script must be run from project root or .tdd-automation directory');
process.exit(1);
}
}
async function rollbackTddAutomation() {
console.log('🔄 TDD Automation Rollback Utility\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const validator = new ClaudeMdValidator(projectRoot);
// List available backups
const backups = validator.listBackups();
if (backups.length === 0) {
console.log('❌ No backup files found');
console.log(' Cannot rollback without backup');
console.log('');
console.log(' Backup files should be in: .claude/CLAUDE.md.backup.*');
console.log('');
process.exit(1);
}
console.log(`📋 Found ${backups.length} backup(s):\n`);
backups.forEach((backup, i) => {
const age = getTimeAgo(backup.created);
console.log(`${i + 1}. ${backup.name}`);
console.log(` Created: ${backup.created.toISOString()} (${age})`);
console.log(` Size: ${formatBytes(backup.size)}\n`);
});
// Use most recent backup
const latestBackup = backups[0];
console.log(`🔄 Rolling back to most recent backup:\n`);
console.log(` ${latestBackup.name}`);
console.log(` Created: ${latestBackup.created.toISOString()}\n`);
// Create backup of current state before rollback
console.log('🔒 Creating safety backup of current state...');
const currentBackup = validator.createBackup();
if (currentBackup.success) {
console.log(`✅ Current state backed up to:`);
console.log(` ${path.basename(currentBackup.path)}\n`);
} else {
console.log(`⚠️ Could not backup current state: ${currentBackup.reason}`);
console.log(` Proceeding with rollback anyway...\n`);
}
// Perform rollback
console.log('🔄 Performing rollback...');
const result = validator.rollback(latestBackup.path);
if (result.success) {
console.log('✅ Rollback successful!\n');
console.log(' CLAUDE.md has been restored from:');
console.log(` ${path.basename(result.restoredFrom)}`);
console.log(` Size: ${formatBytes(result.size)}\n`);
if (currentBackup.success) {
console.log(' Your previous state was saved to:');
console.log(` ${path.basename(currentBackup.path)}\n`);
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('⚠️ TDD automation configuration has been removed from CLAUDE.md\n');
console.log(' Other TDD automation components remain:');
console.log(' • .tdd-automation/ directory');
console.log(' • npm scripts (test:tdd, validate:tdd, etc.)');
console.log(' • git hooks (.git/hooks/pre-commit)');
console.log(' • Claude hooks (.claude/hooks/tdd-auto-enforcer.sh)\n');
console.log(' To remove all TDD automation components:');
console.log(' node .tdd-automation/scripts/uninstall-tdd.js\n');
console.log(' To reinstall TDD automation:');
console.log(' Run the tdd-automation skill again\n');
} else {
console.error('❌ Rollback failed:', result.reason);
console.error('');
console.error(' You may need to manually restore CLAUDE.md from backup');
console.error(` Backup location: ${latestBackup.path}`);
console.error('');
process.exit(1);
}
}
function getTimeAgo(date) {
const now = new Date();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
return `${seconds} second${seconds > 1 ? 's' : ''} ago`;
}
function formatBytes(bytes) {
if (bytes < 1024) return `${bytes} bytes`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
// Run if called directly
if (require.main === module) {
rollbackTddAutomation().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { rollbackTddAutomation };

View File

@@ -0,0 +1,300 @@
#!/usr/bin/env node
/**
* TDD Compliance Validator
*
* Validates that project follows TDD best practices:
* - All implementation files have corresponding tests
* - Test coverage meets minimum threshold
* - Tests are properly structured
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
async function validateTdd() {
console.log('🔍 TDD Compliance Validation\n');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
const results = {
passed: [],
failed: [],
warnings: []
};
// Check 1: Verify test files exist for implementation files
console.log('📋 Check 1: Test file coverage...');
await checkTestFileCoverage(results);
// Check 2: Verify test framework is installed
console.log('\n📋 Check 2: Test framework installation...');
await checkTestFramework(results);
// Check 3: Verify TDD npm scripts exist
console.log('\n📋 Check 3: TDD npm scripts...');
await checkNpmScripts(results);
// Check 4: Verify git hooks installed
console.log('\n📋 Check 4: Git hooks...');
await checkGitHooks(results);
// Check 5: Verify CLAUDE.md has TDD configuration
console.log('\n📋 Check 5: CLAUDE.md configuration...');
await checkClaudeMd(results);
// Summary
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
console.log('📊 Validation Summary\n');
if (results.passed.length > 0) {
console.log(`✅ Passed: ${results.passed.length}`);
results.passed.forEach(msg => console.log(`${msg}`));
console.log('');
}
if (results.warnings.length > 0) {
console.log(`⚠️ Warnings: ${results.warnings.length}`);
results.warnings.forEach(msg => console.log(`${msg}`));
console.log('');
}
if (results.failed.length > 0) {
console.log(`❌ Failed: ${results.failed.length}`);
results.failed.forEach(msg => console.log(`${msg}`));
console.log('');
}
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
if (results.failed.length > 0) {
console.log('❌ TDD compliance validation FAILED\n');
console.log(' Please address the failed checks above');
console.log(' Run: npm run generate:test <file> to create missing tests\n');
process.exit(1);
} else if (results.warnings.length > 0) {
console.log('⚠️ TDD compliance validation PASSED with warnings\n');
console.log(' Consider addressing the warnings above\n');
process.exit(0);
} else {
console.log('✅ TDD compliance validation PASSED\n');
console.log(' All checks passed successfully!\n');
process.exit(0);
}
}
async function checkTestFileCoverage(results) {
const srcDir = path.join(process.cwd(), 'src');
if (!fs.existsSync(srcDir)) {
results.warnings.push('No src/ directory found - skipping test coverage check');
return;
}
const implFiles = findImplementationFiles(srcDir);
const testFiles = findTestFiles(srcDir);
let missing = 0;
let covered = 0;
for (const implFile of implFiles) {
const testFile = findCorrespondingTest(implFile, testFiles);
if (!testFile) {
missing++;
if (missing <= 5) { // Only show first 5 to avoid spam
results.failed.push(`Missing test for: ${path.relative(process.cwd(), implFile)}`);
}
} else {
covered++;
}
}
if (missing > 5) {
results.failed.push(`... and ${missing - 5} more files without tests`);
}
const totalFiles = implFiles.length;
const coverage = totalFiles > 0 ? ((covered / totalFiles) * 100).toFixed(1) : 100;
if (missing === 0 && totalFiles > 0) {
results.passed.push(`All ${totalFiles} implementation files have tests (100%)`);
} else if (coverage >= 80) {
results.warnings.push(`Test file coverage: ${coverage}% (${covered}/${totalFiles}) - below 100%`);
} else if (totalFiles === 0) {
results.warnings.push('No implementation files found in src/');
}
}
async function checkTestFramework(results) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
results.failed.push('package.json not found');
return;
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const testFrameworks = ['vitest', 'jest', '@jest/globals', 'mocha', 'ava'];
const installed = testFrameworks.find(fw => allDeps[fw]);
if (installed) {
results.passed.push(`Test framework installed: ${installed}`);
} else {
results.failed.push('No test framework found (install vitest, jest, or mocha)');
}
} catch (error) {
results.failed.push(`Error reading package.json: ${error.message}`);
}
}
async function checkNpmScripts(results) {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
return; // Already reported in previous check
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
const scripts = packageJson.scripts || {};
const requiredScripts = ['test:tdd', 'validate:tdd', 'generate:test'];
const missingScripts = requiredScripts.filter(script => !scripts[script]);
if (missingScripts.length === 0) {
results.passed.push('All TDD npm scripts installed');
} else {
missingScripts.forEach(script => {
results.warnings.push(`Missing npm script: ${script}`);
});
}
} catch (error) {
results.failed.push(`Error checking npm scripts: ${error.message}`);
}
}
async function checkGitHooks(results) {
const preCommitPath = path.join(process.cwd(), '.git', 'hooks', 'pre-commit');
if (!fs.existsSync(path.join(process.cwd(), '.git'))) {
results.warnings.push('Not a git repository - no git hooks checked');
return;
}
if (!fs.existsSync(preCommitPath)) {
results.warnings.push('Git pre-commit hook not installed');
return;
}
const content = fs.readFileSync(preCommitPath, 'utf-8');
if (content.includes('TDD_AUTOMATION') || content.includes('TDD Validation')) {
results.passed.push('Git pre-commit hook installed for TDD validation');
} else {
results.warnings.push('Git pre-commit hook exists but may not have TDD validation');
}
}
async function checkClaudeMd(results) {
const claudeMdPath = path.join(process.cwd(), '.claude', 'CLAUDE.md');
if (!fs.existsSync(claudeMdPath)) {
results.warnings.push('No .claude/CLAUDE.md found');
return;
}
const content = fs.readFileSync(claudeMdPath, 'utf-8');
const tddMarkers = [
'TDD Red-Green-Refactor',
'tdd-automation-version',
'<!-- TDD_AUTOMATION_START -->'
];
const hasTddConfig = tddMarkers.some(marker => content.includes(marker));
if (hasTddConfig) {
results.passed.push('CLAUDE.md has TDD configuration');
} else {
results.warnings.push('CLAUDE.md exists but missing TDD configuration');
}
}
function findImplementationFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules, dist, build, etc.
if (!['node_modules', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
findImplementationFiles(fullPath, files);
}
} else if (entry.isFile()) {
// Include .ts, .js, .tsx, .jsx files but exclude test files
if (/\.(ts|js|tsx|jsx)$/.test(entry.name) && !/\.(test|spec)\./.test(entry.name)) {
files.push(fullPath);
}
}
}
return files;
}
function findTestFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (!['node_modules', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
findTestFiles(fullPath, files);
}
} else if (entry.isFile()) {
if (/\.(test|spec)\.(ts|js|tsx|jsx)$/.test(entry.name)) {
files.push(fullPath);
}
}
}
return files;
}
function findCorrespondingTest(implFile, testFiles) {
const dir = path.dirname(implFile);
const filename = path.basename(implFile);
const base = filename.replace(/\.(ts|js|tsx|jsx)$/, '');
// Try multiple test file patterns
const patterns = [
path.join(dir, `${base}.test.ts`),
path.join(dir, `${base}.test.js`),
path.join(dir, `${base}.test.tsx`),
path.join(dir, `${base}.test.jsx`),
path.join(dir, `${base}.spec.ts`),
path.join(dir, `${base}.spec.js`),
path.join(dir, `${base}.spec.tsx`),
path.join(dir, `${base}.spec.jsx`)
];
return testFiles.find(testFile => patterns.includes(testFile));
}
// Run if called directly
if (require.main === module) {
validateTdd().catch(error => {
console.error('❌ Unexpected error:', error.message);
process.exit(1);
});
}
module.exports = { validateTdd };

View File

@@ -0,0 +1,110 @@
#!/bin/bash
###############################################################################
# TDD_AUTOMATION: Git Pre-Commit Hook
#
# Validates that tests exist for implementation files before allowing commit.
# Ensures TDD workflow (test-first) is followed.
#
# Installed by: tdd-automation skill
###############################################################################
echo "🔍 TDD Validation: Checking test-first compliance..."
# Get list of new implementation files being committed
IMPL_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E "^src/.*\.(ts|js|tsx|jsx)$" | grep -v ".test." | grep -v ".spec.")
if [ -z "$IMPL_FILES" ]; then
echo "✅ No new implementation files detected"
exit 0
fi
VIOLATIONS=0
# Check each implementation file
while IFS= read -r impl_file; do
if [ -z "$impl_file" ]; then
continue
fi
# Determine expected test file name
dir=$(dirname "$impl_file")
filename=$(basename "$impl_file")
base="${filename%.*}"
ext="${filename##*.}"
# Try multiple test file patterns
test_patterns=(
"$dir/$base.test.$ext"
"$dir/$base.spec.$ext"
"${impl_file%.*}.test.$ext"
"${impl_file%.*}.spec.$ext"
)
test_exists=false
for test_file in "${test_patterns[@]}"; do
if [ -f "$test_file" ]; then
test_exists=true
# Check if test file was committed first (git log timestamps)
test_commit_time=$(git log --diff-filter=A --format="%at" --follow -- "$test_file" 2>/dev/null | head -1)
impl_commit_time=$(date +%s) # Current time for uncommitted file
# If test file exists in git history, it was committed first - good!
if [ -n "$test_commit_time" ]; then
echo "$impl_file -> Test exists: $test_file"
break
else
# Test file exists but not committed yet
if git diff --cached --name-only | grep -q "$test_file"; then
echo "$impl_file -> Test being committed: $test_file"
break
fi
fi
fi
done
if [ "$test_exists" = false ]; then
echo "❌ TDD VIOLATION: No test file found for $impl_file"
echo " Expected one of:"
for pattern in "${test_patterns[@]}"; do
echo " - $pattern"
done
VIOLATIONS=$((VIOLATIONS + 1))
fi
done <<< "$IMPL_FILES"
# Check for test files in commit
TEST_FILES=$(git diff --cached --name-only | grep -E "\.(test|spec)\.(ts|js|tsx|jsx)$")
if [ -n "$TEST_FILES" ]; then
echo ""
echo "📝 Test files in this commit:"
echo "$TEST_FILES" | while IFS= read -r test_file; do
echo "$test_file"
done
fi
echo ""
if [ $VIOLATIONS -gt 0 ]; then
echo "❌ TDD Validation Failed: $VIOLATIONS violation(s) found"
echo ""
echo "TDD requires writing tests BEFORE implementation."
echo ""
echo "To fix:"
echo " 1. Create test file(s) for the implementation"
echo " 2. Commit tests first: git add <test-files> && git commit -m 'Add tests for [feature]'"
echo " 3. Then commit implementation: git add <impl-files> && git commit -m 'Implement [feature]'"
echo ""
echo "Or use: npm run generate:test <impl-file> to create a test template"
echo ""
echo "To bypass this check (not recommended):"
echo " git commit --no-verify"
echo ""
exit 1
fi
echo "✅ TDD Validation Passed: All implementation files have tests"
echo ""
exit 0

View File

@@ -0,0 +1,78 @@
#!/bin/bash
###############################################################################
# TDD Auto-Enforcer Hook
#
# Claude Code hook that automatically guides LLM to follow TDD workflow
# when implementing features.
#
# Installed by: tdd-automation skill
###############################################################################
# Get user's prompt
USER_PROMPT="$1"
# Keywords that indicate implementation work
IMPLEMENTATION_KEYWORDS="implement|add|create|build|feature|write.*code|develop|make.*function|make.*component"
# Check if prompt is about implementing something
if echo "$USER_PROMPT" | grep -qiE "$IMPLEMENTATION_KEYWORDS"; then
cat <<'EOF'
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ TDD ENFORCEMENT ACTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
You are about to implement code. You MUST follow TDD red-green-refactor:
📋 Required Workflow:
1. 🔴 RED Phase (First - DO NOT SKIP)
└─ Write a FAILING test before any implementation
└─ Run test to verify it fails: npm run test:red <test-file>
└─ Use TodoWrite to track this phase
2. 🟢 GREEN Phase (After RED verified)
└─ Write MINIMAL code to make test pass
└─ Run test to verify it passes: npm run test:green <test-file>
└─ Use TodoWrite to track this phase
3. 🔵 REFACTOR Phase (After GREEN verified)
└─ Improve code quality while keeping tests green
└─ Run all tests: npm run test:tdd
└─ Use TodoWrite to track this phase
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚨 CRITICAL RULES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ NEVER write implementation code before writing the test
❌ NEVER skip RED phase verification (must see test fail)
❌ NEVER skip GREEN phase verification (must see test pass)
✅ ALWAYS use TodoWrite to track RED-GREEN-REFACTOR phases
✅ ALWAYS run tests to verify each phase transition
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📝 TodoWrite Template (Use This)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Create a todo list with these phases before starting:
[ ] RED: Write failing test for [feature name]
[ ] Verify test fails with expected error
[ ] GREEN: Implement minimal code to pass test
[ ] Verify test passes
[ ] REFACTOR: Improve code quality
[ ] Verify all tests still pass
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Start with RED phase. Create the test file first.
EOF
fi
# Exit successfully (allow prompt to continue)
exit 0

View File

@@ -0,0 +1,163 @@
#!/usr/bin/env node
/**
* Project Type Detector
*
* Detects project type and test framework to provide appropriate TDD configuration.
*/
const fs = require('fs');
const path = require('path');
class ProjectDetector {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.packageJsonPath = path.join(projectRoot, 'package.json');
}
/**
* Detect project configuration
* @returns {object} Project details
*/
detect() {
const result = {
type: 'unknown',
testFramework: 'unknown',
hasPackageJson: false,
hasGit: false,
hasTypeScript: false,
projectName: path.basename(this.projectRoot),
recommendations: []
};
// Check for package.json
if (fs.existsSync(this.packageJsonPath)) {
result.hasPackageJson = true;
this.analyzePackageJson(result);
}
// Check for git
if (fs.existsSync(path.join(this.projectRoot, '.git'))) {
result.hasGit = true;
} else {
result.recommendations.push('Initialize git repository for version control');
}
// Check for TypeScript
if (fs.existsSync(path.join(this.projectRoot, 'tsconfig.json'))) {
result.hasTypeScript = true;
}
// Detect project type
result.type = this.detectProjectType(result);
// Add recommendations
if (result.testFramework === 'unknown') {
result.recommendations.push('Install a test framework (vitest recommended)');
}
return result;
}
/**
* Analyze package.json for dependencies and scripts
* @param {object} result - Result object to populate
*/
analyzePackageJson(result) {
try {
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
result.projectName = packageJson.name || result.projectName;
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
// Detect test framework
if (allDeps['vitest']) {
result.testFramework = 'vitest';
} else if (allDeps['jest']) {
result.testFramework = 'jest';
} else if (allDeps['mocha']) {
result.testFramework = 'mocha';
} else if (allDeps['ava']) {
result.testFramework = 'ava';
}
// Detect framework/type
if (allDeps['react']) {
result.framework = 'react';
} else if (allDeps['vue']) {
result.framework = 'vue';
} else if (allDeps['@angular/core']) {
result.framework = 'angular';
} else if (allDeps['express']) {
result.framework = 'express';
} else if (allDeps['fastify']) {
result.framework = 'fastify';
} else if (allDeps['next']) {
result.framework = 'next';
}
// Check for existing test scripts
result.hasTestScript = packageJson.scripts && packageJson.scripts.test;
} catch (error) {
console.error('Error parsing package.json:', error.message);
}
}
/**
* Determine primary project type
* @param {object} result - Detection results
* @returns {string} Project type
*/
detectProjectType(result) {
if (result.framework === 'react') return 'react';
if (result.framework === 'vue') return 'vue';
if (result.framework === 'angular') return 'angular';
if (result.framework === 'next') return 'nextjs';
if (result.framework === 'express' || result.framework === 'fastify') return 'nodejs-backend';
if (result.hasTypeScript) return 'typescript';
if (result.hasPackageJson) return 'nodejs';
return 'unknown';
}
/**
* Get recommended test command for project
* @returns {string} Test command
*/
getTestCommand() {
const result = this.detect();
switch (result.testFramework) {
case 'vitest':
return 'vitest --run';
case 'jest':
return 'jest';
case 'mocha':
return 'mocha';
case 'ava':
return 'ava';
default:
return 'npm test';
}
}
/**
* Get recommended test file extension
* @returns {string} File extension
*/
getTestExtension() {
const result = this.detect();
if (result.hasTypeScript) {
return '.test.ts';
}
return '.test.js';
}
}
module.exports = ProjectDetector;

View File

@@ -0,0 +1,305 @@
#!/usr/bin/env node
/**
* Hook Installer
*
* Installs git hooks and Claude hooks for TDD enforcement
*/
const fs = require('fs');
const path = require('path');
class HookInstaller {
constructor(projectRoot, skillRoot) {
this.projectRoot = projectRoot;
this.skillRoot = skillRoot;
this.gitHooksDir = path.join(projectRoot, '.git', 'hooks');
this.claudeHooksDir = path.join(projectRoot, '.claude', 'hooks');
this.tddAutomationDir = path.join(projectRoot, '.tdd-automation');
}
/**
* Install all TDD hooks
* @returns {object} Installation result
*/
installAll() {
const results = {
success: true,
installed: [],
failed: [],
skipped: []
};
// Create directories
this.ensureDirectories();
// Copy TDD automation files
this.copyTddAutomationFiles(results);
// Install git pre-commit hook
this.installGitPreCommit(results);
// Install Claude hooks (if Claude hooks directory exists)
if (fs.existsSync(path.dirname(this.claudeHooksDir))) {
this.installClaudeHooks(results);
}
results.success = results.failed.length === 0;
return results;
}
/**
* Ensure required directories exist
*/
ensureDirectories() {
const dirs = [
this.tddAutomationDir,
path.join(this.tddAutomationDir, 'scripts'),
path.join(this.tddAutomationDir, 'templates'),
path.join(this.tddAutomationDir, 'hooks')
];
for (const dir of dirs) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
// Create Claude hooks directory if .claude exists
if (fs.existsSync(path.join(this.projectRoot, '.claude'))) {
if (!fs.existsSync(this.claudeHooksDir)) {
fs.mkdirSync(this.claudeHooksDir, { recursive: true });
}
}
}
/**
* Copy TDD automation files to project
* @param {object} results - Results object to update
*/
copyTddAutomationFiles(results) {
const scriptsDir = path.join(this.skillRoot, 'scripts');
const templatesDir = path.join(this.skillRoot, 'templates');
const targetScriptsDir = path.join(this.tddAutomationDir, 'scripts');
const targetTemplatesDir = path.join(this.tddAutomationDir, 'templates');
try {
// Copy scripts
if (fs.existsSync(scriptsDir)) {
this.copyDirectory(scriptsDir, targetScriptsDir);
results.installed.push('TDD automation scripts');
}
// Copy templates
if (fs.existsSync(templatesDir)) {
this.copyDirectory(templatesDir, targetTemplatesDir);
results.installed.push('TDD templates');
}
} catch (error) {
results.failed.push(`Copy TDD files: ${error.message}`);
}
}
/**
* Install git pre-commit hook
* @param {object} results - Results object to update
*/
installGitPreCommit(results) {
if (!fs.existsSync(this.gitHooksDir)) {
results.skipped.push('Git pre-commit hook (no .git directory)');
return;
}
const hookPath = path.join(this.gitHooksDir, 'pre-commit');
const templatePath = path.join(this.skillRoot, 'templates', 'pre-commit.sh');
try {
// Check if hook already exists
if (fs.existsSync(hookPath)) {
const existing = fs.readFileSync(hookPath, 'utf-8');
// Check if our TDD hook is already installed
if (existing.includes('TDD_AUTOMATION')) {
results.skipped.push('Git pre-commit hook (already installed)');
return;
}
// Backup existing hook
const backupPath = hookPath + '.backup';
fs.copyFileSync(hookPath, backupPath);
results.installed.push(`Git pre-commit hook backup (${backupPath})`);
// Append our hook to existing
const tddHook = fs.readFileSync(templatePath, 'utf-8');
fs.appendFileSync(hookPath, '\n\n' + tddHook);
results.installed.push('Git pre-commit hook (appended)');
} else {
// Install new hook
fs.copyFileSync(templatePath, hookPath);
fs.chmodSync(hookPath, '755');
results.installed.push('Git pre-commit hook (new)');
}
} catch (error) {
results.failed.push(`Git pre-commit hook: ${error.message}`);
}
}
/**
* Install Claude hooks
* @param {object} results - Results object to update
*/
installClaudeHooks(results) {
const hooksToInstall = [
{
name: 'tdd-auto-enforcer.sh',
description: 'TDD auto-enforcer hook'
}
];
for (const hook of hooksToInstall) {
const sourcePath = path.join(this.skillRoot, 'templates', hook.name);
const targetPath = path.join(this.claudeHooksDir, hook.name);
try {
if (fs.existsSync(targetPath)) {
results.skipped.push(`${hook.description} (already exists)`);
continue;
}
if (!fs.existsSync(sourcePath)) {
results.failed.push(`${hook.description} (template not found)`);
continue;
}
fs.copyFileSync(sourcePath, targetPath);
fs.chmodSync(targetPath, '755');
results.installed.push(hook.description);
} catch (error) {
results.failed.push(`${hook.description}: ${error.message}`);
}
}
}
/**
* Copy directory recursively
* @param {string} src - Source directory
* @param {string} dest - Destination directory
*/
copyDirectory(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
this.copyDirectory(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
// Make scripts executable
if (entry.name.endsWith('.sh') || entry.name.endsWith('.js')) {
try {
fs.chmodSync(destPath, '755');
} catch (error) {
// Ignore chmod errors on systems that don't support it
}
}
}
}
}
/**
* Uninstall all TDD hooks
* @returns {object} Uninstallation result
*/
uninstallAll() {
const results = {
success: true,
removed: [],
failed: []
};
// Remove git pre-commit hook (only our section)
this.uninstallGitPreCommit(results);
// Remove Claude hooks
this.uninstallClaudeHooks(results);
// Remove TDD automation directory
if (fs.existsSync(this.tddAutomationDir)) {
try {
fs.rmSync(this.tddAutomationDir, { recursive: true, force: true });
results.removed.push('.tdd-automation directory');
} catch (error) {
results.failed.push(`.tdd-automation directory: ${error.message}`);
}
}
results.success = results.failed.length === 0;
return results;
}
/**
* Uninstall git pre-commit hook
* @param {object} results - Results object to update
*/
uninstallGitPreCommit(results) {
const hookPath = path.join(this.gitHooksDir, 'pre-commit');
if (!fs.existsSync(hookPath)) {
return;
}
try {
const content = fs.readFileSync(hookPath, 'utf-8');
// Check if our hook is installed
if (!content.includes('TDD_AUTOMATION')) {
return;
}
// If the entire file is our hook, remove it
if (content.trim().startsWith('#!/bin/bash') && content.includes('TDD_AUTOMATION')) {
// Check if there's a backup
const backupPath = hookPath + '.backup';
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, hookPath);
results.removed.push('Git pre-commit hook (restored from backup)');
} else {
fs.unlinkSync(hookPath);
results.removed.push('Git pre-commit hook (removed)');
}
}
} catch (error) {
results.failed.push(`Git pre-commit hook: ${error.message}`);
}
}
/**
* Uninstall Claude hooks
* @param {object} results - Results object to update
*/
uninstallClaudeHooks(results) {
const hooksToRemove = ['tdd-auto-enforcer.sh'];
for (const hookName of hooksToRemove) {
const hookPath = path.join(this.claudeHooksDir, hookName);
if (fs.existsSync(hookPath)) {
try {
fs.unlinkSync(hookPath);
results.removed.push(`Claude hook: ${hookName}`);
} catch (error) {
results.failed.push(`Claude hook ${hookName}: ${error.message}`);
}
}
}
}
}
module.exports = HookInstaller;

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env node
/**
* CLAUDE.md Merger
*
* Safely merges TDD automation configuration into existing CLAUDE.md files
* while preserving all existing content.
*/
const fs = require('fs');
const path = require('path');
class ClaudeMdMerger {
constructor(existingContent = '') {
this.existingContent = existingContent;
this.version = '0.2.0';
}
/**
* Merge TDD section with existing content
* @returns {string} Merged CLAUDE.md content
*/
merge() {
// Strategy: Append TDD section with clear delimiters
// This preserves ALL existing content
const separator = '\n\n' + '='.repeat(80) + '\n\n';
const timestamp = new Date().toISOString();
const mergedContent = [
this.existingContent.trimEnd(),
separator,
'<!-- TDD_AUTOMATION_START -->',
`<!-- Added by tdd-automation skill -->`,
`<!-- Version: ${this.version} -->`,
`<!-- Date: ${timestamp} -->`,
'',
this.buildTddSection(),
'',
'<!-- TDD_AUTOMATION_END -->',
].join('\n');
return mergedContent;
}
/**
* Build the complete TDD automation section
* @returns {string} TDD section content
*/
buildTddSection() {
return `# TDD Red-Green-Refactor Automation (Auto-Installed)
## ⚠️ CRITICAL: TDD is MANDATORY for all feature development
\`\`\`yaml
tdd-automation-version: ${this.version}
tdd-enforcement-level: strict
tdd-phase-tracking: required
\`\`\`
## Development Workflow (STRICTLY ENFORCED)
When implementing ANY new feature or functionality, you MUST follow this sequence:
### Phase 1: RED (Write Failing Test First)
**ALWAYS START HERE. DO NOT SKIP.**
1. **Create or modify the test file BEFORE writing ANY implementation code**
2. Write a test that describes the expected behavior
3. Run the test: \`npm run test:tdd -- <test-file>\`
4. **VERIFY the test fails for the RIGHT reason** (not syntax error, but missing functionality)
5. Use TodoWrite to mark RED phase complete
**DO NOT proceed to implementation until test fails correctly.**
#### RED Phase Checklist:
- [ ] Test file created/modified
- [ ] Test describes expected behavior clearly
- [ ] Test executed and verified to fail
- [ ] Failure reason is correct (missing functionality, not syntax error)
- [ ] RED phase marked in TodoWrite
### Phase 2: GREEN (Minimal Implementation)
**Only after RED phase is verified:**
1. Write ONLY enough code to make the failing test pass
2. No extra features, no "while we're here" additions
3. Keep it simple and focused on passing the test
4. Run test: \`npm run test:tdd -- <test-file>\`
5. **VERIFY the test now passes**
6. Use TodoWrite to mark GREEN phase complete
#### GREEN Phase Checklist:
- [ ] Minimal code written (no extras)
- [ ] Test executed and verified to pass
- [ ] All existing tests still pass
- [ ] GREEN phase marked in TodoWrite
### Phase 3: REFACTOR (Improve Quality)
**Only after GREEN phase is verified:**
1. Improve code structure, naming, and quality
2. Extract duplicated code
3. Simplify complex logic
4. Run full test suite: \`npm run test:tdd\`
5. **VERIFY all tests still pass after refactoring**
6. Use TodoWrite to mark REFACTOR phase complete
#### REFACTOR Phase Checklist:
- [ ] Code structure improved
- [ ] Duplicated code extracted
- [ ] Complex logic simplified
- [ ] All tests still pass
- [ ] REFACTOR phase marked in TodoWrite
## Pre-Implementation TodoWrite Template (ALWAYS Use)
Before writing ANY implementation code, create a todo list with these phases:
\`\`\`markdown
[ ] RED: Write failing test for [feature name]
[ ] Verify test fails with expected error message
[ ] GREEN: Implement minimal code to pass test
[ ] Verify test passes
[ ] REFACTOR: Improve code quality (if needed)
[ ] Verify all tests still pass
\`\`\`
**Example:**
\`\`\`markdown
[ ] RED: Write failing test for user authentication
[ ] Verify test fails with "authenticateUser is not defined"
[ ] GREEN: Implement minimal authenticateUser function
[ ] Verify test passes
[ ] REFACTOR: Extract validation logic to separate function
[ ] Verify all tests still pass
\`\`\`
## Critical Rules (NEVER Violate)
- ❌ **NEVER** write implementation code before writing the test
- ❌ **NEVER** skip the RED phase verification
- ❌ **NEVER** skip the GREEN phase verification
- ❌ **NEVER** skip TodoWrite phase tracking
- ✅ **ALWAYS** run tests to verify RED and GREEN states
- ✅ **ALWAYS** use TodoWrite to track TDD phases
- ✅ **ALWAYS** create test files BEFORE implementation files
- ✅ **ALWAYS** commit tests BEFORE committing implementation
- ✅ **ALWAYS** use semantic test names: \`"should [behavior] when [condition]"\`
## Test Execution Commands
This project has been configured with TDD-optimized test scripts:
\`\`\`bash
# Run all tests once (non-watch mode)
npm run test:tdd
# Run tests in watch mode for development
npm run test:tdd:watch
# Run specific test file (RED/GREEN phase)
npm run test:tdd -- path/to/test.test.ts
# Validate TDD compliance
npm run validate:tdd
\`\`\`
## File Structure Convention
For every implementation file, there must be a corresponding test file:
\`\`\`
src/features/auth/login.ts → Implementation
src/features/auth/login.test.ts → Tests (created FIRST)
src/utils/validation.ts → Implementation
src/utils/validation.test.ts → Tests (created FIRST)
\`\`\`
## Test Naming Convention
Use the pattern: \`"should [behavior] when [condition]"\`
**Good Examples:**
- \`"should return true when email is valid"\`
- \`"should throw error when password is too short"\`
- \`"should update user profile when data is valid"\`
**Bad Examples:**
- \`"test email validation"\` (not descriptive)
- \`"it works"\` (not specific)
- \`"validates email"\` (missing condition)
## Violation Handling
If you accidentally start writing implementation before tests:
1. **STOP immediately**
2. Create the test file first
3. Write the failing test
4. Verify RED state
5. **THEN** proceed with implementation
## TDD Automation Features
This installation includes:
- ✅ **Pre-commit hooks**: Validate tests exist before committing implementation
- ✅ **Test scaffolding**: Generate test file templates with \`npm run generate:test <file>\`
- ✅ **TDD validation**: Check compliance with \`npm run validate:tdd\`
- ✅ **Rollback capability**: Restore previous CLAUDE.md if needed
## Help & Maintenance
### Check TDD Compliance
\`\`\`bash
npm run validate:tdd
\`\`\`
### Generate Test Template
\`\`\`bash
npm run generate:test src/features/auth/login.ts
\`\`\`
### Rollback This Automation
\`\`\`bash
node .tdd-automation/scripts/rollback-tdd.js
\`\`\`
### Remove TDD Section
\`\`\`bash
node .tdd-automation/scripts/remove-tdd-section.js
\`\`\`
### View Backups
\`\`\`bash
ls -lh .claude/CLAUDE.md.backup.*
\`\`\`
## Documentation
- Setup documentation: \`.tdd-automation/README.md\`
- Pre-commit hook: \`.git/hooks/pre-commit\`
- Test templates: \`.tdd-automation/templates/\`
## Success Metrics
Track these metrics to measure TDD adherence:
- Test-first adherence rate: >95%
- RED-GREEN-REFACTOR cycle completion: >90%
- Defect escape rate: <2%
- Test coverage: >80%
---
**Note:** This section was automatically added by the tdd-automation skill.
For support or to report issues, see: .tdd-automation/README.md`;
}
/**
* Create standalone TDD section (for new CLAUDE.md files)
* @returns {string} TDD section content without existing content
*/
static createNew() {
const merger = new ClaudeMdMerger('');
const timestamp = new Date().toISOString();
return [
'<!-- TDD_AUTOMATION_START -->',
`<!-- Added by tdd-automation skill -->`,
`<!-- Version: 0.2.0 -->`,
`<!-- Date: ${timestamp} -->`,
'',
merger.buildTddSection(),
'',
'<!-- TDD_AUTOMATION_END -->',
].join('\n');
}
}
module.exports = ClaudeMdMerger;

View File

@@ -0,0 +1,186 @@
#!/usr/bin/env node
/**
* Package.json Updater
*
* Safely adds TDD-related npm scripts to package.json
*/
const fs = require('fs');
const path = require('path');
class PackageJsonUpdater {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.packageJsonPath = path.join(projectRoot, 'package.json');
}
/**
* Add TDD scripts to package.json
* @param {string} testCommand - Base test command (e.g., 'vitest --run')
* @returns {object} Update result
*/
addTddScripts(testCommand = 'vitest --run') {
if (!fs.existsSync(this.packageJsonPath)) {
return {
success: false,
reason: 'package.json not found'
};
}
try {
// Create backup
const backupPath = this.packageJsonPath + '.backup';
fs.copyFileSync(this.packageJsonPath, backupPath);
// Read and parse package.json
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
// Ensure scripts object exists
if (!packageJson.scripts) {
packageJson.scripts = {};
}
// Define TDD scripts
const tddScripts = {
'test:tdd': testCommand,
'test:tdd:watch': testCommand.replace('--run', '').trim(),
'test:red': `${testCommand} --reporter=verbose`,
'test:green': `${testCommand} --reporter=verbose`,
'validate:tdd': 'node .tdd-automation/scripts/validate-tdd.js',
'generate:test': 'node .tdd-automation/scripts/generate-test.js'
};
// Track what was added
const added = [];
const skipped = [];
// Add scripts that don't exist
for (const [key, value] of Object.entries(tddScripts)) {
if (!packageJson.scripts[key]) {
packageJson.scripts[key] = value;
added.push(key);
} else {
skipped.push(key);
}
}
// Write updated package.json with pretty formatting
fs.writeFileSync(
this.packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf-8'
);
return {
success: true,
added,
skipped,
backup: backupPath
};
} catch (error) {
return {
success: false,
reason: error.message
};
}
}
/**
* Remove TDD scripts from package.json
* @returns {object} Removal result
*/
removeTddScripts() {
if (!fs.existsSync(this.packageJsonPath)) {
return {
success: false,
reason: 'package.json not found'
};
}
try {
// Create backup
const backupPath = this.packageJsonPath + '.backup';
fs.copyFileSync(this.packageJsonPath, backupPath);
// Read and parse package.json
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
if (!packageJson.scripts) {
return {
success: true,
removed: [],
backup: backupPath
};
}
// Define TDD script keys to remove
const tddScriptKeys = [
'test:tdd',
'test:tdd:watch',
'test:red',
'test:green',
'validate:tdd',
'generate:test'
];
// Track what was removed
const removed = [];
// Remove TDD scripts
for (const key of tddScriptKeys) {
if (packageJson.scripts[key]) {
delete packageJson.scripts[key];
removed.push(key);
}
}
// Write updated package.json
fs.writeFileSync(
this.packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n',
'utf-8'
);
return {
success: true,
removed,
backup: backupPath
};
} catch (error) {
return {
success: false,
reason: error.message
};
}
}
/**
* Check if TDD scripts are already installed
* @returns {boolean} True if TDD scripts exist
*/
hasTddScripts() {
if (!fs.existsSync(this.packageJsonPath)) {
return false;
}
try {
const packageJson = JSON.parse(fs.readFileSync(this.packageJsonPath, 'utf-8'));
if (!packageJson.scripts) {
return false;
}
// Check if any TDD scripts exist
const tddScriptKeys = ['test:tdd', 'validate:tdd', 'generate:test'];
return tddScriptKeys.some(key => packageJson.scripts[key]);
} catch (error) {
return false;
}
}
}
module.exports = PackageJsonUpdater;

View File

@@ -0,0 +1,267 @@
#!/usr/bin/env node
/**
* CLAUDE.md Validator
*
* Validates and manages CLAUDE.md files with backup and rollback capabilities.
* Ensures safe installation of TDD automation without data loss.
*/
const fs = require('fs');
const path = require('path');
class ClaudeMdValidator {
constructor(projectRoot) {
this.projectRoot = projectRoot;
this.claudeDir = path.join(projectRoot, '.claude');
this.claudeMdPath = path.join(this.claudeDir, 'CLAUDE.md');
}
/**
* Validate current CLAUDE.md state and determine installation strategy
* @returns {object} Validation result with strategy recommendation
*/
validate() {
const result = {
exists: false,
hasExistingContent: false,
hasTddSection: false,
needsBackup: false,
strategy: 'create', // create | merge | skip | abort
warnings: [],
size: 0,
path: this.claudeMdPath
};
// Check if CLAUDE.md exists
if (!fs.existsSync(this.claudeMdPath)) {
result.strategy = 'create';
result.warnings.push('No CLAUDE.md found - will create new file');
return result;
}
result.exists = true;
const content = fs.readFileSync(this.claudeMdPath, 'utf-8');
result.size = content.length;
// Check if file has meaningful content (not just empty or whitespace)
if (content.trim().length > 0) {
result.hasExistingContent = true;
result.needsBackup = true;
}
// Check if TDD automation is already installed
if (this.detectTddSection(content)) {
result.hasTddSection = true;
result.strategy = 'skip';
result.warnings.push('TDD automation already installed in CLAUDE.md');
return result;
}
// Determine merge strategy
if (result.hasExistingContent) {
result.strategy = 'merge';
result.warnings.push(`Existing CLAUDE.md found (${result.size} bytes) - will merge`);
} else {
result.strategy = 'create';
result.warnings.push('Empty CLAUDE.md found - will replace');
}
return result;
}
/**
* Detect if TDD automation section exists in content
* @param {string} content - CLAUDE.md content to check
* @returns {boolean} True if TDD section detected
*/
detectTddSection(content) {
const markers = [
'<!-- TDD_AUTOMATION_START -->',
'TDD Red-Green-Refactor Automation (Auto-Installed)',
'tdd-automation-version:'
];
return markers.some(marker => content.includes(marker));
}
/**
* Create timestamped backup of current CLAUDE.md
* @returns {object} Backup result with success status and path
*/
createBackup() {
if (!fs.existsSync(this.claudeMdPath)) {
return {
success: false,
reason: 'No file to backup',
path: null
};
}
// Ensure .claude directory exists
if (!fs.existsSync(this.claudeDir)) {
fs.mkdirSync(this.claudeDir, { recursive: true });
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = path.join(
this.claudeDir,
`CLAUDE.md.backup.${timestamp}`
);
try {
fs.copyFileSync(this.claudeMdPath, backupPath);
const originalSize = fs.statSync(this.claudeMdPath).size;
return {
success: true,
path: backupPath,
originalSize,
timestamp
};
} catch (error) {
return {
success: false,
reason: error.message,
path: null
};
}
}
/**
* List all backup files sorted by date (newest first)
* @returns {array} Array of backup file info objects
*/
listBackups() {
if (!fs.existsSync(this.claudeDir)) {
return [];
}
try {
return fs.readdirSync(this.claudeDir)
.filter(f => f.startsWith('CLAUDE.md.backup'))
.map(f => ({
name: f,
path: path.join(this.claudeDir, f),
created: fs.statSync(path.join(this.claudeDir, f)).mtime,
size: fs.statSync(path.join(this.claudeDir, f)).size
}))
.sort((a, b) => b.created - a.created);
} catch (error) {
console.error('Error listing backups:', error.message);
return [];
}
}
/**
* Restore CLAUDE.md from backup file
* @param {string} backupPath - Path to backup file
* @returns {object} Rollback result with success status
*/
rollback(backupPath) {
if (!fs.existsSync(backupPath)) {
return {
success: false,
reason: 'Backup file not found',
path: backupPath
};
}
try {
// Ensure .claude directory exists
if (!fs.existsSync(this.claudeDir)) {
fs.mkdirSync(this.claudeDir, { recursive: true });
}
fs.copyFileSync(backupPath, this.claudeMdPath);
return {
success: true,
restoredFrom: backupPath,
size: fs.statSync(this.claudeMdPath).size
};
} catch (error) {
return {
success: false,
reason: error.message,
path: backupPath
};
}
}
/**
* Remove TDD section from CLAUDE.md
* @returns {object} Removal result with success status
*/
removeTddSection() {
if (!fs.existsSync(this.claudeMdPath)) {
return {
success: false,
reason: 'CLAUDE.md not found'
};
}
const content = fs.readFileSync(this.claudeMdPath, 'utf-8');
// Check if TDD section exists
if (!this.detectTddSection(content)) {
return {
success: false,
reason: 'No TDD section found in CLAUDE.md'
};
}
// Create backup before removal
const backup = this.createBackup();
if (!backup.success) {
return {
success: false,
reason: `Backup failed: ${backup.reason}`
};
}
// Remove TDD section using markers
const startMarker = '<!-- TDD_AUTOMATION_START -->';
const endMarker = '<!-- TDD_AUTOMATION_END -->';
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx === -1 || endIdx === -1) {
return {
success: false,
reason: 'Could not find section markers for clean removal'
};
}
// Remove section and clean up extra whitespace
const beforeSection = content.substring(0, startIdx).trimEnd();
const afterSection = content.substring(endIdx + endMarker.length).trimStart();
const cleanedContent = beforeSection + (afterSection ? '\n\n' + afterSection : '');
try {
fs.writeFileSync(this.claudeMdPath, cleanedContent, 'utf-8');
const originalSize = content.length;
const newSize = cleanedContent.length;
return {
success: true,
backup: backup.path,
originalSize,
newSize,
removed: originalSize - newSize
};
} catch (error) {
// Rollback on error
this.rollback(backup.path);
return {
success: false,
reason: `Write failed: ${error.message}`
};
}
}
}
module.exports = ClaudeMdValidator;