283 lines
9.9 KiB
TypeScript
283 lines
9.9 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
|