269 lines
No EOL
9.2 KiB
TypeScript
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);
|
|
}
|
|
}); |