Files
timmal/tests/e2e/auth-session-persistence.spec.ts

235 lines
7.9 KiB
TypeScript

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`
*/