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
7.4 KiB
7.4 KiB
name, description
| name | description |
|---|---|
| testing-master | 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)
- RED - Write a failing test
- GREEN - Write minimal code to pass
- 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
// 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
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
npm init playwright@latest
npx playwright install chromium
Basic Test Structure
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
// 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
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
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)
- role -
getByRole('button', { name: 'Submit' }) - label -
getByLabel('Email') - placeholder -
getByPlaceholder('Enter email') - text -
getByText('Sign in') - testid -
getByTestId('submit-btn') - CSS -
locator('.submit') - XPath -
locator('//button[@type="submit"]')
Common Actions
// 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
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
await page.route('**/api/**', route => {
route.fulfill({
status: 200,
body: JSON.stringify({ data: 'mocked' }),
});
});
File Upload
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
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
test('mobile responsive', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('/');
// Mobile-specific assertions
});
API Testing
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
- Read the error - What is it saying?
- Check the test - Is it testing the right thing?
- Check the code - Is the implementation correct?
- Check the mocks - Are they set up correctly?
- 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
// 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
- AAA Pattern - Arrange, Act, Assert
- One Assertion - Per test when possible
- Descriptive Names -
it('should return 404 for missing resource') - Fast Tests - Mock external dependencies
- Independent Tests - No order dependency
- Real Data - Don't over-mock
- Coverage - Aim for meaningful coverage, not 100%