8.5 KiB
8.5 KiB
description, applyTo
| description | applyTo |
|---|---|
| Playwright test generation instructions | ** |
Test Writing Guidelines
Code Quality Standards
- Locators: Prioritize user-facing, role-based locators (
getByRole,getByLabel,getByText, etc.) for resilience and accessibility. Usetest.step()to group interactions and improve test readability and reporting. - Assertions: Use auto-retrying web-first assertions. These assertions start with the
awaitkeyword (e.g.,await expect(locator).toHaveText()). Avoidexpect(locator).toBeVisible()unless specifically testing for visibility changes. - Timeouts: Rely on Playwright's built-in auto-waiting mechanisms. Avoid hard-coded waits or increased default timeouts.
- Switch/Toggle Components: Use helper functions from
tests/utils/ui-helpers.ts(clickSwitch,expectSwitchState,toggleSwitch) for reliable interactions. Never use{ force: true }or direct clicks on hidden inputs. - Clarity: Use descriptive test and step titles that clearly state the intent. Add comments only to explain complex logic or non-obvious interactions.
Test Structure
- Imports: Start with
import { test, expect } from '@playwright/test';. - Organization: Group related tests for a feature under a
test.describe()block. - Hooks: Use
beforeEachfor setup actions common to all tests in adescribeblock (e.g., navigating to a page). - Titles: Follow a clear naming convention, such as
Feature - Specific action or scenario.
File Organization
- Location: Store all test files in the
tests/directory. - Naming: Use the convention
<feature-or-page>.spec.ts(e.g.,login.spec.ts,search.spec.ts). - Scope: Aim for one test file per major application feature or page.
Assertion Best Practices
- UI Structure: Use
toMatchAriaSnapshotto verify the accessibility tree structure of a component. This provides a comprehensive and accessible snapshot. - Element Counts: Use
toHaveCountto assert the number of elements found by a locator. - Text Content: Use
toHaveTextfor exact text matches andtoContainTextfor partial matches. - Navigation: Use
toHaveURLto verify the page URL after an action. - Switch States: Use
expectSwitchState(locator, boolean)to verify toggle states. This is more reliable thantoBeChecked()directly.
Switch/Toggle Interaction Patterns
Switch components use a hidden <input> with styled siblings, requiring special handling:
import { clickSwitch, expectSwitchState, toggleSwitch } from './utils/ui-helpers';
// ✅ RECOMMENDED: Click switch with helper
const aclSwitch = page.getByRole('switch', { name: /acl/i });
await clickSwitch(aclSwitch);
// ✅ RECOMMENDED: Assert switch state
await expectSwitchState(aclSwitch, true); // Checked
// ✅ RECOMMENDED: Toggle and verify state change
const newState = await toggleSwitch(aclSwitch);
console.log(`Switch is now ${newState ? 'enabled' : 'disabled'}`);
// ❌ AVOID: Direct click on hidden input
await aclSwitch.click(); // May fail in WebKit/Firefox
// ❌ AVOID: Force clicking (anti-pattern)
await aclSwitch.click({ force: true }); // Bypasses real user behavior
// ❌ AVOID: Hard-coded waits
await page.waitForTimeout(500); // Non-deterministic, slows tests
When to Use:
- Settings pages with enable/disable toggles
- Security dashboard module switches (CrowdSec, ACL, WAF, Rate Limiting)
- Access lists and configuration toggles
- Any UI component using the
Switchprimitive from shadcn/ui
References:
Testing Scope: E2E vs Integration
CRITICAL: Playwright E2E tests verify UI/UX functionality on the Charon management interface (port 8080). They should NOT test middleware enforcement behavior.
What E2E Tests SHOULD Cover
✅ User Interface Interactions:
- Form submissions and validation
- Navigation and routing
- Visual state changes (toggles, badges, status indicators)
- Authentication flows (login, logout, session management)
- CRUD operations via the management API
- Responsive design (mobile vs desktop layouts)
- Accessibility (ARIA labels, keyboard navigation)
✅ Example E2E Assertions:
// GOOD: Testing UI state
await expect(aclToggle).toBeChecked();
await expect(statusBadge).toHaveText('Active');
await expect(page).toHaveURL('/proxy-hosts');
// GOOD: Testing API responses in management interface
const response = await request.post('/api/v1/proxy-hosts', { data: hostConfig });
expect(response.ok()).toBeTruthy();
What E2E Tests should NOT Cover
❌ Middleware Enforcement Behavior:
- Rate limiting blocking requests (429 responses)
- ACL denying access based on IP rules (403 responses)
- WAF blocking malicious payloads (SQL injection, XSS)
- CrowdSec IP bans
❌ Example Wrong E2E Assertions:
// BAD: Testing middleware behavior (rate limiting)
for (let i = 0; i < 6; i++) {
await request.post('/api/v1/emergency/reset');
}
expect(response.status()).toBe(429); // ❌ This tests Caddy middleware
// BAD: Testing WAF blocking
await request.post('/api/v1/data', { data: "'; DROP TABLE users--" });
expect(response.status()).toBe(403); // ❌ This tests Coraza WAF
Integration Tests for Middleware
Middleware enforcement is verified by integration tests in backend/integration/:
cerberus_integration_test.go- Overall security suite behaviorcoraza_integration_test.go- WAF blocking (SQL injection, XSS)crowdsec_integration_test.go- IP reputation and bansrate_limit_integration_test.go- Request throttling
These tests run in Docker Compose with full Caddy+Cerberus stack and are executed in separate CI workflows.
When to Skip Tests
Use test.skip() for tests that require middleware enforcement:
test('should rate limit after 5 attempts', async ({ request }) => {
test.skip(
true,
'Rate limiting enforced via Cerberus middleware (port 80). Verified in integration tests (backend/integration/).'
);
// Test body...
});
Skip Reason Template:
"[Behavior] enforced via Cerberus middleware (port 80). Verified in integration tests (backend/integration/)."
Example Test Structure
import { test, expect } from '@playwright/test';
test.describe('Movie Search Feature', () => {
test.beforeEach(async ({ page }) => {
// Navigate to the application before each test
await page.goto('https://debs-obrien.github.io/playwright-movies-app');
});
test('Search for a movie by title', async ({ page }) => {
await test.step('Activate and perform search', async () => {
await page.getByRole('search').click();
const searchInput = page.getByRole('textbox', { name: 'Search Input' });
await searchInput.fill('Garfield');
await searchInput.press('Enter');
});
await test.step('Verify search results', async () => {
// Verify the accessibility tree of the search results
await expect(page.getByRole('main')).toMatchAriaSnapshot(`
- main:
- heading "Garfield" [level=1]
- heading "search results" [level=2]
- list "movies":
- listitem "movie":
- link "poster of The Garfield Movie The Garfield Movie rating":
- /url: /playwright-movies-app/movie?id=tt5779228&page=1
- img "poster of The Garfield Movie"
- heading "The Garfield Movie" [level=2]
`);
});
});
});
Test Execution Strategy
- Initial Run: Execute tests with
npx playwright test --project=chromium - Debug Failures: Analyze test failures and identify root causes
- Iterate: Refine locators, assertions, or test logic as needed
- Validate: Ensure tests pass consistently and cover the intended functionality
- Report: Provide feedback on test results and any issues discovered
Execution Constraints
- No Truncation: Never pipe Playwright test output through
head,tail, or other truncating commands. Playwright runs interactively and requires user input to quit when piped, causing the command to hang indefinitely. - Full Output: Always capture the complete test output to analyze failures accurately.
Quality Checklist
Before finalizing tests, ensure:
- All locators are accessible and specific and avoid strict mode violations
- Tests are grouped logically and follow a clear structure
- Assertions are meaningful and reflect user expectations
- Tests follow consistent naming conventions
- Code is properly formatted and commented