WellNuo/e2e/helpers/test-helpers.ts
Sergei 67496d6913 Add comprehensive E2E tests for critical flows
Implement Playwright E2E tests covering 43 critical user flows:
- Authentication: login, OTP verification, validation, onboarding
- Beneficiary management: list, detail, equipment, subscription navigation
- Subscription: status display, Stripe integration, demo mode
- Profile: settings, edit, logout flow

Includes:
- Page Object Models for test maintainability
- Test helpers with common utilities
- Updated Playwright config with proper project structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 10:22:47 -08:00

237 lines
6.2 KiB
TypeScript

/**
* E2E Test Helpers for WellNuo
* Common utilities for Playwright tests
*/
import { Page, expect } from '@playwright/test';
// E2E tests run against local Expo web server
// Start with: npm run web (expo start --web)
export const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:8081';
// Test credentials (use bypass OTP for testing)
export const TEST_CREDENTIALS = {
// Test user with existing beneficiary
existingUser: {
email: 'e2e.test@wellnuo.com',
bypassOtp: '000000',
},
// Test user for new registration
newUser: {
email: `e2e.new.${Date.now()}@test.com`,
bypassOtp: '000000',
},
// Invalid test cases
invalid: {
email: 'invalid-email',
wrongOtp: '999999',
},
};
/**
* Wait for app to fully load
*/
export async function waitForAppLoad(page: Page, timeout = 15000): Promise<void> {
await page.waitForLoadState('networkidle', { timeout });
}
/**
* Navigate to login page and verify it loaded
*/
export async function goToLogin(page: Page): Promise<void> {
await page.goto(BASE_URL);
await waitForAppLoad(page);
await expect(page.getByText('Welcome to WellNuo')).toBeVisible({ timeout: 10000 });
}
/**
* Enter email and proceed to OTP screen
*/
export async function enterEmailAndSubmit(page: Page, email: string): Promise<void> {
await page.getByPlaceholder('Enter your email').fill(email);
await page.getByText('Continue').click();
}
/**
* Enter OTP code using keyboard (handles hidden input)
*/
export async function enterOtpCode(page: Page, code: string): Promise<void> {
// Click on the OTP container to focus
await page.click('body');
await page.keyboard.type(code, { delay: 50 });
}
/**
* Wait for OTP screen to appear
*/
export async function waitForOtpScreen(page: Page): Promise<void> {
await expect(page.getByText('Check your email')).toBeVisible({ timeout: 15000 });
}
/**
* Complete login flow (email + OTP)
*/
export async function loginWithOtp(
page: Page,
email: string,
otp: string = '000000'
): Promise<void> {
await goToLogin(page);
await enterEmailAndSubmit(page, email);
await waitForOtpScreen(page);
await enterOtpCode(page, otp);
// Wait for navigation after OTP
await page.waitForTimeout(2000);
}
/**
* Wait for dashboard/main screen
*/
export async function waitForDashboard(page: Page): Promise<void> {
// Could be beneficiary list or dashboard depending on user state
await expect(
page.getByText('My Loved Ones').or(page.getByText('Dashboard'))
).toBeVisible({ timeout: 15000 });
}
/**
* Wait for enter-name screen (new user onboarding)
*/
export async function waitForEnterNameScreen(page: Page): Promise<void> {
await expect(
page.getByText('What should we call you?').or(page.getByPlaceholder('Your name'))
).toBeVisible({ timeout: 10000 });
}
/**
* Wait for add-loved-one screen
*/
export async function waitForAddLovedOneScreen(page: Page): Promise<void> {
await expect(
page.getByText('Add a Loved One').or(page.getByText('loved one'))
).toBeVisible({ timeout: 10000 });
}
/**
* Check if still on login page
*/
export async function isOnLoginPage(page: Page): Promise<boolean> {
return await page.getByText('Welcome to WellNuo').isVisible({ timeout: 1000 }).catch(() => false);
}
/**
* Check if still on OTP page
*/
export async function isOnOtpPage(page: Page): Promise<boolean> {
return await page.getByText('Check your email').isVisible({ timeout: 1000 }).catch(() => false);
}
/**
* Navigate to beneficiary detail
*/
export async function goToBeneficiaryDetail(page: Page, beneficiaryName?: string): Promise<void> {
if (beneficiaryName) {
await page.getByText(beneficiaryName).click();
} else {
// Click first beneficiary card
await page.locator('[data-testid="beneficiary-card"]').first().click();
}
await page.waitForTimeout(1000);
}
/**
* Navigate to subscription screen for a beneficiary
*/
export async function goToSubscription(page: Page): Promise<void> {
await page.getByText('Subscription').click();
await page.waitForTimeout(1000);
}
/**
* Navigate to equipment screen for a beneficiary
*/
export async function goToEquipment(page: Page): Promise<void> {
await page.getByText('Equipment').or(page.getByText('Sensors')).click();
await page.waitForTimeout(1000);
}
/**
* Navigate to profile
*/
export async function goToProfile(page: Page): Promise<void> {
await page.getByText('Profile').click();
await page.waitForTimeout(1000);
}
/**
* Click back button
*/
export async function goBack(page: Page): Promise<void> {
const backButton = page.locator('[data-testid="back-button"]').or(
page.getByText('Use a different email')
);
if (await backButton.isVisible()) {
await backButton.click();
await page.waitForTimeout(500);
} else {
// Try browser back
await page.goBack();
}
}
/**
* Log browser console messages (for debugging)
*/
export function enableConsoleLogging(page: Page): void {
page.on('console', msg => {
if (msg.type() !== 'log') return;
console.log(`BROWSER: ${msg.text()}`);
});
page.on('pageerror', err => console.log(`ERROR: ${err.message}`));
}
/**
* Take named screenshot
*/
export async function screenshot(page: Page, name: string): Promise<void> {
await page.screenshot({
path: `screenshots/${name}.png`,
quality: 50,
scale: 'css',
});
}
/**
* Verify error message is displayed
*/
export async function expectErrorMessage(page: Page, pattern: RegExp | string): Promise<void> {
if (typeof pattern === 'string') {
await expect(page.getByText(pattern)).toBeVisible({ timeout: 5000 });
} else {
await expect(page.getByText(pattern)).toBeVisible({ timeout: 5000 });
}
}
/**
* Wait for toast/notification
*/
export async function waitForToast(page: Page, text: string | RegExp): Promise<void> {
await expect(page.getByText(text)).toBeVisible({ timeout: 5000 });
}
/**
* Check API response in network
*/
export async function interceptApiCall(
page: Page,
urlPattern: string | RegExp,
callback: (response: { status: number; body: unknown }) => void
): Promise<void> {
page.on('response', async response => {
if (response.url().match(urlPattern)) {
const body = await response.json().catch(() => null);
callback({ status: response.status(), body });
}
});
}