14 KiB
E2E Testing Guide
This guide covers running, debugging, and maintaining the End-to-End (E2E) test suite for the Cyber Hybrid Hub application.
Table of Contents
- Overview
- Prerequisites
- Running Tests
- Test Structure
- Writing Tests
- Debugging Tests
- CI/CD Integration
- Troubleshooting
Overview
The E2E test suite uses Playwright to test the application through the browser, simulating real user interactions. Tests are organized by feature and role (landlord, tenant, admin).
Test Categories
-
Authentication (
tests/e2e/auth/)- Login/logout flows
- Role-based access control (RBAC)
- Session management
-
Landlord Features (
tests/e2e/landlord/)- Dashboard
- Property management
- Applications
- Leases
- Maintenance requests
- Payments
-
Tenant Features (
tests/e2e/tenant/)- Dashboard
- Applications
- Leases
- Maintenance requests
- Payments
-
Error Handling (
tests/e2e/error-handling/)- Form validation
- Network errors
- Unauthorized access
- Session expiration
- Edge cases
Prerequisites
Required Software
- Deno v2.x: Runtime for the application
- Node.js v20+: For Playwright CLI
- Playwright: Browser automation framework
Installation
# Install Deno (if not already installed)
curl -fsSL https://deno.land/install.sh | sh
# Install Playwright and browsers
cd .playwright-mcp
npm install
npx playwright install --with-deps chromium
Environment Setup
Set these environment variables (optional, defaults provided):
# Application configuration
export PORT=8080 # Port for dev server
export E2E_BASE_URL=http://127.0.0.1:8080 # Base URL for tests
export E2E_TEST=true # Enable E2E test mode
export HOSTNAME_DENO=127.0.0.1 # Deno server hostname
Running Tests
Run All Tests
# From project root
npx playwright test
# Or from .playwright-mcp directory
cd .playwright-mcp
npx playwright test
Run Specific Test Files
# Run authentication tests
npx playwright test tests/e2e/auth/
# Run specific test file
npx playwright test tests/e2e/auth/login.spec.ts
# Run tests matching a pattern
npx playwright test --grep "login"
Run Tests in Different Modes
# Run in headed mode (show browser)
npx playwright test --headed
# Run with debug UI
npx playwright test --debug
# Run specific test file with UI
npx playwright test --ui tests/e2e/auth/login.spec.ts
# Run with trace on failure
npx playwright test --trace on
# Run tests in parallel
npx playwright test --workers=4
Run Tests on Specific Browser
# Chromium (default)
npx playwright test --project=chromium
# Note: Currently only Chromium is configured
# Add more browsers in playwright.config.ts if needed
View Test Reports
# Open HTML report
npx playwright show-report
# Report is generated in playwright-report/index.html
Test Structure
Directory Layout
tests/e2e/
├── helpers/
│ └── test-utils.ts # Shared test utilities
├── auth/
│ ├── login.spec.ts # Authentication flow tests
│ ├── logout.spec.ts # Logout and session cleanup
│ └── rbac.spec.ts # Role-based access control
├── landlord/
│ ├── dashboard.spec.ts # Landlord dashboard
│ ├── properties.spec.ts # Property management
│ ├── applications.spec.ts # Rental applications
│ ├── leases.spec.ts # Lease management
│ ├── maintenance.spec.ts # Maintenance requests
│ └── payments.spec.ts # Financial data
├── tenant/
│ ├── applications.spec.ts # Tenant applications
│ ├── leases.spec.ts # Tenant leases
│ ├── maintenance.spec.ts # Maintenance requests
│ └── payments.spec.ts # Payment management
└── error-handling/
└── validation.spec.ts # Error scenarios and edge cases
Test File Structure
import { expect, test } from "../../helpers/test-utils.ts";
test.describe("Feature Name", () => {
test.beforeEach(async ({ page }) => {
// Set E2E mode for each test
await page.addInitScript(() => {
globalThis.localStorage.setItem("E2E_TEST", "true");
});
});
test("should do something specific", async ({ page }) => {
// Navigate with E2E user header
await page.goto("/some-route", {
headers: {
"x-e2e-user": "landlord", // or "tenant", "admin"
},
});
// Wait for page to load
await page.waitForTimeout(1000);
// Perform actions and assertions
await expect(page.locator("h1")).toContainText("Expected Text");
});
test.describe("Sub-feature", () => {
test("should test sub-feature", async ({ page }) => {
// Test implementation
});
});
});
Writing Tests
Best Practices
-
Use Descriptive Test Names
test("should allow landlord to create property", async ({ page }) => { // Good: Clear and specific }); test("create property", async ({ page }) => { // Bad: Too vague }); -
Track Console Errors
const errors: string[] = []; page.on("console", (msg) => { if (msg.type() === "error") { errors.push(msg.text()); } }); // After test actions const filteredErrors = errors.filter( (err) => !err.includes("net::ERR_BLOCKED_BY_CLIENT"), ); expect(filteredErrors.length).toBe(0); -
Use Conditional Assertions for Missing Features
const element = page.locator("#some-feature"); if (await element.isVisible({ timeout: 3000 })) { await expect(element).toBeVisible(); // Additional assertions } -
Group Related Tests with
test.describetest.describe("Property Creation", () => { test("should show form", async ({ page }) => {}); test("should validate inputs", async ({ page }) => {}); test("should save property", async ({ page }) => {}); }); -
Use E2E Test Fixtures
// E2E mode is required to bypass OAuth await page.addInitScript(() => { globalThis.localStorage.setItem("E2E_TEST", "true"); }); // Use E2E user headers for authentication await page.goto("/protected-route", { headers: { "x-e2e-user": "landlord", }, });
E2E User Fixtures
The application provides E2E test fixtures for deterministic testing:
landlord: Alex Landlord (owns properties e2e-prop-1, e2e-prop-2)tenant: Sam Tenant (has lease e2e-lease-1)admin: Admin user (full system access)
Common Test Patterns
Navigation with Authentication
await page.goto("/landlord/dashboard", {
headers: {
"x-e2e-user": "landlord",
},
});
await page.waitForTimeout(1000); // Allow for redirects/load
Form Interaction
// Fill form fields
await page.locator('input[name="title"]').fill("Test Property");
await page.locator('textarea[name="description"]').fill("Description");
// Submit form
await page.locator('button[type="submit"]').click();
// Wait for response
await page.waitForTimeout(1000);
API Testing
const response = await page.request.get("/api/properties", {
headers: {
"x-e2e-user": "landlord",
},
});
expect([200, 404].includes(response.status())).toBe(true);
Data Verification
// Verify page content
const content = await page.content();
expect(content.includes("Expected Text")).toBe(true);
// Or use locators
await expect(page.locator("text=Expected Text")).toBeVisible();
Debugging Tests
Debug Mode
Run tests with the Playwright Inspector:
npx playwright test --debug
This opens:
- A browser window
- The Playwright Inspector for stepping through tests
- Time-travel debugging capabilities
UI Mode
Run tests with the Playwright UI:
npx playwright test --ui
Features:
- Visual test explorer
- Watch mode
- Trace viewer
- Time-travel debugging
Headed Mode
See the browser running the test:
npx playwright test --headed
Slow Motion
Run tests in slow motion to see what's happening:
npx playwright test --headed --slow-mo=1000
Screenshots on Failure
Screenshots are automatically captured on failures. Find them in:
test-results/
├── test-name/
│ ├── screenshot.png
│ ├── trace.zip
│ └── video.webm
Traces
Traces capture everything that happened during the test:
# Enable tracing for all tests
npx playwright test --trace on
# View a trace
npx playwright show-trace test-results/trace.zip
Console Logging
Add debugging statements:
test("should do something", async ({ page }) => {
console.log("Current URL:", page.url());
const content = await page.content();
console.log("Page content preview:", content.substring(0, 200));
// Or use page.evaluate
const result = await page.evaluate(() => {
console.log("Browser console log");
return document.body.innerText;
});
});
Common Issues and Solutions
Issue: "Element not found"
Solution: Increase timeout or use conditional checks
// Instead of:
await expect(page.locator("#element")).toBeVisible();
// Use:
if (await page.locator("#element").isVisible({ timeout: 5000 })) {
await expect(page.locator("#element")).toBeVisible();
}
Issue: "Test is too slow"
Solution: Adjust timeouts in playwright.config.ts
export default defineConfig({
timeout: 60_000, // Overall test timeout
expect: {
timeout: 10_000, // Assertion timeout
},
});
Issue: "Tests fail in CI but pass locally"
Solutions:
- Check CI environment variables
- Increase retries in
playwright.config.ts - Use
fullyParallel: falsein CI - Check for timing issues
export default defineConfig({
retries: process.env.CI ? 2 : 0,
fullyParallel: !process.env.CI,
});
Issue: "Database connection errors"
Solution: Ensure database is running and accessible
# Check database status
psql -h localhost -U postgres -d cyberhybridhub -c "SELECT 1;"
# Or use docker-compose
docker-compose up -d postgres
CI/CD Integration
GitHub Actions
The project includes a GitHub Actions workflow
(.github/workflows/e2e-tests.yml) that:
- Runs E2E tests on every push and PR
- Runs tests on a schedule (daily at 2 AM UTC)
- Shards tests across 4 workers for parallel execution
- Uploads test results, screenshots, videos, and traces
- Publishes test results as PR comments
Workflow Features
- Parallel Execution: Tests run across 4 shards
- Retry on Failure: Tests retry up to 2 times in CI
- Artifact Retention: Results kept for 14 days
- Failure Artifacts: Screenshots, videos, and traces kept for 7 days
- Test Summary: JUnit XML results published to PR
Manual Workflow Trigger
Trigger tests manually from GitHub Actions:
- Go to Actions tab in GitHub
- Select "E2E Tests" workflow
- Click "Run workflow"
- Select branch and click "Run workflow"
Local CI Testing
Test CI workflow locally using act:
# Install act
brew install act # macOS
# or
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
# Run workflow
act push --job e2e-tests
Troubleshooting
Port Already in Use
# Kill process using port 8080
lsof -ti:8080 | xargs kill -9
# Or use a different port
PORT=8081 npx playwright test
Browser Installation Issues
# Reinstall Playwright browsers
npx playwright install --force --with-deps chromium
# Or install all browsers
npx playwright install --with-deps
Deno Installation Issues
# Reinstall Deno
rm -rf ~/.deno
curl -fsSL https://deno.land/install.sh | sh
Cache Issues
# Clear Deno cache
deno cache --reload tests/e2e/**/*.spec.ts
# Clear Node cache
rm -rf node_modules package-lock.json
npm install
Test Isolation Issues
If tests interfere with each other:
-
Use unique test data (add timestamps)
const timestamp = Date.now(); await page.locator('input[name="title"]').fill(`Test ${timestamp}`); -
Clean up after tests
test.afterEach(async ({ page }) => { // Delete test data await page.request.delete("/api/test-data", { headers: { "x-e2e-user": "admin" }, }); }); -
Disable parallel execution
// In playwright.config.ts fullyParallel: false, workers: 1,
Database Issues
# Reset test database
psql -h localhost -U postgres -c "DROP DATABASE IF EXISTS cyberhybridhub_test;"
psql -h localhost -U postgres -c "CREATE DATABASE cyberhybridhub_test;"
# Run migrations
deno task db:push
Performance Tips
- Use parallel execution for faster test runs
- Reuse browser context when possible
- Avoid unnecessary waits - use
waitForSelectorinstead ofwaitForTimeout - Group tests by feature to reduce setup overhead
- Use API tests for data validation (faster than UI)
Additional Resources
- Playwright Documentation
- Playwright Best Practices
- Project CLAUDE.md - Project-specific guidelines
- Test Examples - Reference implementations
Support
For issues or questions:
- Check this guide's troubleshooting section
- Review Playwright documentation
- Check existing test files for examples
- Consult project documentation in
CLAUDE.md