tests passing

This commit is contained in:
counterweight 2025-12-18 22:08:31 +01:00
parent 0995e1cc77
commit 7ebfb7a2dd
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
20 changed files with 2009 additions and 126 deletions

283
frontend/e2e/auth.spec.ts Normal file
View file

@ -0,0 +1,283 @@
import { test, expect, Page } from "@playwright/test";
// Helper to generate unique email for each test
function uniqueEmail(): string {
return `test-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`;
}
// Helper to clear localStorage
async function clearAuth(page: Page) {
await page.evaluate(() => localStorage.clear());
}
test.describe("Authentication Flow", () => {
test.beforeEach(async ({ page }) => {
await clearAuth(page);
});
test("redirects to login when not authenticated", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveURL("/login");
});
test("login page has correct form elements", async ({ page }) => {
await page.goto("/login");
await expect(page.locator("h1")).toHaveText("Welcome back");
await expect(page.locator('input[type="email"]')).toBeVisible();
await expect(page.locator('input[type="password"]')).toBeVisible();
await expect(page.locator('button[type="submit"]')).toHaveText("Sign in");
await expect(page.locator('a[href="/signup"]')).toBeVisible();
});
test("signup page has correct form elements", async ({ page }) => {
await page.goto("/signup");
await expect(page.locator("h1")).toHaveText("Create account");
await expect(page.locator('input[type="email"]')).toBeVisible();
await expect(page.locator('input[type="password"]').first()).toBeVisible();
await expect(page.locator('input[type="password"]').nth(1)).toBeVisible();
await expect(page.locator('button[type="submit"]')).toHaveText("Create account");
await expect(page.locator('a[href="/login"]')).toBeVisible();
});
test("can navigate from login to signup", async ({ page }) => {
await page.goto("/login");
await page.click('a[href="/signup"]');
await expect(page).toHaveURL("/signup");
});
test("can navigate from signup to login", async ({ page }) => {
await page.goto("/signup");
await page.click('a[href="/login"]');
await expect(page).toHaveURL("/login");
});
});
test.describe("Signup", () => {
test.beforeEach(async ({ page }) => {
await clearAuth(page);
});
test("can create a new account", async ({ page }) => {
const email = uniqueEmail();
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
// Should redirect to home after signup
await expect(page).toHaveURL("/");
// Should show user email
await expect(page.getByText(email)).toBeVisible();
});
test("shows error for duplicate email", async ({ page }) => {
const email = uniqueEmail();
// First registration
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
// Clear and try again with same email
await clearAuth(page);
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
// Should show error
await expect(page.getByText("Email already registered")).toBeVisible();
});
test("shows error for password mismatch", async ({ page }) => {
await page.goto("/signup");
await page.fill('input[type="email"]', uniqueEmail());
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("differentpassword");
await page.click('button[type="submit"]');
await expect(page.getByText("Passwords do not match")).toBeVisible();
});
test("shows error for short password", async ({ page }) => {
await page.goto("/signup");
await page.fill('input[type="email"]', uniqueEmail());
await page.fill('input[type="password"]', "short");
await page.locator('input[type="password"]').nth(1).fill("short");
await page.click('button[type="submit"]');
await expect(page.getByText("Password must be at least 6 characters")).toBeVisible();
});
test("shows loading state while submitting", async ({ page }) => {
await page.goto("/signup");
await page.fill('input[type="email"]', uniqueEmail());
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
// Start submission and check for loading state
const submitPromise = page.click('button[type="submit"]');
await expect(page.locator('button[type="submit"]')).toHaveText("Creating account...");
await submitPromise;
});
});
test.describe("Login", () => {
const testEmail = `login-test-${Date.now()}@example.com`;
const testPassword = "testpassword123";
test.beforeAll(async ({ browser }) => {
// Create a test user
const page = await browser.newPage();
await page.goto("/signup");
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
await page.locator('input[type="password"]').nth(1).fill(testPassword);
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
await page.close();
});
test.beforeEach(async ({ page }) => {
await clearAuth(page);
});
test("can login with valid credentials", async ({ page }) => {
await page.goto("/login");
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
await expect(page.getByText(testEmail)).toBeVisible();
});
test("shows error for wrong password", async ({ page }) => {
await page.goto("/login");
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', "wrongpassword");
await page.click('button[type="submit"]');
await expect(page.getByText("Incorrect email or password")).toBeVisible();
});
test("shows error for non-existent user", async ({ page }) => {
await page.goto("/login");
await page.fill('input[type="email"]', "nonexistent@example.com");
await page.fill('input[type="password"]', "password123");
await page.click('button[type="submit"]');
await expect(page.getByText("Incorrect email or password")).toBeVisible();
});
test("shows loading state while submitting", async ({ page }) => {
await page.goto("/login");
await page.fill('input[type="email"]', testEmail);
await page.fill('input[type="password"]', testPassword);
const submitPromise = page.click('button[type="submit"]');
await expect(page.locator('button[type="submit"]')).toHaveText("Signing in...");
await submitPromise;
});
});
test.describe("Logout", () => {
test("can logout", async ({ page }) => {
const email = uniqueEmail();
// Sign up first
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
// Click logout
await page.click("text=Sign out");
// Should redirect to login
await expect(page).toHaveURL("/login");
});
test("cannot access home after logout", async ({ page }) => {
const email = uniqueEmail();
// Sign up
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
// Logout
await page.click("text=Sign out");
await expect(page).toHaveURL("/login");
// Try to access home
await page.goto("/");
await expect(page).toHaveURL("/login");
});
});
test.describe("Session Persistence", () => {
test("session persists after page reload", async ({ page }) => {
const email = uniqueEmail();
// Sign up
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
await expect(page.getByText(email)).toBeVisible();
// Reload page
await page.reload();
// Should still be logged in
await expect(page).toHaveURL("/");
await expect(page.getByText(email)).toBeVisible();
});
test("token is stored in localStorage", async ({ page }) => {
const email = uniqueEmail();
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
// Check localStorage
const token = await page.evaluate(() => localStorage.getItem("token"));
expect(token).toBeTruthy();
expect(token!.length).toBeGreaterThan(10);
});
test("token is cleared on logout", async ({ page }) => {
const email = uniqueEmail();
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
await page.click("text=Sign out");
const token = await page.evaluate(() => localStorage.getItem("token"));
expect(token).toBeNull();
});
});

