mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-06 08:47:18 +00:00
test(ui): add E2E tests for invitation accept smart router (#10814)
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
This commit is contained in:
committed by
GitHub
parent
be3c5fb3c1
commit
13b04d339b
@@ -113,6 +113,13 @@ export default defineConfig({
|
||||
name: "sign-up",
|
||||
testMatch: "sign-up.spec.ts",
|
||||
},
|
||||
// This project runs the invitation accept smart router test suite
|
||||
// Tests run unauthenticated (no auth setup dependency)
|
||||
{
|
||||
name: "invitation-accept",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
testMatch: /invitation-accept\/.*\.spec\.ts/,
|
||||
},
|
||||
// This project runs the scans test suite
|
||||
{
|
||||
name: "scans",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect, Locator, Page } from "@playwright/test";
|
||||
|
||||
import { BasePage } from "../base-page";
|
||||
|
||||
export class InvitationAcceptPage extends BasePage {
|
||||
// Choice screen (unauthenticated user with valid token)
|
||||
readonly choiceHeading: Locator;
|
||||
readonly choiceDescription: Locator;
|
||||
readonly signInButton: Locator;
|
||||
readonly createAccountButton: Locator;
|
||||
|
||||
// No-token error screen
|
||||
readonly noTokenHeading: Locator;
|
||||
readonly noTokenDescription: Locator;
|
||||
readonly goToSignInLink: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
||||
this.choiceHeading = page.getByRole("heading", {
|
||||
name: "You've Been Invited",
|
||||
});
|
||||
this.choiceDescription = page.getByText(/invited to join a tenant/i);
|
||||
this.signInButton = page.getByRole("button", {
|
||||
name: /I have an account.*Sign in/i,
|
||||
});
|
||||
this.createAccountButton = page.getByRole("button", {
|
||||
name: /I'm new.*Create an account/i,
|
||||
});
|
||||
|
||||
this.noTokenHeading = page.getByRole("heading", {
|
||||
name: "Invalid Invitation Link",
|
||||
});
|
||||
this.noTokenDescription = page.getByText(
|
||||
/No invitation token was provided/i,
|
||||
);
|
||||
this.goToSignInLink = page.getByRole("link", { name: "Go to Sign In" });
|
||||
}
|
||||
|
||||
async gotoWithToken(token: string): Promise<void> {
|
||||
await super.goto(
|
||||
`/invitation/accept?invitation_token=${encodeURIComponent(token)}`,
|
||||
);
|
||||
}
|
||||
|
||||
async gotoWithoutToken(): Promise<void> {
|
||||
await super.goto("/invitation/accept");
|
||||
}
|
||||
|
||||
async verifyChoiceScreen(): Promise<void> {
|
||||
await expect(this.choiceHeading).toBeVisible();
|
||||
await expect(this.choiceDescription).toBeVisible();
|
||||
await expect(this.signInButton).toBeVisible();
|
||||
await expect(this.createAccountButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyNoTokenScreen(): Promise<void> {
|
||||
await expect(this.noTokenHeading).toBeVisible();
|
||||
await expect(this.noTokenDescription).toBeVisible();
|
||||
await expect(this.goToSignInLink).toBeVisible();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
### E2E Tests: Invitation Accept Smart Router
|
||||
|
||||
**Suite ID:** `INVITE-ACCEPT-E2E`
|
||||
**Feature:** `/invitation/accept` smart router that handles invitation links for both
|
||||
authenticated and unauthenticated users.
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `INVITE-ACCEPT-E2E-001` - Unauthenticated user sees choice screen and Sign in preserves token
|
||||
|
||||
**Priority:** `high`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type: @e2e
|
||||
- feature: @invitation, @invitation-accept
|
||||
|
||||
**Description/Objective:** Verify the smart router renders the choice screen for
|
||||
an unauthenticated user with a valid token, and that clicking "Sign in"
|
||||
redirects to `/sign-in` with a `callbackUrl` that preserves the invitation token.
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Application is running.
|
||||
- No active session (cookies cleared).
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Clear all cookies.
|
||||
2. Navigate to `/invitation/accept?invitation_token=test-token`.
|
||||
3. Verify the choice screen is rendered.
|
||||
4. Click the "I have an account — Sign in" button.
|
||||
5. Verify the redirect target and `callbackUrl` query param.
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- Heading "You've Been Invited" is visible.
|
||||
- Description text "invited to join a tenant" is visible.
|
||||
- Both "I have an account — Sign in" and "I'm new — Create an account" buttons are visible.
|
||||
- After clicking "Sign in", URL is `/sign-in?callbackUrl=...`.
|
||||
- Decoded `callbackUrl` equals `/invitation/accept?invitation_token=test-token`.
|
||||
- Decoded `callbackUrl` contains `invitation_token=test-token`.
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- `callbackUrl` preserves the original invitation path with token.
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `INVITE-ACCEPT-E2E-002` - "Create an account" button redirects to sign-up with invitation token
|
||||
|
||||
**Priority:** `high`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type: @e2e
|
||||
- feature: @invitation, @invitation-accept
|
||||
|
||||
**Description/Objective:** Verify the "Create an account" button redirects to
|
||||
`/sign-up` preserving the `invitation_token` query param, and that the sign-up
|
||||
form actually renders (no redirect loop back to `/invitation/accept`).
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Application is running.
|
||||
- No active session (cookies cleared).
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Clear all cookies.
|
||||
2. Navigate to `/invitation/accept?invitation_token=test-token`.
|
||||
3. Wait for the "I'm new — Create an account" button to be visible.
|
||||
4. Click the button.
|
||||
5. Verify the resulting URL and that the sign-up form is rendered.
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- URL pathname is `/sign-up`.
|
||||
- Query param `invitation_token` equals `test-token`.
|
||||
- Sign-up form is rendered (email input and submit button visible).
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- No redirect back to `/invitation/accept` (smart router does not loop).
|
||||
|
||||
### Notes:
|
||||
|
||||
- The legacy `action=signup` param is no longer emitted: the backward-compat
|
||||
redirect from `/sign-up?invitation_token=...` to `/invitation/accept` was
|
||||
removed, so no action bypass is needed. See also `AUTH-MW-E2E-003` in
|
||||
`ui/tests/auth/auth-middleware.spec.ts`, which covers that `/sign-up` with a
|
||||
token is no longer rewritten.
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `INVITE-ACCEPT-E2E-004` - No token shows error screen
|
||||
|
||||
**Priority:** `medium`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type: @e2e
|
||||
- feature: @invitation, @invitation-accept
|
||||
|
||||
**Description/Objective:** Verify that navigating to `/invitation/accept`
|
||||
without an `invitation_token` query param shows the no-token error state and
|
||||
that the "Go to Sign In" link redirects to `/sign-in`.
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Application is running.
|
||||
- No active session (cookies cleared).
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Clear all cookies.
|
||||
2. Navigate to `/invitation/accept` (no query params).
|
||||
3. Verify the no-token error screen is rendered.
|
||||
4. Click the "Go to Sign In" link.
|
||||
5. Verify redirect to `/sign-in`.
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- Heading "Invalid Invitation Link" is visible.
|
||||
- Description "No invitation token was provided" is visible.
|
||||
- "Go to Sign In" link is visible and clickable.
|
||||
- After click, URL matches `/sign-in`.
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- Client-side render only: no API calls involved.
|
||||
@@ -0,0 +1,95 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { SignInPage } from "../sign-in-base/sign-in-base-page";
|
||||
import { SignUpPage } from "../sign-up/sign-up-page";
|
||||
import { InvitationAcceptPage } from "./invitation-accept-page";
|
||||
|
||||
test.describe("Invitation Accept Smart Router", () => {
|
||||
// Match the auth suites' timeout to handle slow dev server starts
|
||||
test.setTimeout(60000);
|
||||
|
||||
test(
|
||||
"unauthenticated user sees choice screen and Sign in preserves token in callbackUrl",
|
||||
{
|
||||
tag: [
|
||||
"@e2e",
|
||||
"@invitation",
|
||||
"@invitation-accept",
|
||||
"@INVITE-ACCEPT-E2E-001",
|
||||
],
|
||||
},
|
||||
async ({ page, context }) => {
|
||||
const invitationPage = new InvitationAcceptPage(page);
|
||||
const signInPage = new SignInPage(page);
|
||||
|
||||
await context.clearCookies();
|
||||
|
||||
const token = "test-token";
|
||||
await invitationPage.gotoWithToken(token);
|
||||
await invitationPage.verifyChoiceScreen();
|
||||
|
||||
await invitationPage.signInButton.click();
|
||||
await signInPage.verifyRedirectWithCallback(
|
||||
`/invitation/accept?invitation_token=${token}`,
|
||||
);
|
||||
|
||||
const callbackUrl = new URL(page.url()).searchParams.get("callbackUrl");
|
||||
expect(callbackUrl).toContain(`invitation_token=${token}`);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'"Create an account" button redirects to sign-up with the invitation token',
|
||||
{
|
||||
tag: [
|
||||
"@e2e",
|
||||
"@invitation",
|
||||
"@invitation-accept",
|
||||
"@INVITE-ACCEPT-E2E-002",
|
||||
],
|
||||
},
|
||||
async ({ page, context }) => {
|
||||
const invitationPage = new InvitationAcceptPage(page);
|
||||
const signUpPage = new SignUpPage(page);
|
||||
|
||||
await context.clearCookies();
|
||||
|
||||
const token = "test-token";
|
||||
await invitationPage.gotoWithToken(token);
|
||||
|
||||
await expect(invitationPage.createAccountButton).toBeVisible();
|
||||
await invitationPage.createAccountButton.click();
|
||||
|
||||
await page.waitForURL(/\/sign-up\?/);
|
||||
const url = new URL(page.url());
|
||||
expect(url.pathname).toBe("/sign-up");
|
||||
expect(url.searchParams.get("invitation_token")).toBe(token);
|
||||
|
||||
await signUpPage.verifyPageLoaded();
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"navigating to /invitation/accept without a token shows the no-token error screen",
|
||||
{
|
||||
tag: [
|
||||
"@e2e",
|
||||
"@invitation",
|
||||
"@invitation-accept",
|
||||
"@INVITE-ACCEPT-E2E-004",
|
||||
],
|
||||
},
|
||||
async ({ page, context }) => {
|
||||
const invitationPage = new InvitationAcceptPage(page);
|
||||
const signInPage = new SignInPage(page);
|
||||
|
||||
await context.clearCookies();
|
||||
|
||||
await invitationPage.gotoWithoutToken();
|
||||
await invitationPage.verifyNoTokenScreen();
|
||||
|
||||
await invitationPage.goToSignInLink.click();
|
||||
await signInPage.verifyOnSignInPage();
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user