Files
opencode-skill/skills/testing-master/SKILL.md
Kunthawat Greethong 7edf5bc4d0 feat: Import 35+ skills, merge duplicates, add openclaw installer
Major updates:
- Added 35+ new skills from awesome-opencode-skills and antigravity repos
- Merged SEO skills into seo-master
- Merged architecture skills into architecture
- Merged security skills into security-auditor and security-coder
- Merged testing skills into testing-master and testing-patterns
- Merged pentesting skills into pentesting
- Renamed website-creator to thai-frontend-dev
- Replaced skill-creator with github version
- Removed Chutes references (use MiniMax API instead)
- Added install-openclaw-skills.sh for cross-platform installation
- Updated .env.example with MiniMax API credentials
2026-03-26 11:37:39 +07:00

314 lines
7.4 KiB
Markdown

---
name: testing-master
description: |
Master testing skill combining TDD, E2E testing, Playwright, and webapp testing.
Use when writing tests, setting up testing infrastructure, or fixing failing tests.
---
# Testing Master
Comprehensive testing skill combining: TDD, E2E testing, Playwright, webapp testing, and testing patterns.
---
## Quick Reference
| Task | Use Section |
|------|-------------|
| Write tests first | **TDD** |
| E2E browser tests | **E2E Testing** |
| Playwright automation | **Playwright** |
| Fix failing tests | **Test Fixing** |
| Generate unit tests | **Unit Testing** |
| Web app testing | **Webapp Testing** |
---
## Test-Driven Development (TDD)
### Core Principle
> "Write the test first. Watch it fail. Write minimal code to pass."
### The Iron Law
```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```
### TDD Cycle (Red-Green-Refactor)
1. **RED** - Write a failing test
2. **GREEN** - Write minimal code to pass
3. **REFACTOR** - Improve code while keeping tests passing
### When to Use TDD
- ✅ New features
- ✅ Bug fixes
- ✅ Refactoring
- ✅ Behavior changes
- ❌ Throwaway prototypes
- ❌ Generated code
- ❌ Configuration files
### Example: TDD Cycle
```javascript
// 1. RED - Write failing test
describe('Calculator', () => {
it('should add two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});
});
// 2. GREEN - Minimal implementation
class Calculator {
add(a, b) {
return a + b;
}
}
// 3. REFACTOR - Improve (if needed)
```
### Test Naming Conventions
```javascript
describe('UserService', () => {
it('should return null for non-existent user', () => {});
it('should hash password before storing', () => {});
it('should throw ValidationError for invalid email', () => {});
});
```
---
## E2E Testing
### Playwright Setup
```bash
npm init playwright@latest
npx playwright install chromium
```
### Basic Test Structure
```javascript
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('should login with valid credentials', async ({ page }) => {
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.fill('[name="email"]', 'invalid@example.com');
await page.fill('[name="password"]', 'wrong');
await page.click('[type="submit"]');
await expect(page.locator('.error')).toContainText('Invalid credentials');
});
});
```
### Page Object Model
```javascript
// pages/LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
this.emailInput = page.locator('[name="email"]');
this.passwordInput = page.locator('[name="password"]');
this.submitButton = page.locator('[type="submit"]');
}
async login(email, password) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// test.js
test('login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('user@example.com', 'password123');
});
```
### Visual Regression Testing
```javascript
import { test, expect } from '@playwright/test';
test('homepage visual regression', async ({ page }) => {
await page.goto('/');
const screenshot = await page.screenshot();
expect(screenshot).toMatchSnapshot('homepage.png');
});
```
### Cross-Browser Testing
```javascript
test.describe('Browser Compatibility', () => {
test('works on Chrome', async ({ browserName }) => {
test.skip(browserName !== 'chromium', 'Skip on non-Chrome');
// Chrome-specific test
});
});
```
---
## Playwright Testing
### Locators (Priority Order)
1. **role** - `getByRole('button', { name: 'Submit' })`
2. **label** - `getByLabel('Email')`
3. **placeholder** - `getByPlaceholder('Enter email')`
4. **text** - `getByText('Sign in')`
5. **testid** - `getByTestId('submit-btn')`
6. **CSS** - `locator('.submit')`
7. **XPath** - `locator('//button[@type="submit"]')`
### Common Actions
```javascript
// Click
await page.click('button');
// Fill input
await page.fill('input[name="email"]', 'test@example.com');
// Select
await page.selectOption('select', 'Option 1');
// Checkbox
await page.check('input[type="checkbox"]');
// Hover
await page.hover('.dropdown');
// Drag and drop
await page.dragAndDrop('.source', '.target');
```
### Assertions
```javascript
expect(locator).toBeVisible()
expect(locator).toBeHidden()
expect(locator).toBeEnabled()
expect(locator).toBeDisabled()
expect(locator).toHaveText('expected')
expect(locator).toContainText('partial')
expect(locator).toHaveValue('value')
expect(locator).toHaveCount(5)
expect(page).toHaveURL('**/dashboard')
expect(page).toHaveTitle('Dashboard')
```
### Network Interception
```javascript
await page.route('**/api/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ data: 'mocked' }),
});
});
```
### File Upload
```javascript
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
```
---
## Webapp Testing
### Test Coverage Checklist
- [ ] Homepage loads
- [ ] Navigation works
- [ ] Forms submit correctly
- [ ] Validation messages appear
- [ ] Error states handled
- [ ] Loading states work
- [ ] Authentication flows
- [ ] Responsive design
- [ ] Accessibility (a11y)
- [ ] Performance
### Accessibility Testing
```javascript
test('accessibility check', async ({ page }) => {
await page.goto('/');
// Check for accessibility violations
const violations = await new AxeBuilder({ page }).analyze();
expect(violations.length).toBe(0);
});
```
### Mobile Testing
```javascript
test('mobile responsive', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
// Mobile-specific assertions
});
```
### API Testing
```javascript
test('API integration', async ({ request }) => {
const response = await request.get('https://api.example.com/users');
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy();
});
```
---
## Test Fixing
### Debugging Failing Tests
1. **Read the error** - What is it saying?
2. **Check the test** - Is it testing the right thing?
3. **Check the code** - Is the implementation correct?
4. **Check the mocks** - Are they set up correctly?
5. **Check the data** - Is the test data valid?
### Common Issues
| Issue | Solution |
|-------|----------|
| Flaky tests | Add retries, stabilize timing |
| Race conditions | Add waits, use explicit conditions |
| Environment differences | Use Docker, consistent setup |
| Data dependencies | Use fixtures, clean state |
### Test Isolation
```javascript
// Bad - shared state
let user;
test('creates user', () => { user = createUser(); });
test('modifies user', () => { modifyUser(user.id); });
// Good - isolated
test('creates and modifies user', () => {
const user = createUser();
modifyUser(user.id);
// assertions
});
```
---
## Best Practices
1. **AAA Pattern** - Arrange, Act, Assert
2. **One Assertion** - Per test when possible
3. **Descriptive Names** - `it('should return 404 for missing resource')`
4. **Fast Tests** - Mock external dependencies
5. **Independent Tests** - No order dependency
6. **Real Data** - Don't over-mock
7. **Coverage** - Aim for meaningful coverage, not 100%