mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
257 lines
8.5 KiB
TypeScript
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();
|
|
});
|
|
});
|