integratedhub/tests/E2E_TEST_GUIDE.md
2026-02-14 02:20:19 +00:00

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

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

# 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

  1. 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
    });
    
  2. 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);
    
  3. Use Conditional Assertions for Missing Features

    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

    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

    // 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:

  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:

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:

  1. Check CI environment variables
  2. Increase retries in playwright.config.ts
  3. Use fullyParallel: false in CI
  4. 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:

  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:

# 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:

  1. Use unique test data (add timestamps)

    const timestamp = Date.now();
    await page.locator('input[name="title"]').fill(`Test ${timestamp}`);
    
  2. Clean up after tests

    test.afterEach(async ({ page }) => {
      // Delete test data
      await page.request.delete("/api/test-data", {
        headers: { "x-e2e-user": "admin" },
      });
    });
    
  3. 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

  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

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