# 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 ```typescript 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 ```typescript 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 ```typescript 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: ```typescript 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 ```bash deno task e2e ``` ### Run specific test file ```bash deno run -A npm:playwright test tests/e2e/example.test.ts ``` ### Run with UI mode ```bash deno task e2e:ui ``` ### View test report ```bash deno task e2e:report ``` ## Best Practices ### 1. Always Apply Deterministic Behavior ```typescript test.beforeEach(async ({ page }) => { await applyDeterministicPage(page); }); ``` ### 2. Set User Context Early ```typescript 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 ```typescript // Good - unique every time const property = generateTestProperty(); // Bad - may conflict const property = { title: "Test Property" }; ``` ### 4. Wait for Page Load ```typescript await page.goto("/some/page"); await waitForPageLoad(page); // Always wait after navigation ``` ### 5. Check for Console Errors ```typescript await expectNoConsoleErrors(page); // Catch JS errors ``` ### 6. Use Resilient Assertions ```typescript // 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: ```typescript 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](https://playwright.dev/) - `services/e2eFixtures.ts` - Fixture data definitions - `services/stateMiddleware.ts` - E2E authentication bypass - `playwright.config.ts` - Test configuration