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

8.2 KiB

E2E Test Helpers & Documentation

This directory contains end-to-end tests using Playwright with deterministic test fixtures.

Overview

The E2E test infrastructure provides:

  • Deterministic Testing: Consistent time, random values, and no animations
  • E2E User Authentication: Bypass normal auth via x-e2e-user header
  • Fixture Data: Pre-populated test data in services/e2eFixtures.ts
  • Reusable Helpers: Common test utilities in helpers/fixtures.ts

Available E2E Users

The following test users are available (defined in services/e2eFixtures.ts):

User Type User ID Name Description
landlord e2e-landlord-1 Alex Landlord Fully onboarded landlord with properties and leases
landlord-2 e2e-landlord-2 Jordan Landlord Second landlord for multi-user testing
landlord-onboarding e2e-landlord-setup-1 (empty) New landlord not yet onboarded
tenant e2e-tenant-1 Taylor Tenant Tenant with active lease
admin e2e-admin-1 Admin User Admin with full system access

Test Data Available

Each E2E user has pre-populated data:

Landlord (Alex)

  • Properties: Skyline Lofts (available), Maple Row (occupied)
  • Leases: 1 active lease for Maple Row
  • Messages: 11 messages (mix of read/unread)
  • Maintenance Requests: 2 requests (open, in-progress)
  • Payment Transactions: 3 completed payments

Tenant (Taylor)

  • Leases: 1 active lease at Maple Row
  • Messages: Conversations with landlord
  • Maintenance Requests: 2 submitted requests
  • Payment Methods: 1 default payment method
  • Payment Transactions: Payment history

Landlord-2 (Jordan)

  • Properties: Riverside Condo
  • Leases: None (testing landlord with no leases)

How to Write Tests

Basic UI Test

import { expect, test } from "@playwright/test";
import {
  applyDeterministicPage,
  ROUTES,
  useE2EUser,
} from "../helpers/fixtures.ts";

test("landlord views dashboard", async ({ page }) => {
  // 1. Apply deterministic behavior
  await applyDeterministicPage(page);

  // 2. Set E2E user context
  await useE2EUser(page, "landlord");

  // 3. Navigate and test
  await page.goto(ROUTES.landlordDashboard);
  await expect(page.locator("h1")).toContainText("Dashboard");
});

API Test

import { expect, test } from "@playwright/test";
import { e2eHeaders } from "../helpers/fixtures.ts";

test("API returns landlord properties", async ({ request }) => {
  const response = await request.get("/api/retrieve/properties", {
    headers: e2eHeaders("landlord"),
  });

  expect(response.ok()).toBeTruthy();
  const data = await response.json();
  expect(data.properties).toHaveLength(2);
});

Test with Generated Data

import { generateTestProperty } from "../helpers/fixtures.ts";

test("create new property", async ({ page, request }) => {
  const newProperty = generateTestProperty({
    title: "My Test Property",
    bedrooms: 3,
  });

  const response = await request.post("/api/create/property", {
    headers: e2eHeaders("landlord"),
    data: newProperty,
  });

  expect(response.ok()).toBeTruthy();
});

Helper Functions Reference

useE2EUser(page, userType)

Sets the E2E user context for browser tests.

  • Parameters:
    • page: Playwright Page object
    • userType: E2E user type ("landlord", "tenant", etc.)
  • What it does:
    • Injects x-e2e-user header in all requests
    • Sets localStorage flag to skip encryption

applyDeterministicPage(page)

Makes page behavior deterministic for consistent tests.

  • Parameters:
    • page: Playwright Page object
  • What it does:
    • Stubs Date.now() to return fixed base time
    • Stubs Math.random() for consistent sequences
    • Disables CSS animations/transitions
    • Sets timezone to UTC

e2eHeaders(userType)

Creates headers for API requests with E2E authentication.

  • Parameters:
    • userType: E2E user type
  • Returns: Headers object with x-e2e-user and Content-Type

generateTestProperty(overrides?)

Generates a unique test property object.

  • Parameters:
    • overrides: Optional partial property data
  • Returns: Complete property object with unique values

generateTestLease(overrides?)

Generates a unique test lease object.

generateTestMessage(overrides?)

Generates a unique test message object.

generateTestMaintenanceRequest(overrides?)

Generates a unique test maintenance request object.

expectVisible(page, selector)

Asserts an element is visible.

expectText(page, selector, text)

Asserts an element contains specific text.

expectNoConsoleErrors(page)

Asserts no console errors occurred.

waitForPageLoad(page)

Waits for page to fully load (networkidle + domcontentloaded).

Routes Reference

Common routes are available in ROUTES constant:

ROUTES.home; // "/"
ROUTES.landlordDashboard; // "/landlord/dashboard"
ROUTES.tenantDashboard; // "/tenant/dashboard"
ROUTES.landlordProperties; // "/landlord/properties"
ROUTES.landlordLeases; // "/landlord/leases"
ROUTES.tenantLeases; // "/tenant/leases"
// ... and more

Running Tests

Run all E2E tests

deno task e2e

Run specific test file

deno run -A npm:playwright test tests/e2e/example.test.ts

Run with UI mode

deno task e2e:ui

View test report

deno task e2e:report

Best Practices

1. Always Apply Deterministic Behavior

test.beforeEach(async ({ page }) => {
  await applyDeterministicPage(page);
});

2. Set User Context Early

test("my test", async ({ page }) => {
  await useE2EUser(page, "landlord"); // Do this first
  await page.goto("/some/page"); // Then navigate
});

3. Use Generated Data for Uniqueness

// Good - unique every time
const property = generateTestProperty();

// Bad - may conflict
const property = { title: "Test Property" };

4. Wait for Page Load

await page.goto("/some/page");
await waitForPageLoad(page); // Always wait after navigation

5. Check for Console Errors

await expectNoConsoleErrors(page); // Catch JS errors

6. Use Resilient Assertions

// Good - checks for section header
await expectText(page, "h1", "Dashboard");

// Bad - assumes specific data that may change
await expectText(page, "text=Skyline Lofts", "Skyline Lofts");

Data Isolation Testing

The E2E infrastructure supports testing data isolation:

test("users cannot see each other's data", async ({ page }) => {
  // Landlord view
  await useE2EUser(page, "landlord");
  await page.goto(ROUTES.landlordProperties);
  await expect(page.locator("text=Skyline Lofts")).toBeVisible();

  // Tenant view (should not see landlord properties)
  await useE2EUser(page, "tenant");
  await page.goto(ROUTES.tenantLeases);
  await expect(page.locator("text=Skyline Lofts")).not.toBeVisible();
});

Troubleshooting

Tests Failing with "Not Found"

  • Ensure the dev server is running with E2E_TEST=true
  • Check that routes exist in the codebase

Tests Flaky / Inconsistent

  • Ensure applyDeterministicPage() is called
  • Check that you're using waitForPageLoad() after navigation
  • Verify no real-time timers or intervals in your code

Data Not Persisting

  • Remember: E2E tests use in-memory fixtures
  • Data created in one test persists for that test run
  • Use unique generated data to avoid conflicts

Authentication Issues

  • Verify x-e2e-user header is being set
  • Check services/stateMiddleware.ts for E2E logic
  • Ensure E2E_TEST=true is set in environment

Additional Resources

  • Playwright Documentation
  • services/e2eFixtures.ts - Fixture data definitions
  • services/stateMiddleware.ts - E2E authentication bypass
  • playwright.config.ts - Test configuration