diff --git a/ui/tests/auth/auth-middleware.spec.ts b/ui/tests/auth/auth-middleware.spec.ts index 16ef42997a..d8e89640c9 100644 --- a/ui/tests/auth/auth-middleware.spec.ts +++ b/ui/tests/auth/auth-middleware.spec.ts @@ -1,6 +1,6 @@ -import { test } from "@playwright/test"; +import { expect, test } from "@playwright/test"; -import { TEST_CREDENTIALS } from "../helpers"; +import { getSessionWithoutCookies, TEST_CREDENTIALS } from "../helpers"; import { ProvidersPage } from "../providers/providers-page"; import { ScansPage } from "../scans/scans-page"; import { SignInPage } from "../sign-in-base/sign-in-base-page"; @@ -40,24 +40,14 @@ test.describe("Middleware Error Handling", () => { await providersPage.goto(); await providersPage.verifyPageLoaded(); - const cookies = await context.cookies(); - const sessionCookie = cookies.find((c) => - c.name.includes("authjs.session-token"), - ); + // Remove auth cookies to simulate a broken/expired session deterministically. + await context.clearCookies(); - if (sessionCookie) { - await context.clearCookies(); - await context.addCookies([ - { - ...sessionCookie, - value: "invalid-session-token", - }, - ]); + const expiredSession = await getSessionWithoutCookies(page); + expect(expiredSession).toBeNull(); - await scansPage.goto(); - // With invalid session, should redirect to sign-in - await signInPage.verifyOnSignInPage(); - } + await scansPage.goto(); + await signInPage.verifyOnSignInPage(); }, ); diff --git a/ui/tests/auth/auth-session-errors.spec.ts b/ui/tests/auth/auth-session-errors.spec.ts index 55521f22b3..000ade831d 100644 --- a/ui/tests/auth/auth-session-errors.spec.ts +++ b/ui/tests/auth/auth-session-errors.spec.ts @@ -1,8 +1,5 @@ import { expect, test } from "@playwright/test"; -import { TEST_CREDENTIALS } from "../helpers"; -import { ProvidersPage } from "../providers/providers-page"; -import { ScansPage } from "../scans/scans-page"; import { SignInPage } from "../sign-in-base/sign-in-base-page"; test.describe("Session Error Messages", () => { @@ -65,28 +62,10 @@ test.describe("Session Error Messages", () => { { tag: ["@e2e", "@auth", "@session", "@AUTH-SESSION-E2E-004"] }, async ({ page, context }) => { const signInPage = new SignInPage(page); - const scansPage = new ScansPage(page); - const providersPage = new ProvidersPage(page); - - await signInPage.loginAndVerify(TEST_CREDENTIALS.VALID); - - // Navigate to a specific page (just need to be on a protected route) - await scansPage.goto(); - await expect(page.locator("main")).toBeVisible(); - - // Navigate to a safe public page before clearing cookies - // This prevents background requests from the protected page (scans) - // triggering a client-side redirect race condition when cookies are cleared - await signInPage.goto(); - - // Clear cookies to simulate session expiry await context.clearCookies(); - // Try to navigate to a different protected route - // Use fresh navigation to force middleware evaluation - await providersPage.gotoFresh(); - - // Should be redirected to login with callbackUrl + // Navigate directly to a protected route and assert callbackUrl preservation. + await page.goto("/providers", { waitUntil: "commit" }); await signInPage.verifyRedirectWithCallback("/providers"); }, ); diff --git a/ui/tests/invitations/invitations-page.ts b/ui/tests/invitations/invitations-page.ts index 72d6d4fdc9..6e1e61c550 100644 --- a/ui/tests/invitations/invitations-page.ts +++ b/ui/tests/invitations/invitations-page.ts @@ -1,9 +1,7 @@ import { Page, Locator, expect } from "@playwright/test"; import { BasePage } from "../base-page"; - export class InvitationsPage extends BasePage { - // Page heading readonly pageHeadingSendInvitation: Locator; readonly pageHeadingInvitations: Locator; @@ -18,34 +16,52 @@ export class InvitationsPage extends BasePage { readonly reviewInvitationDetailsButton: Locator; readonly shareUrl: Locator; - constructor(page: Page) { super(page); // Page heading - this.pageHeadingInvitations = page.getByRole("heading", { name: "Invitations" }); - this.pageHeadingSendInvitation = page.getByRole("heading", { name: "Send Invitation" }); + this.pageHeadingInvitations = page.getByRole("heading", { + name: "Invitations", + }); + this.pageHeadingSendInvitation = page.getByRole("heading", { + name: "Send Invitation", + }); // Button to invite a new user - this.inviteButton = page.getByRole("link", { name: "Send Invitation", exact: true }); - this.sendInviteButton = page.getByRole("button", { name: "Send Invitation", exact: true }); + this.inviteButton = page.getByRole("link", { + name: "Send Invitation", + exact: true, + }); + this.sendInviteButton = page.getByRole("button", { + name: "Send Invitation", + exact: true, + }); // Form inputs this.emailInput = page.getByRole("textbox", { name: "Email" }); // Form select - this.roleSelect = page.getByRole("combobox", { name: /Role|Select a role/i }); + this.roleSelect = page + .getByRole("combobox", { name: /Role|Select a role/i }) + .or(page.getByRole("button", { name: /Role|Select a role/i })) + .first(); // Form details - this.reviewInvitationDetailsButton = page.getByRole('button', { name: /Review Invitation Details/i }); + this.reviewInvitationDetailsButton = page.getByRole("button", { + name: /Review Invitation Details/i, + }); // Multiple strategies to find the share URL - this.shareUrl = page.locator('a[href*="/sign-up?invitation_token="], [data-testid="share-url"], .share-url, code, pre').first(); + this.shareUrl = page + .locator( + 'a[href*="/sign-up?invitation_token="], [data-testid="share-url"], .share-url, code, pre', + ) + .first(); } async goto(): Promise { // Navigate to the invitations page - + await super.goto("/invitations"); } @@ -83,18 +99,21 @@ export class InvitationsPage extends BasePage { // Select the role option // Open the role dropdown + await expect(this.roleSelect).toBeVisible({ timeout: 15000 }); await this.roleSelect.click(); // Prefer ARIA role option inside listbox - const option = this.page.getByRole("option", { name: new RegExp(`^${role}$`, "i") }); + const option = this.page.getByRole("option", { + name: new RegExp(`^${role}$`, "i"), + }); if (await option.count()) { await option.first().click(); } else { throw new Error(`Role option ${role} not found`); } - // Ensure the combobox now shows the chosen value - await expect(this.roleSelect).toContainText(new RegExp(role, "i")); + // Ensure a role value was selected in the trigger + await expect(this.roleSelect).not.toContainText(/Select a role/i); } async verifyInviteDataPageLoaded(): Promise { @@ -108,7 +127,7 @@ export class InvitationsPage extends BasePage { // Get the share url text content const text = await this.shareUrl.textContent(); - + if (!text) { throw new Error("Share url not found"); }