import { test, expect } from '@playwright/test'; /** * E2E Test: Session Persistence Across Browser Restarts * * User Story: US4 - Session Persistence * Task: T011 * * **Purpose**: Verify that authenticated users remain logged in after page reload * * **Acceptance Criteria**: * - AC-1: After login, user reloads page and remains on /dashboard (not redirected to /login) * - AC-2: After reload, user name is still visible in navbar * - AC-3: After reload, user can still access protected pages * * **Technical Implementation**: * - Tests rely on Pocketbase authStore persistence via localStorage * - initAuth() composable function restores session on mount via auth plugin * - Auth state is synchronized via pb.authStore.onChange listener * * **Test Flow**: * 1. Navigate to /login * 2. Authenticate via OAuth (mock provider) * 3. Verify redirect to /dashboard * 4. Verify user name visible in navbar * 5. Reload page * 6. Verify still on /dashboard (no redirect to /login) * 7. Verify user name still visible in navbar * * **Notes**: * - Requires Pocketbase OAuth mock provider configured for testing * - May need to configure test-specific OAuth provider or use Pocketbase test mode * - Session persistence depends on valid auth token not being expired */ test.describe('Session Persistence', () => { test.beforeEach(async ({ page }) => { // Clear any existing auth state before each test await page.context().clearCookies(); await page.evaluate(() => localStorage.clear()); }); test('should maintain authentication after page reload', async ({ page }) => { // Step 1: Navigate to login page await page.goto('/login'); await expect(page).toHaveURL('/login'); // Step 2: Authenticate via OAuth // NOTE: This requires a mock OAuth provider configured in Pocketbase for testing // The actual implementation will depend on the test environment setup // For now, this is a placeholder that documents the expected flow // TODO: Replace with actual OAuth flow once test provider is configured // Example mock implementation: // await page.click('[data-testid="oauth-google"]'); // await page.waitForURL('/dashboard'); // Placeholder: Simulate successful OAuth by setting auth state directly await page.evaluate(() => { // Mock Pocketbase authStore for testing const mockAuthData = { token: 'mock-jwt-token', record: { id: 'test-user-id', email: 'test@example.com', name: 'Test User', emailVisibility: false, verified: true, avatar: '', created: new Date().toISOString(), updated: new Date().toISOString() } }; localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData)); }); // Navigate to dashboard to trigger auth restoration await page.goto('/dashboard'); await expect(page).toHaveURL('/dashboard'); // Step 3: Verify user is authenticated - check for user name in navbar // NOTE: Adjust selector based on actual navbar implementation const userNameElement = page.locator('[data-testid="user-name"]').or( page.locator('text=Test User') ); await expect(userNameElement).toBeVisible({ timeout: 5000 }); // Step 4: Reload the page await page.reload(); // Step 5: Verify still on /dashboard (session persisted) await expect(page).toHaveURL('/dashboard'); // Step 6: Verify user name still visible after reload await expect(userNameElement).toBeVisible({ timeout: 5000 }); }); test('should persist session across browser context restarts', async ({ browser }) => { // Create initial context and authenticate const context1 = await browser.newContext(); const page1 = await context1.newPage(); await page1.goto('/login'); // Simulate OAuth authentication await page1.evaluate(() => { const mockAuthData = { token: 'mock-jwt-token', record: { id: 'test-user-id', email: 'test@example.com', name: 'Test User', emailVisibility: false, verified: true, avatar: '', created: new Date().toISOString(), updated: new Date().toISOString() } }; localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData)); }); await page1.goto('/dashboard'); await expect(page1).toHaveURL('/dashboard'); // Extract storage state to simulate browser restart const storageState = await context1.storageState(); await context1.close(); // Create new context with stored state (simulates browser restart) const context2 = await browser.newContext({ storageState }); const page2 = await context2.newPage(); // Navigate directly to dashboard await page2.goto('/dashboard'); // Verify session persisted - should not redirect to /login await expect(page2).toHaveURL('/dashboard'); // Verify user info still available const userNameElement = page2.locator('[data-testid="user-name"]').or( page2.locator('text=Test User') ); await expect(userNameElement).toBeVisible({ timeout: 5000 }); await context2.close(); }); test('should redirect to login when accessing protected page without session', async ({ page }) => { // Ensure no auth state exists await page.goto('/'); await page.evaluate(() => localStorage.clear()); // Try to access protected page await page.goto('/dashboard'); // Should redirect to login with redirect parameter await expect(page).toHaveURL(/\/login\?redirect=.*dashboard/); }); test('should restore session and allow access to other protected pages', async ({ page }) => { // Set up authenticated session await page.goto('/login'); await page.evaluate(() => { const mockAuthData = { token: 'mock-jwt-token', record: { id: 'test-user-id', email: 'test@example.com', name: 'Test User', emailVisibility: false, verified: true, avatar: '', created: new Date().toISOString(), updated: new Date().toISOString() } }; localStorage.setItem('pocketbase_auth', JSON.stringify(mockAuthData)); }); await page.goto('/dashboard'); await expect(page).toHaveURL('/dashboard'); // Reload to test session persistence await page.reload(); await expect(page).toHaveURL('/dashboard'); // Navigate to other protected pages // NOTE: Adjust these based on actual application routes // await page.goto('/projects'); // await expect(page).toHaveURL('/projects'); // await page.goto('/tasks'); // await expect(page).toHaveURL('/tasks'); }); }); /** * IMPLEMENTATION NOTES FOR TEST SETUP: * * 1. **Playwright Configuration Required**: * - Create `playwright.config.ts` in project root * - Configure base URL to match dev server (http://localhost:3000) * - Set up test fixtures for Pocketbase mock * * 2. **Pocketbase Test Provider Setup**: * Option A: Use Pocketbase test mode with mock OAuth provider * Option B: Create test-specific OAuth provider (e.g., test-provider) * Option C: Mock OAuth at network level using Playwright's route interception * * 3. **Mock OAuth Flow** (Recommended Approach): * ```typescript * await page.route('**/api/collections/users/auth-with-oauth2', async route => { * await route.fulfill({ * status: 200, * body: JSON.stringify({ * token: 'mock-jwt', * record: { ... } * }) * }); * }); * ``` * * 4. **Data Test IDs**: * Add the following to components: * - Navbar user name: `data-testid="user-name"` * - OAuth provider buttons: `data-testid="oauth-{provider}"` * - Logout button: `data-testid="logout-button"` * * 5. **Running Tests**: * - Install Playwright: `pnpm add -D @playwright/test` * - Add to package.json: `"test:e2e": "playwright test"` * - Run: `pnpm test:e2e` */