- Replaced static paths with dynamic locale subpaths (`/[locale]/*`) in imports, URLs, and assertions across tests. - Updated `next-intl` mocks for improved compatibility with `locale`-aware components. - Standardized `page.goto` and navigation tests with `/en` as the base locale for consistency.
214 lines
7.2 KiB
TypeScript
214 lines
7.2 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Login Flow', () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
// Collect browser console logs per test for debugging
|
|
let consoleLogs: string[] = [];
|
|
|
|
test.beforeEach(async ({ page, context }) => {
|
|
consoleLogs = [];
|
|
|
|
// Capture console logs
|
|
page.on('console', (msg) => {
|
|
try {
|
|
consoleLogs.push(`[${msg.type()}] ${msg.text()}`);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
});
|
|
|
|
// Ensure clean state across parallel workers
|
|
await context.clearCookies();
|
|
await page.addInitScript(() => {
|
|
try {
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
} catch {}
|
|
});
|
|
|
|
// Navigate to login page before each test
|
|
await page.goto('/en/login');
|
|
});
|
|
|
|
test.afterEach(async ({ page }, testInfo) => {
|
|
if (testInfo.status !== 'passed') {
|
|
// Attach current URL
|
|
await testInfo.attach('page-url.txt', {
|
|
body: page.url(),
|
|
contentType: 'text/plain',
|
|
});
|
|
|
|
// Attach skeleton count to see if page stuck on loading state
|
|
try {
|
|
const skeletonCount = await page.locator('.animate-pulse').count();
|
|
await testInfo.attach('skeleton-count.txt', {
|
|
body: String(skeletonCount),
|
|
contentType: 'text/plain',
|
|
});
|
|
} catch {}
|
|
|
|
// Attach full DOM snapshot
|
|
try {
|
|
const html = await page.content();
|
|
await testInfo.attach('dom.html', {
|
|
body: html,
|
|
contentType: 'text/html',
|
|
});
|
|
} catch {}
|
|
|
|
// Attach full page screenshot
|
|
try {
|
|
const img = await page.screenshot({ fullPage: true });
|
|
await testInfo.attach('screenshot.png', {
|
|
body: img,
|
|
contentType: 'image/png',
|
|
});
|
|
} catch {}
|
|
|
|
// Attach console logs
|
|
try {
|
|
await testInfo.attach('console.log', {
|
|
body: consoleLogs.join('\n'),
|
|
contentType: 'text/plain',
|
|
});
|
|
} catch {}
|
|
}
|
|
});
|
|
|
|
test('should display login form', async ({ page }) => {
|
|
// Check page title
|
|
await expect(page.locator('h2')).toContainText('Sign in to your account');
|
|
|
|
// Check form elements exist
|
|
await expect(page.locator('input[name="email"]')).toBeVisible();
|
|
await expect(page.locator('input[name="password"]')).toBeVisible();
|
|
await expect(page.locator('button[type="submit"]')).toBeVisible();
|
|
|
|
// Check links
|
|
await expect(page.getByText('Forgot password?')).toBeVisible();
|
|
await expect(page.getByText("Don't have an account?")).toBeVisible();
|
|
});
|
|
|
|
test('should show validation errors for empty form', async ({ page }) => {
|
|
// Ensure the dynamically loaded form is mounted and interactive
|
|
const emailInput = page.locator('input[name="email"]');
|
|
const passwordInput = page.locator('input[name="password"]');
|
|
const submitButton = page.locator('button[type="submit"]');
|
|
|
|
await expect(emailInput).toBeVisible();
|
|
await expect(passwordInput).toBeVisible();
|
|
await expect(submitButton).toBeVisible();
|
|
await expect(submitButton).toBeEnabled();
|
|
|
|
// Touch fields to mimic user interaction
|
|
await emailInput.focus();
|
|
await emailInput.blur();
|
|
await passwordInput.focus();
|
|
await passwordInput.blur();
|
|
|
|
// Submit empty form
|
|
await submitButton.click();
|
|
|
|
// Wait for validation errors - allow extra time for slower browsers
|
|
await expect(page.locator('#email-error')).toBeVisible();
|
|
await expect(page.locator('#password-error')).toBeVisible();
|
|
|
|
// Verify error messages
|
|
await expect(page.locator('#email-error')).toContainText('Email is required');
|
|
await expect(page.locator('#password-error')).toContainText('Password');
|
|
});
|
|
|
|
test('should show validation error for invalid email', async ({ page }) => {
|
|
// Fill invalid email and submit
|
|
await page.locator('input[name="email"]').fill('invalid-email');
|
|
await page.locator('input[name="password"]').fill('Password123!');
|
|
|
|
await page.locator('button[type="submit"]').click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Should stay on login page (validation failed)
|
|
await expect(page).toHaveURL('/en/login');
|
|
});
|
|
|
|
test('should show error for invalid credentials', async ({ page }) => {
|
|
// Fill with invalid credentials
|
|
await page.locator('input[name="email"]').fill('wrong@example.com');
|
|
await page.locator('input[name="password"]').fill('WrongPassword123!');
|
|
|
|
// Submit form
|
|
await page.locator('button[type="submit"]').click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Without backend, we just verify form is still functional (doesn't crash)
|
|
// Should still be on login page
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
|
|
test('should successfully login with valid credentials', async ({ page }) => {
|
|
// Note: This test requires a valid test user in the backend
|
|
// Fill with valid test credentials
|
|
await page.locator('input[name="email"]').fill('test@example.com');
|
|
await page.locator('input[name="password"]').fill('TestPassword123!');
|
|
|
|
// Submit form
|
|
await page.locator('button[type="submit"]').click();
|
|
|
|
// Wait for redirect or error (will likely error without backend)
|
|
await page.waitForTimeout(2000);
|
|
});
|
|
|
|
test('should navigate to forgot password page', async ({ page }) => {
|
|
// Click forgot password link - use Promise.all to wait for navigation
|
|
const forgotLink = page.getByRole('link', { name: 'Forgot password?' });
|
|
|
|
await Promise.all([page.waitForURL('/en/password-reset'), forgotLink.click()]);
|
|
|
|
// Should be on password reset page
|
|
await expect(page).toHaveURL('/en/password-reset');
|
|
await expect(page.locator('h2')).toContainText('Reset your password');
|
|
});
|
|
|
|
test('should navigate to register page', async ({ page }) => {
|
|
// Click sign up link - use Promise.all to wait for navigation
|
|
const signupLink = page.getByRole('link', { name: 'Sign up' });
|
|
|
|
await Promise.all([page.waitForURL('/en/register'), signupLink.click()]);
|
|
|
|
// Should be on register page
|
|
await expect(page).toHaveURL('/en/register');
|
|
await expect(page.locator('h2')).toContainText('Create your account');
|
|
});
|
|
|
|
test('should toggle password visibility', async ({ page }) => {
|
|
const passwordInput = page.locator('input[name="password"]');
|
|
|
|
// Password should start as hidden
|
|
await expect(passwordInput).toHaveAttribute('type', 'password');
|
|
|
|
// Note: If password toggle is implemented, test it here
|
|
// For now, just verify initial state
|
|
});
|
|
|
|
test('should disable submit button while loading', async ({ page }) => {
|
|
// Fill form
|
|
await page.locator('input[name="email"]').fill('test@example.com');
|
|
await page.locator('input[name="password"]').fill('Password123!');
|
|
|
|
const submitButton = page.locator('button[type="submit"]');
|
|
|
|
// Submit form
|
|
await submitButton.click();
|
|
|
|
// Wait briefly to check loading state
|
|
await page.waitForTimeout(100);
|
|
|
|
// Button should either be disabled or show loading text
|
|
const isDisabled = await submitButton.isDisabled().catch(() => false);
|
|
const buttonText = await submitButton.textContent();
|
|
|
|
// Accept either disabled state or loading text
|
|
expect(isDisabled || buttonText?.toLowerCase().includes('sign')).toBeTruthy();
|
|
});
|
|
});
|