# Makerkit E2E Testing Patterns

## Page Objects Location

`apps/e2e/tests/*.po.ts`

## Auth Page Object

```typescript
export class AuthPageObject {
  constructor(private readonly page: Page) {}

  static MFA_KEY = 'test-mfa-key';

  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="workspace-dropdown-trigger"]');
    await this.page.click('[data-test="workspace-sign-out"]');
  }

  async bootstrapUser(params: { email: string; password: string; name: string }) {
    // Creates user via API
    await fetch('/api/test/create-user', {
      method: 'POST',
      body: JSON.stringify(params),
    });
  }

  async loginAsUser(params: { email: string; password: string }) {
    await this.page.goto('/auth/sign-in');
    await this.signIn(params);
    await this.page.waitForURL('**/home/**');
  }

  createRandomEmail() {
    const value = Math.random() * 10000000000000;
    return `${value.toFixed(0)}@makerkit.dev`;
  }
}
```

## Common Selectors

```typescript
// Workspace dropdown (sidebar header - combined account switcher + user menu)
'[data-test="workspace-dropdown-trigger"]'  // Opens the dropdown
'[data-test="workspace-switch-submenu"]'    // Sub-trigger for workspace switching
'[data-test="workspace-switch-content"]'    // Sub-menu content with workspace list
'[data-test="workspace-team-item"]'         // Individual team items in switcher
'[data-test="create-team-trigger"]'         // Create team button in switcher
'[data-test="workspace-sign-out"]'          // Sign out button
'[data-test="workspace-settings-link"]'     // Settings link
'[data-test="account-dropdown-display-name"]' // User display name (inside dropdown panel)

// Opening the workspace switcher (two-step: open dropdown, then submenu)
await page.click('[data-test="workspace-dropdown-trigger"]');
await page.click('[data-test="workspace-switch-submenu"]');

// Navigation
'[data-test="sidebar-menu"]'
'[data-test="mobile-menu-trigger"]'

// Forms
'[data-test="submit-button"]'
'[data-test="cancel-button"]'

// Modals
'[data-test="dialog-confirm"]'
'[data-test="dialog-cancel"]'
```

## Test Setup Pattern

```typescript
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  const auth = new AuthPageObject(page);

  await auth.bootstrapUser({
    email: 'test@example.com',
    password: 'password123',
    name: 'Test User',
  });

  await auth.loginAsUser({
    email: 'test@example.com',
    password: 'password123',
  });

  // Save authentication state
  await page.context().storageState({ path: '.auth/user.json' });
});
```

## Reliability Patterns

### OTP/Email Operations

```typescript
await expect(async () => {
  const otpCode = await this.getOtpCodeFromEmail(email);
  expect(otpCode).not.toBeNull();
  await this.enterOtpCode(otpCode);
}).toPass();
```

### MFA Verification

```typescript
await expect(async () => {
  await auth.submitMFAVerification(AuthPageObject.MFA_KEY);
}).toPass({
  intervals: [500, 2500, 5000, 7500, 10_000, 15_000, 20_000]
});
```

### Network Requests

```typescript
await expect(async () => {
  const response = await this.page.waitForResponse(
    resp => resp.url().includes('auth/v1/user')
  );
  expect(response.status()).toBe(200);
}).toPass();
```

## Test Organization

```
apps/e2e/
├── playwright.config.ts
├── tests/
│   ├── auth.setup.ts
│   ├── authentication/
│   │   ├── sign-in.spec.ts
│   │   └── sign-up.spec.ts
│   ├── billing/
│   │   └── subscription.spec.ts
│   ├── teams/
│   │   └── invitations.spec.ts
│   └── utils/
│       └── auth.po.ts
└── .auth/
    └── user.json
```

## Running Tests

```bash
# Single file
pnpm --filter web-e2e exec playwright test authentication --workers=1

# With UI
pnpm --filter web-e2e exec playwright test --ui

# Debug mode
pnpm --filter web-e2e exec playwright test --debug
```
