Write automated tests for authentication

This commit is contained in:
Moshfegh Hamedani 2026-02-13 10:18:44 -08:00
parent b1f8a4211e
commit 365babe9cd
5 changed files with 508 additions and 6 deletions

2
.gitignore vendored
View file

@ -1,3 +1,3 @@
node_modules node_modules
e2e/test-results/ e2e/test-results/
playwright-report/ e2e/playwright-report/

62
e2e/fixtures/auth.ts Normal file
View file

@ -0,0 +1,62 @@
import { Page, expect } from "@playwright/test";
/**
* Test credentials based on seeded data from server/prisma/seed.ts
*/
export const TEST_USERS = {
admin: {
email: "admin@example.com",
password: "password123",
name: "Admin",
role: "admin",
},
// Currently only admin is seeded, but we can add agent users here if seeded
} as const;
/**
* Logs in a user with the provided credentials
*/
export async function login(
page: Page,
credentials: { email: string; password: string }
) {
await page.goto("/login");
await page.getByLabel("Email").fill(credentials.email);
await page.getByLabel("Password").fill(credentials.password);
await page.getByRole("button", { name: /sign in/i }).click();
}
/**
* Logs in as the seeded admin user
*/
export async function loginAsAdmin(page: Page) {
await login(page, TEST_USERS.admin);
// Wait for redirect to home page after successful login
await expect(page).toHaveURL("/");
}
/**
* Logs out the current user
*/
export async function logout(page: Page) {
await page.getByRole("button", { name: /sign out/i }).click();
// Wait for redirect to login page
await expect(page).toHaveURL("/login");
}
/**
* Checks if the user is on the login page
*/
export async function expectLoginPage(page: Page) {
await expect(page).toHaveURL("/login");
await expect(page.getByText("Helpdesk")).toBeVisible();
await expect(page.getByText(/sign in to your account/i)).toBeVisible();
}
/**
* Checks if the user is on the home page (authenticated)
*/
export async function expectHomePage(page: Page) {
await expect(page).toHaveURL("/");
await expect(page.getByRole("heading", { name: /dashboard/i })).toBeVisible();
}

443
e2e/tests/auth.spec.ts Normal file
View file

