324 lines
8.2 KiB
Markdown
324 lines
8.2 KiB
Markdown
# 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
|