## End-to-End Testing with Playwright ## Running Tests Running the tests for testing single file: ```bash pnpm --filter web-e2e exec playwright test --workers=1 ``` Example: ```bash pnpm --filter web-e2e exec playwright test --workers=1 ``` This is useful for quickly testing a single file or a specific feature and should be your default choice. Running all tests (rarely needed, only use if asked by the user): ```bash pnpm test ``` ### Page Object Pattern (Required) Always use Page Objects for test organization and reusability: ```typescript // Example: auth.po.ts export class AuthPageObject { constructor(private readonly page: Page) {} async signIn(params: { email: string; password: string }) { await this.page.fill('input[name="email"]', params.email); await this.page.fill('input[name="password"]', params.password); await this.page.click('button[type="submit"]'); } async signOut() { await this.page.click('[data-test="account-dropdown-trigger"]'); await this.page.click('[data-test="account-dropdown-sign-out"]'); } } ``` ### Reliability Patterns **Use `toPass()` for flaky operations** - Always wrap unreliable operations: ```typescript // ✅ CORRECT - Reliable email/OTP operations await expect(async () => { const otpCode = await this.getOtpCodeFromEmail(email); expect(otpCode).not.toBeNull(); await this.enterOtpCode(otpCode); }).toPass(); // ✅ CORRECT - Network requests with validation await expect(async () => { const response = await this.page.waitForResponse(resp => resp.url().includes('auth/v1/user') ); expect(response.status()).toBe(200); }).toPass(); // ✅ CORRECT - Complex operations with custom intervals await expect(async () => { await auth.submitMFAVerification(AuthPageObject.MFA_KEY); }).toPass({ intervals: [500, 2500, 5000, 7500, 10_000, 15_000, 20_000] }); ``` ### Test Data Management **Email Testing**: Use `createRandomEmail()` for unique test emails: ```typescript createRandomEmail() { const value = Math.random() * 10000000000000; return `${value.toFixed(0)}@makerkit.dev`; } ``` **User Bootstrapping**: Use `bootstrapUser()` for consistent test user creation: ```typescript await auth.bootstrapUser({ email: 'test@example.com', password: 'testingpassword', name: 'Test User' }); ``` This method creates a user with an API call. To sign in: ```tsx await auth.loginAsUser({ email: 'test@example.com', password: 'testingpassword', }); ``` ### Test Selectors **Always use `data-test` attributes** for reliable element selection: ```typescript // ✅ CORRECT - Use data-test attributes await this.page.click('[data-test="submit-button"]'); await this.page.fill('[data-test="email-input"]', email); // ✅ OR await this.page.getByTestId('submit-button').click(); // ❌ AVOID - Fragile selectors await this.page.click('.btn-primary'); await this.page.click('button:nth-child(2)'); ``` ### Test Organization - **Feature-based folders**: `/tests/authentication/`, `/tests/billing/` - **Page Objects**: `*.po.ts` files for reusable page interactions - **Setup files**: `auth.setup.ts` for global test setup - **Utility classes**: `/tests/utils/` for shared functionality