Testing
Testing
Comprehensive end-to-end testing using Playwright for the Cbox Init Web UI.
Overview
The test suite provides:
- 35 comprehensive tests across 3 test suites
- Multi-browser support (Chromium, Firefox, WebKit)
- Mobile testing (Pixel 5, iPhone 12)
- Accessibility validation (WCAG 2.1 compliance)
- API mocking for isolated testing
- Visual regression with screenshots
- 100% test coverage of critical paths
Quick Start
Install Dependencies
cd web
npm install
npx playwright install
Run Tests
# Run all tests
npm run test
# Run in specific browser
npm run test:chromium
npm run test:firefox
npm run test:webkit
# Run with UI mode (interactive)
npm run test:ui
# Run in headed mode (visible browser)
npm run test:headed
# Debug mode
npm run test:debug
# View HTML report
npm run test:report
Test Suites
1. Dashboard Tests (16 tests)
File: web/e2e/dashboard.spec.ts
Tests core dashboard functionality:
test('should display process stats', async ({ page }) => {
await page.waitForTimeout(500);
await expect(page.getByText('Total Processer')).toBeVisible();
await expect(page.getByText('Kører')).toBeVisible();
await expect(page.getByText('Stoppet')).toBeVisible();
await expect(page.getByText('Fejlet')).toBeVisible();
});
Coverage:
- Page rendering and layout
- Health status display
- Stats cards (total, running, stopped, failed)
- Process cards and instances
- Dark mode toggle
- Refresh functionality
- API error handling
- Empty state handling
- Mobile responsiveness
2. Process Actions Tests (11 tests)
File: web/e2e/process-actions.spec.ts
Tests process control functionality:
test('should start a stopped process', async ({ page }) => {
let startActionCalled = false;
await page.route('**/api/v1/processes/test-process', async (route) => {
if (route.request().method() === 'POST') {
const postData = route.request().postDataJSON();
if (postData.action === 'start') {
startActionCalled = true;
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true }),
});
}
}
});
await page.goto('/');
await page.waitForTimeout(500);
const processCard = page.getByTestId('process-card-test-process');
const startButton = processCard.locator('button').first();
await startButton.click();
await page.waitForTimeout(500);
expect(startActionCalled).toBeTruthy();
});
Coverage:
- Start process action
- Stop process action
- Restart process action
- Error handling
- Button states and visibility
- Process state changes
- Scale information
- Instance count display
- Hover effects
3. Accessibility Tests (12 tests)
File: web/e2e/accessibility.spec.ts
WCAG 2.1 compliance validation:
test('should not have automatically detectable accessibility issues', async ({ page }) => {
await page.waitForTimeout(500);
const buttons = page.locator('button');
const buttonCount = await buttons.count();
for (let i = 0; i < buttonCount; i++) {
const button = buttons.nth(i);
const ariaLabel = await button.getAttribute('aria-label');
const title = await button.getAttribute('title');
const textContent = await button.textContent();
expect(
ariaLabel || title || (textContent && textContent.trim().length > 0)
).toBeTruthy();
}
});
Coverage:
- Semantic HTML structure
- ARIA labels for icon buttons
- Keyboard navigation
- Screen reader support
- Color contrast ratios
- Proper heading hierarchy
- Form label associations
- Focus indicators
- Dark mode accessibility
- Status messages
Configuration
Playwright Config
File: web/playwright.config.ts
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
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 Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});
API Mocking
All tests use API mocking for isolation:
async function mockAPIResponses(page: Page) {
// Mock health endpoint
await page.route('**/api/v1/health', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'healthy' }),
});
});
// Mock processes list endpoint
await page.route('**/api/v1/processes', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
processes: [
{
name: 'php-fpm',
state: 'running',
scale: 2,
instances: [/* ... */],
},
],
}),
});
});
}
test.beforeEach(async ({ page }) => {
await mockAPIResponses(page);
});
Test Patterns
Using Test IDs
For reliable element selection:
// Component with data-testid
<div data-testid={`process-card-${process.name}`}>
{/* ... */}
</div>
// Test selector
const processCard = page.getByTestId('process-card-test-process');
Role-Based Selectors
For semantic element selection:
await expect(
page.getByRole('heading', { name: 'php-fpm', exact: true })
).toBeVisible();
CSS Selectors
For specific styling-based selection:
const runningBadges = page.locator('span.text-green-500.px-2', {
hasText: 'running'
});
await expect(runningBadges).toHaveCount(3);
Waiting Strategies
// Wait for timeout (use sparingly)
await page.waitForTimeout(500);
// Wait for specific text
await page.waitForSelector('text=Process started');
// Wait for network response
await page.waitForResponse('**/api/v1/processes');
CI/CD Integration
GitHub Actions
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
run: |
cd web
npm ci
npx playwright install --with-deps
- name: Run tests
run: npm run test --prefix web
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: web/playwright-report/
Docker Testing
FROM mcr.microsoft.com/playwright:v1.56.1-focal
WORKDIR /app
COPY web/package*.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
CMD ["npm", "run", "test"]
Debugging Tests
Interactive UI Mode
npm run test:ui
Features:
- Visual test execution
- Step through tests
- Time travel debugging
- DOM snapshots
- Network logs
Debug Mode
npm run test:debug
Opens headed browser with:
- Playwright Inspector
- Step-by-step execution
- Selector playground
- Console logs
Screenshots on Failure
Automatic screenshot capture:
test-results/
├── dashboard-test-name-chromium/
│ ├── test-failed-1.png
│ └── error-context.md
Trace Files
Enable trace recording:
use: {
trace: 'on-first-retry',
}
View traces:
npx playwright show-trace test-results/trace.zip
Best Practices
1. Use Reliable Selectors
// ✅ Good: Test IDs
page.getByTestId('process-card-nginx')
// ✅ Good: Role-based
page.getByRole('button', { name: 'Start' })
// ❌ Bad: Text content (can match multiple)
page.locator('text=nginx')
2. Avoid Hard Waits
// ❌ Bad: Hard timeout
await page.waitForTimeout(1000);
// ✅ Good: Wait for condition
await expect(page.getByText('Process started')).toBeVisible();
3. Mock External Dependencies
Always mock API calls:
// ✅ Good: Mocked
await page.route('**/api/**', mockHandler);
// ❌ Bad: Real API (flaky, slow)
// (no mocking)
4. Clean Test Isolation
test.beforeEach(async ({ page }) => {
await mockAPIResponses(page);
await page.goto('/');
});
test.afterEach(async ({ page }) => {
await page.close();
});
5. Descriptive Test Names
// ✅ Good
test('should display error message when API returns 500', async ({ page }) => {
// ...
});
// ❌ Bad
test('error handling', async ({ page }) => {
// ...
});
Performance
Parallel Execution
Tests run in parallel by default (8 workers):
fullyParallel: true,
workers: process.env.CI ? 1 : undefined,
Test Sharding
Split tests across multiple machines:
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
Selective Test Running
# Run specific file
npx playwright test dashboard.spec.ts
# Run specific test
npx playwright test -g "should start a stopped process"
# Run by tag
npx playwright test --grep @smoke
Coverage Metrics
Current test coverage:
- 35/35 tests passing (100%)
- 3 test suites (Dashboard, Actions, Accessibility)
- 5 browser configurations (Desktop + Mobile)
- All critical paths covered
Troubleshooting
Tests Failing Locally
- Check Node version:
node --version(20+) - Reinstall browsers:
npx playwright install - Clear cache:
rm -rf node_modules && npm install - Check port availability:
lsof -i :5173
Timeout Errors
Increase timeout:
test('slow operation', async ({ page }) => {
test.setTimeout(60000); // 60 seconds
// ...
});
Selector Issues
Use Playwright Inspector:
PWDEBUG=1 npm run test
Click "Pick locator" to test selectors.
Future Enhancements
Planned improvements:
- Visual regression testing
- Performance benchmarks
- API contract testing
- Load testing scenarios
- Mutation testing
- Code coverage integration