Files
prowler/ui/tests/auth-login.spec.ts
2025-10-10 11:02:10 +02:00

257 lines
8.5 KiB
TypeScript

import { test, expect } from "@playwright/test";
import {
goToLogin,
goToSignUp,
fillLoginForm,
submitLoginForm,
login,
verifySuccessfulLogin,
verifyLoginError,
verifyLoginFormElements,
verifyDashboardRoute,
toggleSamlMode,
verifySamlModeActive,
goBackFromSaml,
verifyNormalModeActive,
logout,
verifyLogoutSuccess,
waitForPageLoad,
TEST_CREDENTIALS,
ERROR_MESSAGES,
URLS,
verifyLoadingState,
} from "./helpers";
test.describe("Login Flow", () => {
test.beforeEach(async ({ page }) => {
await goToLogin(page);
});
test("should display login form elements", async ({ page }) => {
await verifyLoginFormElements(page);
});
test("should successfully login with valid credentials", async ({ page }) => {
await login(page, TEST_CREDENTIALS.VALID);
await verifySuccessfulLogin(page);
await verifyDashboardRoute(page);
});
test("should show error message with invalid credentials", async ({
page,
}) => {
// Attempt login with invalid credentials
await login(page, TEST_CREDENTIALS.INVALID);
await verifyLoginError(page, ERROR_MESSAGES.INVALID_CREDENTIALS);
});
test("should handle empty form submission", async ({ page }) => {
// Submit empty form
await submitLoginForm(page);
// Should show both email and password validation errors
await verifyLoginError(page, ERROR_MESSAGES.INVALID_EMAIL);
await verifyLoginError(page, ERROR_MESSAGES.PASSWORD_REQUIRED);
// Verify we're still on login page
await expect(page).toHaveURL(URLS.LOGIN);
});
test("should validate email format", async ({ page }) => {
// Attempt login with invalid email format
await login(page, TEST_CREDENTIALS.INVALID_EMAIL_FORMAT);
// Verify field-level email validation message
await verifyLoginError(page, ERROR_MESSAGES.INVALID_EMAIL);
// Verify we're still on login page
await expect(page).toHaveURL(URLS.LOGIN);
});
test("should require password when email is filled", async ({ page }) => {
// Fill only email, leave password empty
await page.getByLabel("Email").fill(TEST_CREDENTIALS.VALID.email);
await submitLoginForm(page);
// Should show password required error
await verifyLoginError(page, ERROR_MESSAGES.PASSWORD_REQUIRED);
// Verify we're still on login page
await expect(page).toHaveURL(URLS.LOGIN);
});
test("should toggle SAML SSO mode", async ({ page }) => {
// Toggle to SAML mode
await toggleSamlMode(page);
await verifySamlModeActive(page);
// Toggle back to normal mode
await goBackFromSaml(page);
await verifyNormalModeActive(page);
});
test("should show loading state during form submission", async ({ page }) => {
// Fill valid credentials
await fillLoginForm(
page,
TEST_CREDENTIALS.VALID.email,
TEST_CREDENTIALS.VALID.password,
);
// Submit form and verify loading state
await submitLoginForm(page);
// Verify loading state
await verifyLoadingState(page);
});
test("should handle SAML authentication flow", async ({ page }) => {
// Enter email for SAML
const samlEmail = "user@saml-domain.com";
// Toggle to SAML mode
await toggleSamlMode(page);
// Fill email (password should be hidden)
await page.getByLabel("Email").fill(samlEmail);
// Submit should trigger SAML redirect (we can't test the actual SAML flow in E2E)
// but we can verify the form submission
await submitLoginForm(page);
// Note: In a real scenario, this would redirect to IdP
// For testing, we just verify the form was submitted
});
});
test.describe("Session Persistence", () => {
test("should maintain session after browser refresh", async ({ page }) => {
// Login first
await goToLogin(page);
await login(page, TEST_CREDENTIALS.VALID);
await verifySuccessfulLogin(page);
// Refresh the page
await page.reload();
await waitForPageLoad(page);
// Verify session is maintained
await expect(page).toHaveURL(URLS.DASHBOARD);
await verifyDashboardRoute(page);
// Verify user is not redirected back to login
await expect(page).not.toHaveURL(URLS.LOGIN);
});
test("should redirect to login when accessing protected route without session", async ({
page,
}) => {
// Try to access protected route without login
await page.goto(URLS.DASHBOARD);
// Should be redirected to login page (may include callbackUrl)
await expect(page).toHaveURL(/\/sign-in/);
await expect(page.getByText("Sign in", { exact: true })).toBeVisible();
});
test("should logout successfully", async ({ page }) => {
// Login first
await goToLogin(page);
await login(page, TEST_CREDENTIALS.VALID);
await verifySuccessfulLogin(page);
// Logout
await logout(page);
await verifyLogoutSuccess(page);
// Verify cannot access protected route after logout
await page.goto(URLS.DASHBOARD);
await expect(page).toHaveURL(/\/sign-in/);
});
test("should handle session timeout gracefully", async ({ browser }) => {
// Test approach: Verify that a new browser context without auth cookies
// gets redirected to login when accessing protected routes
// First, login in one context to verify auth works
const authContext = await browser.newContext();
const authPage = await authContext.newPage();
await goToLogin(authPage);
await login(authPage, TEST_CREDENTIALS.VALID);
await verifySuccessfulLogin(authPage);
// Verify session exists in authenticated context
const authResponse = await authPage.request.get("/api/auth/session");
const authSession = await authResponse.json();
expect(authSession).toBeTruthy();
expect(authSession.user).toBeTruthy();
// Now create a completely separate context without any auth
const unauthContext = await browser.newContext();
const unauthPage = await unauthContext.newPage();
// Try to access protected route in unauthenticated context
await unauthPage.goto(URLS.PROFILE, {
waitUntil: "networkidle",
});
// Should be redirected to login since this context has no auth (may include callbackUrl)
await expect(unauthPage).toHaveURL(/\/sign-in/);
// Verify session is null in unauthenticated context
const unauthResponse = await unauthPage.request.get("/api/auth/session");
const unauthSessionText = await unauthResponse.text();
expect(unauthSessionText).toBe("null");
// Clean up
await authPage.close();
await authContext.close();
await unauthPage.close();
await unauthContext.close();
});
});
test.describe("Navigation", () => {
test("should navigate to sign up page", async ({ page }) => {
await goToLogin(page);
await page.getByRole("link", { name: "Sign up" }).click();
await expect(page).toHaveURL(URLS.SIGNUP);
});
test("should navigate from sign up back to sign in", async ({ page }) => {
await goToSignUp(page);
await page.getByRole("link", { name: "Log in" }).click();
await expect(page).toHaveURL(URLS.LOGIN);
await expect(page.getByText("Sign in", { exact: true })).toBeVisible();
});
test("should handle browser back button correctly", async ({ page }) => {
await goToLogin(page);
await page.getByRole("link", { name: "Sign up" }).click();
await expect(page).toHaveURL(URLS.SIGNUP);
await page.goBack();
await expect(page).toHaveURL(URLS.LOGIN);
await expect(page.getByText("Sign in", { exact: true })).toBeVisible();
});
});
test.describe("Accessibility", () => {
test.beforeEach(async ({ page }) => {
await goToLogin(page);
});
test("should be navigable with keyboard", async ({ page }) => {
// Tab through form elements
await page.keyboard.press("Tab"); // Toggle theme
await page.keyboard.press("Tab"); // Email field
await expect(page.getByLabel("Email")).toBeFocused();
await page.keyboard.press("Tab"); // Password field
await expect(page.getByLabel("Password")).toBeFocused();
await page.keyboard.press("Tab"); // Show password button
await page.keyboard.press("Tab"); // Login button
if (process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true") {
await page.keyboard.press("Tab"); // Forgot password
}
await expect(page.getByRole("button", { name: "Log in" })).toBeFocused();
});
test("should have proper ARIA labels", async ({ page }) => {
await expect(page.getByRole("textbox", { name: "Email" })).toBeVisible();
await expect(page.getByRole("textbox", { name: "Password" })).toBeVisible();
await expect(page.getByRole("button", { name: "Log in" })).toBeVisible();
});
});