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
314 lines
7.4 KiB
Markdown
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%
|