mirror of
https://github.com/mosh-hamedani/helpdesk.git
synced 2026-05-21 11:58:19 +02:00
Write automated tests for authentication
This commit is contained in:
parent
b1f8a4211e
commit
365babe9cd
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,3 @@
|
|||
node_modules
|
||||
e2e/test-results/
|
||||
playwright-report/
|
||||
e2e/playwright-report/
|
||||
|
|
|
|||
62
e2e/fixtures/auth.ts
Normal file
62
e2e/fixtures/auth.ts
Normal 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
443
e2e/tests/auth.spec.ts
Normal 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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -7,7 +7,8 @@ export default defineConfig({
|
|||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: "html",
|
||||
outputDir: "./e2e/test-results",
|
||||
reporter: [["html", { outputFolder: "./e2e/playwright-report" }]],
|
||||
use: {
|
||||
baseURL: "http://localhost:5174",
|
||||
trace: "on-first-retry",
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"status": "failed",
|
||||
"failedTests": []
|
||||
}
|
||||
Loading…
Reference in a new issue