feat: initial Claude Code configuration scaffold
Comprehensive Claude Code guidance system with: - 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit - 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#), infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD), testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability) - 3 hooks: secret detection, auto-formatting, TDD git pre-commit - Strict TDD enforcement with 80%+ coverage requirements - Multi-model strategy: Opus for planning, Sonnet for execution (opusplan) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
389
.claude/skills/testing/browser-testing/SKILL.md
Normal file
389
.claude/skills/testing/browser-testing/SKILL.md
Normal file
@@ -0,0 +1,389 @@
|
||||
---
|
||||
name: browser-testing
|
||||
description: Browser automation with Playwright for E2E tests and Claude's Chrome MCP for rapid development verification. Use when writing E2E tests or verifying UI during development.
|
||||
---
|
||||
|
||||
# Browser Testing Skill
|
||||
|
||||
## Two Approaches
|
||||
|
||||
### 1. Claude Chrome MCP (Development Verification)
|
||||
Quick, interactive testing during development to verify UI works before writing permanent tests.
|
||||
|
||||
### 2. Playwright (Permanent E2E Tests)
|
||||
Automated, repeatable tests that run in CI/CD.
|
||||
|
||||
---
|
||||
|
||||
## Claude Chrome MCP (Quick Verification)
|
||||
|
||||
Use Chrome MCP tools for rapid UI verification during development.
|
||||
|
||||
### Basic Workflow
|
||||
|
||||
```
|
||||
1. Navigate to page
|
||||
2. Take screenshot to verify state
|
||||
3. Find and interact with elements
|
||||
4. Verify expected outcome
|
||||
5. Record GIF for complex flows
|
||||
```
|
||||
|
||||
### Navigation
|
||||
```typescript
|
||||
// Navigate to URL
|
||||
mcp__claude-in-chrome__navigate({ url: "http://localhost:5173", tabId: 123 })
|
||||
|
||||
// Go back/forward
|
||||
mcp__claude-in-chrome__navigate({ url: "back", tabId: 123 })
|
||||
```
|
||||
|
||||
### Finding Elements
|
||||
```typescript
|
||||
// Find by natural language
|
||||
mcp__claude-in-chrome__find({ query: "login button", tabId: 123 })
|
||||
mcp__claude-in-chrome__find({ query: "email input field", tabId: 123 })
|
||||
mcp__claude-in-chrome__find({ query: "product card containing organic", tabId: 123 })
|
||||
```
|
||||
|
||||
### Reading Page State
|
||||
```typescript
|
||||
// Get accessibility tree (best for understanding structure)
|
||||
mcp__claude-in-chrome__read_page({ tabId: 123, filter: "interactive" })
|
||||
|
||||
// Get text content (for articles/text-heavy pages)
|
||||
mcp__claude-in-chrome__get_page_text({ tabId: 123 })
|
||||
```
|
||||
|
||||
### Interactions
|
||||
```typescript
|
||||
// Click element by reference
|
||||
mcp__claude-in-chrome__computer({
|
||||
action: "left_click",
|
||||
ref: "ref_42",
|
||||
tabId: 123
|
||||
})
|
||||
|
||||
// Type text
|
||||
mcp__claude-in-chrome__computer({
|
||||
action: "type",
|
||||
text: "user@example.com",
|
||||
tabId: 123
|
||||
})
|
||||
|
||||
// Fill form input
|
||||
mcp__claude-in-chrome__form_input({
|
||||
ref: "ref_15",
|
||||
value: "test@example.com",
|
||||
tabId: 123
|
||||
})
|
||||
|
||||
// Press keys
|
||||
mcp__claude-in-chrome__computer({
|
||||
action: "key",
|
||||
text: "Enter",
|
||||
tabId: 123
|
||||
})
|
||||
```
|
||||
|
||||
### Screenshots and GIFs
|
||||
```typescript
|
||||
// Take screenshot
|
||||
mcp__claude-in-chrome__computer({ action: "screenshot", tabId: 123 })
|
||||
|
||||
// Record GIF for complex interaction
|
||||
mcp__claude-in-chrome__gif_creator({ action: "start_recording", tabId: 123 })
|
||||
// ... perform actions ...
|
||||
mcp__claude-in-chrome__gif_creator({
|
||||
action: "export",
|
||||
download: true,
|
||||
filename: "login-flow.gif",
|
||||
tabId: 123
|
||||
})
|
||||
```
|
||||
|
||||
### Verification Pattern
|
||||
|
||||
```
|
||||
1. Navigate to the page
|
||||
2. Screenshot to see current state
|
||||
3. Find the element you want to interact with
|
||||
4. Interact with it
|
||||
5. Screenshot to verify the result
|
||||
6. If complex flow, record a GIF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Playwright (Permanent E2E Tests)
|
||||
|
||||
### Project Setup
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: [
|
||||
['html'],
|
||||
['junit', { outputFile: 'results.xml' }],
|
||||
],
|
||||
use: {
|
||||
baseURL: 'http://localhost:5173',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
{
|
||||
name: 'mobile',
|
||||
use: { ...devices['iPhone 13'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:5173',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Page Object Pattern
|
||||
|
||||
```typescript
|
||||
// e2e/pages/login.page.ts
|
||||
import { Page, Locator, expect } from '@playwright/test';
|
||||
|
||||
export class LoginPage {
|
||||
readonly page: Page;
|
||||
readonly emailInput: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly submitButton: Locator;
|
||||
readonly errorMessage: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.emailInput = page.getByLabel('Email');
|
||||
this.passwordInput = page.getByLabel('Password');
|
||||
this.submitButton = page.getByRole('button', { name: 'Sign in' });
|
||||
this.errorMessage = page.getByRole('alert');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/login');
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
await this.emailInput.fill(email);
|
||||
await this.passwordInput.fill(password);
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async expectError(message: string) {
|
||||
await expect(this.errorMessage).toContainText(message);
|
||||
}
|
||||
|
||||
async expectLoggedIn() {
|
||||
await expect(this.page).toHaveURL('/dashboard');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Patterns
|
||||
|
||||
```typescript
|
||||
// e2e/auth/login.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { LoginPage } from '../pages/login.page';
|
||||
|
||||
test.describe('Login', () => {
|
||||
let loginPage: LoginPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.goto();
|
||||
});
|
||||
|
||||
test('should show validation errors for empty form', async () => {
|
||||
await loginPage.submitButton.click();
|
||||
|
||||
await expect(loginPage.page.getByText('Email is required')).toBeVisible();
|
||||
await expect(loginPage.page.getByText('Password is required')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error for invalid credentials', async () => {
|
||||
await loginPage.login('wrong@example.com', 'wrongpassword');
|
||||
|
||||
await loginPage.expectError('Invalid credentials');
|
||||
});
|
||||
|
||||
test('should redirect to dashboard on successful login', async () => {
|
||||
await loginPage.login('user@example.com', 'correctpassword');
|
||||
|
||||
await loginPage.expectLoggedIn();
|
||||
await expect(loginPage.page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should persist session after page reload', async ({ page }) => {
|
||||
await loginPage.login('user@example.com', 'correctpassword');
|
||||
await loginPage.expectLoggedIn();
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page).toHaveURL('/dashboard');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### API Mocking
|
||||
|
||||
```typescript
|
||||
// e2e/dashboard/projects.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Projects Dashboard', () => {
|
||||
test('should display projects from API', async ({ page }) => {
|
||||
// Mock API response
|
||||
await page.route('**/api/projects', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify([
|
||||
{ id: '1', name: 'Project A', status: 'active' },
|
||||
{ id: '2', name: 'Project B', status: 'completed' },
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByText('Project A')).toBeVisible();
|
||||
await expect(page.getByText('Project B')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error state on API failure', async ({ page }) => {
|
||||
await page.route('**/api/projects', async (route) => {
|
||||
await route.fulfill({ status: 500 });
|
||||
});
|
||||
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page.getByRole('alert')).toContainText('Failed to load projects');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Visual Regression Testing
|
||||
|
||||
```typescript
|
||||
// e2e/visual/components.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Visual Regression', () => {
|
||||
test('login page matches snapshot', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
await expect(page).toHaveScreenshot('login-page.png');
|
||||
});
|
||||
|
||||
test('dashboard matches snapshot', async ({ page }) => {
|
||||
// Setup authenticated state
|
||||
await page.goto('/dashboard');
|
||||
|
||||
await expect(page).toHaveScreenshot('dashboard.png', {
|
||||
mask: [page.locator('.timestamp')], // Mask dynamic content
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Accessibility Testing
|
||||
|
||||
```typescript
|
||||
// e2e/a11y/accessibility.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
|
||||
test.describe('Accessibility', () => {
|
||||
test('login page should have no accessibility violations', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test('dashboard should have no accessibility violations', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
|
||||
const results = await new AxeBuilder({ page })
|
||||
.exclude('.third-party-widget') // Exclude third-party content
|
||||
.analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Playwright
|
||||
npx playwright test # Run all tests
|
||||
npx playwright test --project=chromium # Specific browser
|
||||
npx playwright test login.spec.ts # Specific file
|
||||
npx playwright test --ui # Interactive UI mode
|
||||
npx playwright test --debug # Debug mode
|
||||
npx playwright show-report # View HTML report
|
||||
npx playwright codegen # Generate tests by recording
|
||||
|
||||
# Update snapshots
|
||||
npx playwright test --update-snapshots
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use role-based locators** (accessible to all)
|
||||
```typescript
|
||||
page.getByRole('button', { name: 'Submit' })
|
||||
page.getByLabel('Email')
|
||||
```
|
||||
|
||||
2. **Wait for specific conditions** (not arbitrary timeouts)
|
||||
```typescript
|
||||
await expect(page.getByText('Success')).toBeVisible();
|
||||
```
|
||||
|
||||
3. **Isolate tests** (each test sets up its own state)
|
||||
```typescript
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
});
|
||||
```
|
||||
|
||||
4. **Use Page Objects** (DRY, maintainable)
|
||||
```typescript
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.login(email, password);
|
||||
```
|
||||
|
||||
5. **Mock external APIs** (fast, reliable)
|
||||
```typescript
|
||||
await page.route('**/api/**', handler);
|
||||
```
|
||||
Reference in New Issue
Block a user