629 lines
14 KiB
Markdown
629 lines
14 KiB
Markdown
# 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](#overview)
|
|
- [Prerequisites](#prerequisites)
|
|
- [Running Tests](#running-tests)
|
|
- [Test Structure](#test-structure)
|
|
- [Writing Tests](#writing-tests)
|
|
- [Debugging Tests](#debugging-tests)
|
|
- [CI/CD Integration](#cicd-integration)
|
|
- [Troubleshooting](#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
|
|
|
|
1. **Authentication** (`tests/e2e/auth/`)
|
|
- Login/logout flows
|
|
- Role-based access control (RBAC)
|
|
- Session management
|
|
|
|
2. **Landlord Features** (`tests/e2e/landlord/`)
|
|
- Dashboard
|
|
- Property management
|
|
- Applications
|
|
- Leases
|
|
- Maintenance requests
|
|
- Payments
|
|
|
|
3. **Tenant Features** (`tests/e2e/tenant/`)
|
|
- Dashboard
|
|
- Applications
|
|
- Leases
|
|
- Maintenance requests
|
|
- Payments
|
|
|
|
4. **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
|
|
|
|
```bash
|
|
# 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):
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# From project root
|
|
npx playwright test
|
|
|
|
# Or from .playwright-mcp directory
|
|
cd .playwright-mcp
|
|
npx playwright test
|
|
```
|
|
|
|
### Run Specific Test Files
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```typescript
|
|
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
|
|
|
|
1. **Use Descriptive Test Names**
|
|
```typescript
|
|
test("should allow landlord to create property", async ({ page }) => {
|
|
// Good: Clear and specific
|
|
});
|
|
|
|
test("create property", async ({ page }) => {
|
|
// Bad: Too vague
|
|
});
|
|
```
|
|
|
|
2. **Track Console Errors**
|
|
```typescript
|
|
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);
|
|
```
|
|
|
|
3. **Use Conditional Assertions for Missing Features**
|
|
```typescript
|
|
const element = page.locator("#some-feature");
|
|
if (await element.isVisible({ timeout: 3000 })) {
|
|
await expect(element).toBeVisible();
|
|
// Additional assertions
|
|
}
|
|
```
|
|
|
|
4. **Group Related Tests with `test.describe`**
|
|
```typescript
|
|
test.describe("Property Creation", () => {
|
|
test("should show form", async ({ page }) => {});
|
|
test("should validate inputs", async ({ page }) => {});
|
|
test("should save property", async ({ page }) => {});
|
|
});
|
|
```
|
|
|
|
5. **Use E2E Test Fixtures**
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
await page.goto("/landlord/dashboard", {
|
|
headers: {
|
|
"x-e2e-user": "landlord",
|
|
},
|
|
});
|
|
|
|
await page.waitForTimeout(1000); // Allow for redirects/load
|
|
```
|
|
|
|
#### Form Interaction
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
const response = await page.request.get("/api/properties", {
|
|
headers: {
|
|
"x-e2e-user": "landlord",
|
|
},
|
|
});
|
|
|
|
expect([200, 404].includes(response.status())).toBe(true);
|
|
```
|
|
|
|
#### Data Verification
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```bash
|
|
npx playwright test --debug
|
|
```
|
|
|
|
This opens:
|
|
|
|
1. A browser window
|
|
2. The Playwright Inspector for stepping through tests
|
|
3. Time-travel debugging capabilities
|
|
|
|
### UI Mode
|
|
|
|
Run tests with the Playwright UI:
|
|
|
|
```bash
|
|
npx playwright test --ui
|
|
```
|
|
|
|
Features:
|
|
|
|
- Visual test explorer
|
|
- Watch mode
|
|
- Trace viewer
|
|
- Time-travel debugging
|
|
|
|
### Headed Mode
|
|
|
|
See the browser running the test:
|
|
|
|
```bash
|
|
npx playwright test --headed
|
|
```
|
|
|
|
### Slow Motion
|
|
|
|
Run tests in slow motion to see what's happening:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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`
|
|
|
|
```typescript
|
|
export default defineConfig({
|
|
timeout: 60_000, // Overall test timeout
|
|
expect: {
|
|
timeout: 10_000, // Assertion timeout
|
|
},
|
|
});
|
|
```
|
|
|
|
#### Issue: "Tests fail in CI but pass locally"
|
|
|
|
**Solutions**:
|
|
|
|
1. Check CI environment variables
|
|
2. Increase retries in `playwright.config.ts`
|
|
3. Use `fullyParallel: false` in CI
|
|
4. Check for timing issues
|
|
|
|
```typescript
|
|
export default defineConfig({
|
|
retries: process.env.CI ? 2 : 0,
|
|
fullyParallel: !process.env.CI,
|
|
});
|
|
```
|
|
|
|
#### Issue: "Database connection errors"
|
|
|
|
**Solution**: Ensure database is running and accessible
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
1. Runs E2E tests on every push and PR
|
|
2. Runs tests on a schedule (daily at 2 AM UTC)
|
|
3. Shards tests across 4 workers for parallel execution
|
|
4. Uploads test results, screenshots, videos, and traces
|
|
5. 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:
|
|
|
|
1. Go to Actions tab in GitHub
|
|
2. Select "E2E Tests" workflow
|
|
3. Click "Run workflow"
|
|
4. Select branch and click "Run workflow"
|
|
|
|
### Local CI Testing
|
|
|
|
Test CI workflow locally using [act](https://github.com/nektos/act):
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# Kill process using port 8080
|
|
lsof -ti:8080 | xargs kill -9
|
|
|
|
# Or use a different port
|
|
PORT=8081 npx playwright test
|
|
```
|
|
|
|
### Browser Installation Issues
|
|
|
|
```bash
|
|
# Reinstall Playwright browsers
|
|
npx playwright install --force --with-deps chromium
|
|
|
|
# Or install all browsers
|
|
npx playwright install --with-deps
|
|
```
|
|
|
|
### Deno Installation Issues
|
|
|
|
```bash
|
|
# Reinstall Deno
|
|
rm -rf ~/.deno
|
|
curl -fsSL https://deno.land/install.sh | sh
|
|
```
|
|
|
|
### Cache Issues
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
1. **Use unique test data** (add timestamps)
|
|
```typescript
|
|
const timestamp = Date.now();
|
|
await page.locator('input[name="title"]').fill(`Test ${timestamp}`);
|
|
```
|
|
|
|
2. **Clean up after tests**
|
|
```typescript
|
|
test.afterEach(async ({ page }) => {
|
|
// Delete test data
|
|
await page.request.delete("/api/test-data", {
|
|
headers: { "x-e2e-user": "admin" },
|
|
});
|
|
});
|
|
```
|
|
|
|
3. **Disable parallel execution**
|
|
```typescript
|
|
// In playwright.config.ts
|
|
fullyParallel: false,
|
|
workers: 1,
|
|
```
|
|
|
|
### Database Issues
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. **Use parallel execution** for faster test runs
|
|
2. **Reuse browser context** when possible
|
|
3. **Avoid unnecessary waits** - use `waitForSelector` instead of
|
|
`waitForTimeout`
|
|
4. **Group tests** by feature to reduce setup overhead
|
|
5. **Use API tests** for data validation (faster than UI)
|
|
|
|
## Additional Resources
|
|
|
|
- [Playwright Documentation](https://playwright.dev)
|
|
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)
|
|
- [Project CLAUDE.md](../CLAUDE.md) - Project-specific guidelines
|
|
- [Test Examples](./e2e/) - Reference implementations
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
|
|
1. Check this guide's troubleshooting section
|
|
2. Review Playwright documentation
|
|
3. Check existing test files for examples
|
|
4. Consult project documentation in `CLAUDE.md`
|