Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 21 additions & 63 deletions frontend/e2e/auth-fixes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import { test, expect, dismissCookieBanner } from './fixtures';

/**
* auth-fixes.spec.ts
* E2E tests that specifically cover the auth bugs fixed in the last PR:
*
* 1. Login form is visible on dark background (correct text/input colors)
* 2. Empty-field validation shows inline error (no navigation away)
* 3. Wrong-credentials shows inline error message (not a blank screen)
* 4. Guest login "Continue as Guest" button works end-to-end
* 5. "Sign up" link navigates to /register
* 6. "Sign in" link on register page navigates to /login
* E2E tests for auth pages in light mode — Login & Register.
*/

test.describe('Login Page — Visual & Validation', () => {
Expand All @@ -18,20 +11,19 @@ test.describe('Login Page — Visual & Validation', () => {
await dismissCookieBanner(page);
});

test('h1 heading is visible against dark background', async ({ page }) => {
test('h1 heading is visible and reads "Log in"', async ({ page }) => {
const heading = page.locator('h1');
await expect(heading).toBeVisible();
await expect(heading).toContainText('Welcome Back');
await expect(heading).toContainText('Log in');
});

test('email label is visible (dark-mode text color)', async ({ page }) => {
// Label must be visible — previously used text-gray-700 on dark bg
test('email label is visible', async ({ page }) => {
const emailLabel = page.locator('label[for="email"]');
await expect(emailLabel).toBeVisible();
await expect(emailLabel).toContainText('Email');
});

test('password label is visible (dark-mode text color)', async ({ page }) => {
test('password label is visible', async ({ page }) => {
const passLabel = page.locator('label[for="password"]');
await expect(passLabel).toBeVisible();
await expect(passLabel).toContainText('Password');
Expand All @@ -50,100 +42,72 @@ test.describe('Login Page — Visual & Validation', () => {
test('eye toggle reveals and hides password', async ({ page }) => {
const input = page.locator('#password');
await input.fill('secret123');

// Click the toggle button inside the password wrapper
const toggle = page.locator('#password').locator('..').locator('button');
await toggle.click();
await expect(input).toHaveAttribute('type', 'text');

await toggle.click();
await expect(input).toHaveAttribute('type', 'password');
});

test('remember me checkbox is visible and toggleable', async ({ page }) => {
const checkbox = page.locator('input[type="checkbox"]');
await expect(checkbox).toBeVisible();
await checkbox.check();
await expect(checkbox).toBeChecked();
await checkbox.uncheck();
await expect(checkbox).not.toBeChecked();
});

test('forgot password link is visible', async ({ page }) => {
await expect(page.locator('text=Forgot password?')).toBeVisible();
await expect(page.locator('text=Forgot password')).toBeVisible();
});

test('"Don\'t have an account? Sign up" link is visible', async ({ page }) => {
await expect(page.getByRole('link', { name: /sign up/i })).toBeVisible();
test('"Register Now" link is visible', async ({ page }) => {
await expect(page.getByRole('link', { name: /register now/i })).toBeVisible();
});

test('"Continue as Guest" button is visible', async ({ page }) => {
await expect(page.getByRole('button', { name: /continue as guest/i })).toBeVisible();
});
});

test.describe('Login Page — Validation errors (inline, no toast spam)', () => {
test.describe('Login Page — Validation errors', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await dismissCookieBanner(page);
});

test('shows inline error when both fields are empty', async ({ page }) => {
await page.getByRole('button', { name: /sign in$/i }).click();

// Inline error banner should appear inside the form
const errorBanner = page.locator('div.bg-red-900\\/50, [class*="bg-red"]').first();
await page.getByRole('button', { name: /sign in/i }).click();
const errorBanner = page.locator('[class*="bg-red"]').first();
await expect(errorBanner).toBeVisible({ timeout: 5000 });
await expect(errorBanner).toContainText(/fill in all fields/i);

// Must NOT have navigated away
await expect(page).toHaveURL('/login');
});

test('shows inline error when password field is empty', async ({ page }) => {
await page.locator('#email').fill('user@test.com');
await page.getByRole('button', { name: /sign in$/i }).click();

const errorBanner = page.locator('div.bg-red-900\\/50, [class*="bg-red"]').first();
await page.getByRole('button', { name: /sign in/i }).click();
const errorBanner = page.locator('[class*="bg-red"]').first();
await expect(errorBanner).toBeVisible({ timeout: 5000 });
await expect(page).toHaveURL('/login');
});

test('shows inline error (not just a toast) on wrong credentials', async ({ page }) => {
test('shows inline error on wrong credentials', async ({ page }) => {
await page.locator('#email').fill('wrong@test.com');
await page.locator('#password').fill('WrongPass123');
await page.getByRole('button', { name: /sign in$/i }).click();

// The inline red error box should appear
const errorBanner = page.locator('div.bg-red-900\\/50, [class*="bg-red"]').first();
await page.getByRole('button', { name: /sign in/i }).click();
const errorBanner = page.locator('[class*="bg-red"]').first();
await expect(errorBanner).toBeVisible({ timeout: 10000 });

// Page should still be /login (no redirect)
await expect(page).toHaveURL('/login');
});

test('sign-in button shows spinner while loading', async ({ page }) => {
await page.locator('#email').fill('test@example.com');
await page.locator('#password').fill('password123');

// Click and immediately check for spinner
await page.getByRole('button', { name: /sign in$/i }).click();

// The loader / spinner should appear briefly
const spinner = page.locator('.animate-spin');
// If the network is fast the spinner may disappear immediately — use a
// soft check rather than strict assertion
await spinner.isVisible().catch(() => false);
// Just assert the page stays on /login or moves to /dashboard — no crash
await page.getByRole('button', { name: /sign in/i }).click();
// Soft check — spinner may disappear fast on fast connections
await page.locator('.animate-spin').isVisible().catch(() => false);
await expect(page.locator('body')).toBeVisible();
});
});

test.describe('Login → Register navigation', () => {
test('clicking Sign up navigates to /register', async ({ page }) => {
test('clicking Register Now navigates to /register', async ({ page }) => {
await page.goto('/login');
await dismissCookieBanner(page);
await page.getByRole('link', { name: /sign up/i }).click();
await page.getByRole('link', { name: /register now/i }).click();
await expect(page).toHaveURL('/register');
});

Expand All @@ -157,7 +121,6 @@ test.describe('Login → Register navigation', () => {

test.describe('Guest Login Flow', () => {
test.beforeEach(async ({ page }) => {
// Start from a clean state
await page.goto('/privacy-policy');
await page.evaluate(() => localStorage.clear());
});
Expand All @@ -174,11 +137,8 @@ test.describe('Guest Login Flow', () => {
await dismissCookieBanner(page);
await page.getByRole('button', { name: /continue as guest/i }).click();
await expect(page).toHaveURL('/dashboard', { timeout: 15000 });

// Navigate to a protected page
await page.goto('/tasks');
await expect(page).toHaveURL('/tasks');

await page.goto('/pomodoro');
await expect(page).toHaveURL('/pomodoro');
});
Expand All @@ -188,8 +148,6 @@ test.describe('Guest Login Flow', () => {
await dismissCookieBanner(page);
await page.getByRole('button', { name: /continue as guest/i }).click();
await expect(page).toHaveURL('/dashboard', { timeout: 15000 });

// Try to go back to login — should be redirected
await page.goto('/login');
await expect(page).toHaveURL('/dashboard');
});
Expand Down
Loading
Loading