@ -0,0 +1,443 @@
import { test, expect } from "@playwright/test";
import {
TEST_USERS,
login,
loginAsAdmin,
logout,
expectLoginPage,
expectHomePage,
} from "../fixtures/auth";
test.describe("Authentication", () => {
test.describe("Login Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/login");
});
test("should display login form with all elements", async ({ page }) => {
// Check page title and description
await expect(page.getByText("Helpdesk")).toBeVisible();
await expect(page.getByText(/sign in to your account/i)).toBeVisible();
// Check form fields
await expect(page.getByLabel("Email")).toBeVisible();
await expect(page.getByLabel("Password")).toBeVisible();
// Check submit button
await expect(
page.getByRole("button", { name: /sign in/i })
).toBeVisible();
});
test("should login successfully with valid admin credentials", async ({
page,
}) => {
await login(page, TEST_USERS.admin);
// Should redirect to home page
await expectHomePage(page);
// Should display user name in navigation
await expect(page.getByText(TEST_USERS.admin.name)).toBeVisible();
});
test("should show error with invalid email format", async ({ page }) => {
await page.getByLabel("Email").fill("invalid-email");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: /sign in/i }).click();
// Should show client-side validation error
await expect(
page.getByText(/please enter a valid email/i)
).toBeVisible();
// Should remain on login page
await expectLoginPage(page);
});
test("should show error with empty email", async ({ page }) => {
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: /sign in/i }).click();
// Should show client-side validation error
await expect(
page.getByText(/please enter a valid email/i)
).toBeVisible();
// Should remain on login page
await expectLoginPage(page);
});
test("should show error with empty password", async ({ page }) => {
await page.getByLabel("Email").fill(TEST_USERS.admin.email);
await page.getByRole("button", { name: /sign in/i }).click();
// Should show client-side validation error
await expect(page.getByText(/password is required/i)).toBeVisible();
// Should remain on login page
await expectLoginPage(page);
});
test("should show error with empty email and password", async ({
page,
}) => {
await page.getByRole("button", { name: /sign in/i }).click();
// Should show both validation errors
await expect(
page.getByText(/please enter a valid email/i)
).toBeVisible();
await expect(page.getByText(/password is required/i)).toBeVisible();
// Should remain on login page
await expectLoginPage(page);
});
test("should show error with invalid email (non-existent user)", async ({
page,
}) => {
await login(page, {
email: "nonexistent@example.com",
password: "password123",
});
// Should show server error message
await expect(page.getByRole("alert")).toBeVisible();
await expect(page.getByRole("alert")).toContainText(
/invalid email or password/i
);
// Should remain on login page
await expectLoginPage(page);
});
test("should show error with incorrect password", async ({ page }) => {
await login(page, {
email: TEST_USERS.admin.email,
password: "wrongpassword",
});
// Should show server error message
await expect(page.getByRole("alert")).toBeVisible();
await expect(page.getByRole("alert")).toContainText(
/invalid email or password/i
);
// Should remain on login page
await expectLoginPage(page);
});
test("should show loading state during login", async ({ page }) => {
await page.getByLabel("Email").fill(TEST_USERS.admin.email);
await page.getByLabel("Password").fill(TEST_USERS.admin.password);
// Start login
const loginPromise = page
.getByRole("button", { name: /sign in/i })
.click();
// Check loading state appears (button disabled with loading text)
await expect(page.getByText(/signing in.../i)).toBeVisible();
await loginPromise;
});
test("should redirect to home if already authenticated", async ({
page,
}) => {
// Login first
await loginAsAdmin(page);
// Try to visit login page again
await page.goto("/login");
// Should redirect to home page
await expectHomePage(page);
});
test("should clear server error on new submission", async ({ page }) => {
// First submission with wrong password
await login(page, {
email: TEST_USERS.admin.email,
password: "wrongpassword",
});
// Error should be visible
await expect(page.getByRole("alert")).toBeVisible();
// Try again with correct password
await page.getByLabel("Email").clear();
await page.getByLabel("Password").clear();
await login(page, TEST_USERS.admin);
// Should redirect to home (error cleared)
await expectHomePage(page);
});
});
test.describe("Session Persistence", () => {
test("should maintain session after page reload", async ({ page }) => {
// Login
await loginAsAdmin(page);
// Reload the page
await page.reload();
// Should still be authenticated and on home page
await expectHomePage(page);
await expect(page.getByText(TEST_USERS.admin.name)).toBeVisible();
});
test("should maintain session when navigating directly to protected route", async ({
page,
}) => {
// Login
await loginAsAdmin(page);
// Navigate to another protected route
await page.goto("/users");
// Should be able to access it
await expect(page).toHaveURL("/users");
await expect(
page.getByRole("heading", { name: /users/i })
).toBeVisible();
});
test("should maintain session across multiple page navigations", async ({
page,
}) => {
// Login
await loginAsAdmin(page);
// Navigate to different routes
await page.goto("/users");
await expect(page).toHaveURL("/users");
await page.goto("/");
await expectHomePage(page);
// Should still show user in nav
await expect(page.getByText(TEST_USERS.admin.name)).toBeVisible();
});
});
test.describe("Logout", () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page);
});
test("should logout successfully", async ({ page }) => {
await logout(page);
// Should redirect to login page
await expectLoginPage(page);
});
test("should not be able to access protected routes after logout", async ({
page,
}) => {
await logout(page);
// Try to access home page
await page.goto("/");
// Should redirect to login
await expectLoginPage(page);
});
test("should not be able to access admin routes after logout", async ({
page,
}) => {
await logout(page);
// Try to access admin page
await page.goto("/users");
// Should redirect to login
await expectLoginPage(page);
});
test("should require login again after logout", async ({ page }) => {
await logout(page);
// Try to access home page
await page.goto("/");
// Should be on login page
await expectLoginPage(page);
// Login again
await loginAsAdmin(page);
// Should be able to access home page
await expectHomePage(page);
});
});
test.describe("Protected Routes", () => {
test("should redirect unauthenticated user to login when accessing root", async ({
page,
}) => {
await page.goto("/");
await expectLoginPage(page);
});
test("should redirect unauthenticated user to login when accessing admin route", async ({
page,
}) => {
await page.goto("/users");
await expectLoginPage(page);
});
test("should redirect unauthenticated user from home to login", async ({
page,
}) => {
await page.goto("/");
// Should redirect to login
await expectLoginPage(page);
});
test("should allow access to protected route after login", async ({
page,
}) => {
// Try to access protected route first
await page.goto("/");
await expectLoginPage(page);
// Login
await loginAsAdmin(page);
// Should now be on home page
await expectHomePage(page);
});
});
test.describe("Admin Route Protection", () => {
test("should allow admin to access admin routes", async ({ page }) => {
await loginAsAdmin(page);
// Navigate to admin route
await page.goto("/users");
// Should be able to access it
await expect(page).toHaveURL("/users");
await expect(
page.getByRole("heading", { name: /users/i })
).toBeVisible();
});
test("should show 'Users' link in navigation for admin", async ({
page,
}) => {
await loginAsAdmin(page);
// Users link should be visible in navigation
await expect(page.getByRole("link", { name: /users/i })).toBeVisible();
});
test("should navigate to users page when clicking Users link", async ({
page,
}) => {
await loginAsAdmin(page);
// Click Users link
await page.getByRole("link", { name: /users/i }).click();
// Should navigate to users page
await expect(page).toHaveURL("/users");
await expect(
page.getByRole("heading", { name: /users/i })
).toBeVisible();
});
test("should maintain access to home route while on admin route", async ({
page,
}) => {
await loginAsAdmin(page);
// Go to admin route
await page.goto("/users");
await expect(page).toHaveURL("/users");
// Manually navigate back to home
await page.goto("/");
// Should be able to access home
await expectHomePage(page);
});
// Note: Currently only admin user is seeded. If agent users are added to seed.ts,
// uncomment and update these tests:
//
// test("should redirect agent to home when accessing admin route", async ({
// page,
// }) => {
// await login(page, TEST_USERS.agent);
// await page.goto("/users");
// await expectHomePage(page);
// });
//
// test("should not show 'Users' link in navigation for agent", async ({
// page,
// }) => {
// await login(page, TEST_USERS.agent);
// await expect(page.getByRole("link", { name: /users/i })).not.toBeVisible();
// });
});
test.describe("URL Handling", () => {
test("should redirect unknown routes to home for authenticated user", async ({
page,
}) => {
await loginAsAdmin(page);
// Visit unknown route
await page.goto("/unknown-route");
// Should redirect to home
await expectHomePage(page);
});
test("should redirect unknown routes to login for unauthenticated user", async ({
page,
}) => {
// Visit unknown route without being logged in
await page.goto("/unknown-route");
// Should redirect to login (via home redirect)
await expectLoginPage(page);
});
});
test.describe("Navigation Bar", () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page);
});
test("should display correct user information", async ({ page }) => {
// User name should be visible
await expect(page.getByText(TEST_USERS.admin.name)).toBeVisible();
// Sign out button should be visible
await expect(
page.getByRole("button", { name: /sign out/i })
).toBeVisible();
});
test("should display Helpdesk branding", async ({ page }) => {
await expect(page.getByText("Helpdesk").first()).toBeVisible();
});
test("should show navigation appropriate to user role", async ({
page,
}) => {
// Admin should see Users link
await expect(page.getByRole("link", { name: /users/i })).toBeVisible();
// When we have agent users, we would test they don't see it
});
});
});

View file

@ -7,7 +7,8 @@ export default defineConfig({
fullyParallel: true, fullyParallel: true,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
reporter: "html", outputDir: "./e2e/test-results",
reporter: [["html", { outputFolder: "./e2e/playwright-report" }]],
use: { use: {
baseURL: "http://localhost:5174", baseURL: "http://localhost:5174",
trace: "on-first-retry", trace: "on-first-retry",

View file

@ -1,4 +0,0 @@
{
"status": "failed",
"failedTests": []
}