View file

@ -1,29 +1,148 @@
import { test, expect } from "@playwright/test";
import { test, expect, Page } from "@playwright/test";
test("displays counter value", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1")).not.toHaveText("...");
// Helper to generate unique email for each test
function uniqueEmail(): string {
return `counter-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`;
}
// Helper to authenticate a user
async function authenticate(page: Page): Promise<string> {
const email = uniqueEmail();
await page.evaluate(() => localStorage.clear());
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
return email;
}
test.describe("Counter - Authenticated", () => {
test("displays counter value", async ({ page }) => {
await authenticate(page);
await expect(page.locator("h1")).toBeVisible();
// Counter should be a number (not loading state)
const text = await page.locator("h1").textContent();
expect(text).toMatch(/^\d+$/);
});
test("displays current count label", async ({ page }) => {
await authenticate(page);
await expect(page.getByText("Current Count")).toBeVisible();
});
test("clicking increment button increases counter", async ({ page }) => {
await authenticate(page);
await expect(page.locator("h1")).not.toHaveText("...");
const before = await page.locator("h1").textContent();
await page.click("text=Increment");
await expect(page.locator("h1")).toHaveText(String(Number(before) + 1));
});
test("clicking increment multiple times", async ({ page }) => {
await authenticate(page);
await expect(page.locator("h1")).not.toHaveText("...");
const before = Number(await page.locator("h1").textContent());
await page.click("text=Increment");
await page.click("text=Increment");
await page.click("text=Increment");
await expect(page.locator("h1")).toHaveText(String(before + 3));
});
test("counter persists after page reload", async ({ page }) => {
await authenticate(page);
await expect(page.locator("h1")).not.toHaveText("...");
const before = await page.locator("h1").textContent();
await page.click("text=Increment");
const expected = String(Number(before) + 1);
await expect(page.locator("h1")).toHaveText(expected);
await page.reload();
await expect(page.locator("h1")).toHaveText(expected);
});
test("counter is shared between users", async ({ page, browser }) => {
// First user increments
await authenticate(page);
await expect(page.locator("h1")).not.toHaveText("...");
const initialValue = Number(await page.locator("h1").textContent());
await page.click("text=Increment");
await page.click("text=Increment");
const afterFirst = initialValue + 2;
await expect(page.locator("h1")).toHaveText(String(afterFirst));
// Second user in new context sees the same value
const page2 = await browser.newPage();
await authenticate(page2);
await expect(page2.locator("h1")).toHaveText(String(afterFirst));
// Second user increments
await page2.click("text=Increment");
await expect(page2.locator("h1")).toHaveText(String(afterFirst + 1));
// First user reloads and sees the increment
await page.reload();
await expect(page.locator("h1")).toHaveText(String(afterFirst + 1));
await page2.close();
});
});
test("clicking +1 increments the counter", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1")).not.toHaveText("...");
test.describe("Counter - Unauthenticated", () => {
test("redirects to login when accessing counter without auth", async ({ page }) => {
await page.evaluate(() => localStorage.clear());
await page.goto("/");
await expect(page).toHaveURL("/login");
});
const before = await page.locator("h1").textContent();
await page.click("button");
await expect(page.locator("h1")).toHaveText(String(Number(before) + 1));
test("shows login form when redirected", async ({ page }) => {
await page.evaluate(() => localStorage.clear());
await page.goto("/");
await expect(page.locator("h1")).toHaveText("Welcome back");
});
});
test("counter persists after reload", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1")).not.toHaveText("...");
test.describe("Counter - Session Integration", () => {
test("can access counter after login", async ({ page }) => {
const email = uniqueEmail();
const before = await page.locator("h1").textContent();
await page.click("button");
const expected = String(Number(before) + 1);
await expect(page.locator("h1")).toHaveText(expected);
// Sign up first
await page.goto("/signup");
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.locator('input[type="password"]').nth(1).fill("password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
await page.reload();
await expect(page.locator("h1")).toHaveText(expected);
// Logout
await page.click("text=Sign out");
await expect(page).toHaveURL("/login");
// Login again
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', "password123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
// Counter should be visible
await expect(page.locator("h1")).toBeVisible();
const text = await page.locator("h1").textContent();
expect(text).toMatch(/^\d+$/);
});
test("counter API requires authentication", async ({ page }) => {
// Try to access counter API directly without auth
const response = await page.request.get("http://localhost:8000/api/counter");
expect(response.status()).toBe(403);
});
test("counter increment API requires authentication", async ({ page }) => {
const response = await page.request.post("http://localhost:8000/api/counter/increment");
expect(response.status()).toBe(403);
});
});