veza/tools/tests/e2e/specs/auth.spec.ts
2025-12-03 22:56:50 +01:00

269 lines
No EOL
9.2 KiB
TypeScript

import { test, expect, Page } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs';
// Test data
const testEmail = `user+e2e${Date.now()}@lab.veza`;
const testPassword = `V3za!e2e-${Date.now()}`;
const sessionFile = path.join(__dirname, '../../.session-e2e.json');
// Helper functions
async function saveSession(page: Page) {
const localStorage = await page.evaluate(() => {
return {
access_token: window.localStorage.getItem('access_token'),
refresh_token: window.localStorage.getItem('refresh_token'),
user: window.localStorage.getItem('user'),
};
});
fs.writeFileSync(sessionFile, JSON.stringify(localStorage, null, 2));
}
async function loadSession(page: Page) {
if (fs.existsSync(sessionFile)) {
const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8'));
await page.evaluate((session) => {
if (session.access_token) window.localStorage.setItem('access_token', session.access_token);
if (session.refresh_token) window.localStorage.setItem('refresh_token', session.refresh_token);
if (session.user) window.localStorage.setItem('user', session.user);
}, session);
}
}
test.describe('Authentication Flow', () => {
test.beforeEach(async ({ page }) => {
// Clear storage
await page.context().clearCookies();
await page.evaluate(() => window.localStorage.clear());
});
test('should show login page for unauthenticated users', async ({ page }) => {
await page.goto('/');
// Should redirect to login
await expect(page).toHaveURL(/\/(login|auth)/);
// Check for login form elements
await expect(page.locator('input[type="email"], input[name="email"]')).toBeVisible();
await expect(page.locator('input[type="password"], input[name="password"]')).toBeVisible();
await expect(page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")')).toBeVisible();
});
test('should show validation errors for invalid inputs', async ({ page }) => {
await page.goto('/login');
// Try to submit empty form
await page.locator('button[type="submit"], button:has-text("Login")').click();
// Should show validation errors
await expect(page.locator('text=/email.*required/i')).toBeVisible({ timeout: 5000 });
// Try invalid email
await page.fill('input[type="email"]', 'invalid-email');
await page.fill('input[type="password"]', 'short');
await page.locator('button[type="submit"]').click();
// Should show email validation error
await expect(page.locator('text=/invalid.*email/i')).toBeVisible({ timeout: 5000 });
});
test('should register new user successfully', async ({ page }) => {
await page.goto('/register');
// Fill registration form
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
// Check for password confirmation field
const confirmField = page.locator('input[name="confirmPassword"], input[name="password_confirmation"]');
if (await confirmField.isVisible()) {
await confirmField.fill(testPassword);
}
// Submit form
await page.locator('button[type="submit"], button:has-text("Register"), button:has-text("Sign up")').click();
// Should redirect to dashboard or show success
await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 });
// Save session
await saveSession(page);
// Verify user is logged in
await expect(page.locator('text=/logout|sign out/i')).toBeVisible({ timeout: 5000 });
});
test('should prevent duplicate registration', async ({ page }) => {
await page.goto('/register');
// Try to register with same email
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
const confirmField = page.locator('input[name="confirmPassword"], input[name="password_confirmation"]');
if (await confirmField.isVisible()) {
await confirmField.fill(testPassword);
}
await page.locator('button[type="submit"]').click();
// Should show error
await expect(page.locator('text=/already.*exist|already.*registered|duplicate/i')).toBeVisible({ timeout: 5000 });
});
test('should login with valid credentials', async ({ page }) => {
await page.goto('/login');
// Fill login form
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
// Submit
await page.locator('button[type="submit"], button:has-text("Login")').click();
// Should redirect to dashboard
await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 });
// Save session
await saveSession(page);
// Verify user is logged in
await expect(page.locator('text=/logout|sign out/i')).toBeVisible({ timeout: 5000 });
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('/login');
// Try wrong password
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', 'WrongPassword123!');
await page.locator('button[type="submit"]').click();
// Should show error
await expect(page.locator('text=/invalid.*credentials|wrong.*password|authentication.*failed/i')).toBeVisible({ timeout: 5000 });
});
test('should handle token refresh', async ({ page }) => {
// Load previous session
await loadSession(page);
// Go to protected page
await page.goto('/dashboard');
// Should still be authenticated
await expect(page).toHaveURL(/\/(dashboard|home|app)/);
// Simulate token expiry by modifying localStorage
await page.evaluate(() => {
const token = window.localStorage.getItem('access_token');
if (token) {
// Modify token to make it invalid
window.localStorage.setItem('access_token', token + '_expired');
}
});
// Trigger an API call that should refresh the token
await page.reload();
// Should either still work (refresh successful) or redirect to login
const url = page.url();
expect(url).toMatch(/\/(dashboard|home|app|login)/);
});
test('should logout successfully', async ({ page }) => {
// Load session and login
await loadSession(page);
await page.goto('/dashboard');
// Find and click logout
const logoutButton = page.locator('button:has-text("Logout"), a:has-text("Logout"), button:has-text("Sign out")');
await logoutButton.click();
// Should redirect to login
await expect(page).toHaveURL(/\/(login|auth)/, { timeout: 5000 });
// Should clear session
const localStorage = await page.evaluate(() => {
return {
access_token: window.localStorage.getItem('access_token'),
refresh_token: window.localStorage.getItem('refresh_token'),
};
});
expect(localStorage.access_token).toBeNull();
expect(localStorage.refresh_token).toBeNull();
});
test('should handle remember me functionality', async ({ page }) => {
await page.goto('/login');
// Check if remember me exists
const rememberMe = page.locator('input[type="checkbox"][name="remember"], label:has-text("Remember me")');
if (await rememberMe.isVisible()) {
await rememberMe.check();
}
// Login
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
await page.locator('button[type="submit"]').click();
// Wait for redirect
await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 });
// Check if cookies are set with longer expiry
const cookies = await page.context().cookies();
const authCookie = cookies.find(c => c.name.includes('auth') || c.name.includes('session'));
if (authCookie) {
// Cookie should have expiry set (not session cookie)
expect(authCookie.expires).toBeGreaterThan(Date.now() / 1000);
}
});
test('should handle social login buttons', async ({ page }) => {
await page.goto('/login');
// Check for social login buttons
const googleButton = page.locator('button:has-text("Google"), a:has-text("Google")');
const githubButton = page.locator('button:has-text("GitHub"), a:has-text("GitHub")');
// Test visibility (they might not be implemented)
const hasGoogle = await googleButton.isVisible().catch(() => false);
const hasGithub = await githubButton.isVisible().catch(() => false);
if (hasGoogle) {
// Click should navigate to OAuth provider
const [popup] = await Promise.all([
page.waitForEvent('popup'),
googleButton.click()
]).catch(() => [null]);
if (popup) {
expect(popup.url()).toContain('google.com');
await popup.close();
}
}
if (hasGithub) {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
githubButton.click()
]).catch(() => [null]);
if (popup) {
expect(popup.url()).toContain('github.com');
await popup.close();
}
}
});
});
// Cleanup
test.afterAll(() => {
if (fs.existsSync(sessionFile)) {
fs.unlinkSync(sessionFile);
}
});