arbret/frontend/e2e/counter.spec.ts

213 lines
8.1 KiB
TypeScript
Raw Normal View History

2025-12-20 11:12:11 +01:00
import { test, expect, Page, APIRequestContext } from "@playwright/test";
const API_BASE = "http://localhost:8000";
const ADMIN_EMAIL = "admin@example.com";
const ADMIN_PASSWORD = "admin123";
2025-12-18 21:48:41 +01:00
2025-12-18 22:08:31 +01:00
// Helper to generate unique email for each test
function uniqueEmail(): string {
return `counter-${Date.now()}-${Math.random().toString(36).substring(7)}@example.com`;
}
2025-12-20 11:12:11 +01:00
// Helper to create an invite via API
async function createInvite(request: APIRequestContext): Promise<string> {
const loginResp = await request.post(`${API_BASE}/api/auth/login`, {
data: { email: ADMIN_EMAIL, password: ADMIN_PASSWORD },
});
const cookies = loginResp.headers()["set-cookie"];
const meResp = await request.get(`${API_BASE}/api/auth/me`, {
headers: { Cookie: cookies },
});
const admin = await meResp.json();
const inviteResp = await request.post(`${API_BASE}/api/admin/invites`, {
data: { godfather_id: admin.id },
headers: { Cookie: cookies },
});
const invite = await inviteResp.json();
return invite.identifier;
}
// Helper to authenticate a user with invite-based signup
async function authenticate(page: Page, request: APIRequestContext): Promise<string> {
2025-12-18 22:08:31 +01:00
const email = uniqueEmail();
2025-12-20 11:12:11 +01:00
const inviteCode = await createInvite(request);
2025-12-18 22:24:46 +01:00
await page.context().clearCookies();
2025-12-18 22:08:31 +01:00
await page.goto("/signup");
2025-12-20 11:12:11 +01:00
// Enter invite code first
await page.fill('input#inviteCode', inviteCode);
await page.click('button[type="submit"]');
// Wait for registration form
await expect(page.locator("h1")).toHaveText("Create account");
// Fill registration
await page.fill('input#email', email);
await page.fill('input#password', "password123");
await page.fill('input#confirmPassword', "password123");
2025-12-18 22:08:31 +01:00
await page.click('button[type="submit"]');
2025-12-20 11:12:11 +01:00
2025-12-18 22:08:31 +01:00
await expect(page).toHaveURL("/");
return email;
}
test.describe("Counter - Authenticated", () => {
2025-12-20 11:12:11 +01:00
test("displays counter value", async ({ page, request }) => {
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
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+$/);
});
2025-12-20 11:12:11 +01:00
test("displays current count label", async ({ page, request }) => {
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
await expect(page.getByText("Current Count")).toBeVisible();
});
2025-12-20 11:12:11 +01:00
test("clicking increment button increases counter", async ({ page, request }) => {
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
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));
});
2025-12-20 11:12:11 +01:00
test("clicking increment multiple times", async ({ page, request }) => {
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
await expect(page.locator("h1")).not.toHaveText("...");
const before = Number(await page.locator("h1").textContent());
2025-12-18 23:33:32 +01:00
// Click increment and wait for each update to complete
2025-12-18 22:08:31 +01:00
await page.click("text=Increment");
2025-12-18 23:33:32 +01:00
await expect(page.locator("h1")).not.toHaveText(String(before));
const afterFirst = Number(await page.locator("h1").textContent());
2025-12-18 22:08:31 +01:00
await page.click("text=Increment");
2025-12-18 23:33:32 +01:00
await expect(page.locator("h1")).not.toHaveText(String(afterFirst));
const afterSecond = Number(await page.locator("h1").textContent());
2025-12-18 22:08:31 +01:00
await page.click("text=Increment");
2025-12-18 23:33:32 +01:00
await expect(page.locator("h1")).not.toHaveText(String(afterSecond));
// Final value should be at least 3 more than we started with
const final = Number(await page.locator("h1").textContent());
expect(final).toBeGreaterThanOrEqual(before + 3);
2025-12-18 22:08:31 +01:00
});
2025-12-20 11:12:11 +01:00
test("counter persists after page reload", async ({ page, request }) => {
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
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);
});
2025-12-20 11:12:11 +01:00
test("counter is shared between users", async ({ page, browser, request }) => {
2025-12-18 22:08:31 +01:00
// First user increments
2025-12-20 11:12:11 +01:00
await authenticate(page, request);
2025-12-18 22:08:31 +01:00
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");
2025-12-18 23:33:32 +01:00
// Wait for the counter to update (value should increase by 2 from what this user started with)
await expect(page.locator("h1")).not.toHaveText(String(initialValue));
const afterFirstUser = Number(await page.locator("h1").textContent());
expect(afterFirstUser).toBeGreaterThan(initialValue);
2025-12-18 22:08:31 +01:00
2025-12-18 23:33:32 +01:00
// Second user in new context sees the current value
2025-12-18 22:08:31 +01:00
const page2 = await browser.newPage();
2025-12-20 11:12:11 +01:00
await authenticate(page2, request);
2025-12-18 23:33:32 +01:00
await expect(page2.locator("h1")).not.toHaveText("...");
const page2InitialValue = Number(await page2.locator("h1").textContent());
// The value should be at least what user 1 saw (might be higher due to parallel tests)
expect(page2InitialValue).toBeGreaterThanOrEqual(afterFirstUser);
2025-12-18 22:08:31 +01:00
// Second user increments
await page2.click("text=Increment");
2025-12-19 11:08:19 +01:00
// Wait for counter to update - use >= because parallel tests may also increment
await expect(page2.locator("h1")).not.toHaveText(String(page2InitialValue));
const page2AfterIncrement = Number(await page2.locator("h1").textContent());
expect(page2AfterIncrement).toBeGreaterThanOrEqual(page2InitialValue + 1);
2025-12-18 22:08:31 +01:00
2025-12-18 23:33:32 +01:00
// First user reloads and sees the increment (value should be >= what page2 has)
2025-12-18 22:08:31 +01:00
await page.reload();
2025-12-18 23:33:32 +01:00
await expect(page.locator("h1")).not.toHaveText("...");
const page1Reloaded = Number(await page.locator("h1").textContent());
expect(page1Reloaded).toBeGreaterThanOrEqual(page2InitialValue + 1);
2025-12-18 22:08:31 +01:00
await page2.close();
});
2025-12-18 21:48:41 +01:00
});
2025-12-18 22:08:31 +01:00
test.describe("Counter - Unauthenticated", () => {
test("redirects to login when accessing counter without auth", async ({ page }) => {
2025-12-18 22:24:46 +01:00
await page.context().clearCookies();
2025-12-18 22:08:31 +01:00
await page.goto("/");
await expect(page).toHaveURL("/login");
});
2025-12-18 21:48:41 +01:00
2025-12-18 22:08:31 +01:00
test("shows login form when redirected", async ({ page }) => {
2025-12-18 22:24:46 +01:00
await page.context().clearCookies();
2025-12-18 22:08:31 +01:00
await page.goto("/");
await expect(page.locator("h1")).toHaveText("Welcome back");
});
2025-12-18 21:48:41 +01:00
});
2025-12-18 22:08:31 +01:00
test.describe("Counter - Session Integration", () => {
2025-12-20 11:12:11 +01:00
test("can access counter after login", async ({ page, request }) => {
2025-12-18 22:08:31 +01:00
const email = uniqueEmail();
2025-12-20 11:12:11 +01:00
const inviteCode = await createInvite(request);
2025-12-18 21:48:41 +01:00
2025-12-20 11:12:11 +01:00
// Sign up with invite
2025-12-18 22:08:31 +01:00
await page.goto("/signup");
2025-12-20 11:12:11 +01:00
await page.fill('input#inviteCode', inviteCode);
await page.click('button[type="submit"]');
await expect(page.locator("h1")).toHaveText("Create account");
await page.fill('input#email', email);
await page.fill('input#password', "password123");
await page.fill('input#confirmPassword', "password123");
2025-12-18 22:08:31 +01:00
await page.click('button[type="submit"]');
await expect(page).toHaveURL("/");
2025-12-18 21:48:41 +01:00
2025-12-18 22:08:31 +01:00
// 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("/");
2025-12-18 23:33:32 +01:00
// Counter should be visible - wait for it to load (not showing "...")
2025-12-18 22:08:31 +01:00
await expect(page.locator("h1")).toBeVisible();
2025-12-18 23:33:32 +01:00
await expect(page.locator("h1")).not.toHaveText("...");
2025-12-18 22:08:31 +01:00
const text = await page.locator("h1").textContent();
expect(text).toMatch(/^\d+$/);
});
2025-12-18 21:48:41 +01:00
2025-12-18 22:08:31 +01:00
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");
2025-12-18 22:24:46 +01:00
expect(response.status()).toBe(401);
2025-12-18 22:08:31 +01:00
});
test("counter increment API requires authentication", async ({ page }) => {
const response = await page.request.post("http://localhost:8000/api/counter/increment");
2025-12-18 22:24:46 +01:00
expect(response.status()).toBe(401);
2025-12-18 22:08:31 +01:00
});
});