From 18998b88671542b4ea12010029fe47e17232db9b Mon Sep 17 00:00:00 2001 From: StylusFrost <43682773+StylusFrost@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:25:34 +0100 Subject: [PATCH] test(ui): E2E Test - New user sign-up/registration (#8895) Co-authored-by: Pepe Fagoaga --- .github/workflows/ui-e2e-tests.yml | 1 + ui/package.json | 8 +- ui/playwright.config.ts | 6 + ui/tests/base-page.ts | 159 ++++++++++++++++++ ui/tests/helpers.ts | 14 +- ui/tests/{page-objects => home}/home-page.ts | 32 +--- ui/tests/{ => sign-in}/auth-login.spec.ts | 2 +- .../{page-objects => sign-in}/sign-in-page.ts | 79 +++------ ui/tests/sign-up/sign-up-page.ts | 117 +++++++++++++ ui/tests/sign-up/sign-up.md | 43 +++++ ui/tests/sign-up/sign-up.spec.ts | 49 ++++++ 11 files changed, 420 insertions(+), 90 deletions(-) create mode 100644 ui/tests/base-page.ts rename ui/tests/{page-objects => home}/home-page.ts (78%) rename ui/tests/{ => sign-in}/auth-login.spec.ts (99%) rename ui/tests/{page-objects => sign-in}/sign-in-page.ts (76%) create mode 100644 ui/tests/sign-up/sign-up-page.ts create mode 100644 ui/tests/sign-up/sign-up.md create mode 100644 ui/tests/sign-up/sign-up.spec.ts diff --git a/.github/workflows/ui-e2e-tests.yml b/.github/workflows/ui-e2e-tests.yml index dea6f1f3e2..81f191765c 100644 --- a/.github/workflows/ui-e2e-tests.yml +++ b/.github/workflows/ui-e2e-tests.yml @@ -18,6 +18,7 @@ jobs: AUTH_TRUST_HOST: true NEXTAUTH_URL: 'http://localhost:3000' NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1' + E2E_NEW_PASSWORD: ${{ secrets.E2E_NEW_PASSWORD }} steps: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/ui/package.json b/ui/package.json index ee103a6f45..e043f89ac2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,10 +15,10 @@ "format:check": "./node_modules/.bin/prettier --check ./app", "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app", "prepare": "husky", - "test:e2e": "playwright test --project=chromium", - "test:e2e:ui": "playwright test --project=chromium --ui", - "test:e2e:debug": "playwright test --project=chromium --debug", - "test:e2e:headed": "playwright test --project=chromium --headed", + "test:e2e": "playwright test --project=chromium --project=sign-up", + "test:e2e:ui": "playwright test --project=chromium --project=sign-up --ui", + "test:e2e:debug": "playwright test --project=chromium --project=sign-up --debug", + "test:e2e:headed": "playwright test --project=chromium --project=sign-up --headed", "test:e2e:report": "playwright show-report", "test:e2e:install": "playwright install" }, diff --git a/ui/playwright.config.ts b/ui/playwright.config.ts index 5bea00bfe7..a57952ab98 100644 --- a/ui/playwright.config.ts +++ b/ui/playwright.config.ts @@ -89,6 +89,12 @@ export default defineConfig({ { name: "chromium", use: { ...devices["Desktop Chrome"] }, + testMatch: "auth-login.spec.ts", + }, + // This project runs the sign-up test suite + { + name: "sign-up", + testMatch: "sign-up.spec.ts", }, ], diff --git a/ui/tests/base-page.ts b/ui/tests/base-page.ts new file mode 100644 index 0000000000..c9b73f2b1a --- /dev/null +++ b/ui/tests/base-page.ts @@ -0,0 +1,159 @@ +import { Page, Locator, expect } from "@playwright/test"; + +/** + * Base page object class containing common functionality + * that can be shared across all page objects + */ +export abstract class BasePage { + readonly page: Page; + + // Common UI elements that appear on most pages + readonly title: Locator; + readonly loadingIndicator: Locator; + readonly themeToggle: Locator; + + constructor(page: Page) { + this.page = page; + + // Common locators that most pages share + this.title = page.locator("h1, h2, [role='heading']").first(); + this.loadingIndicator = page.getByRole("status", { name: "Loading" }); + this.themeToggle = page.getByRole("button", { name: "Toggle theme" }); + } + + // Common navigation methods + async goto(url: string): Promise { + await this.page.goto(url); + await this.waitForPageLoad(); + } + + async waitForPageLoad(): Promise { + await this.page.waitForLoadState("networkidle"); + } + + async refresh(): Promise { + await this.page.reload(); + await this.waitForPageLoad(); + } + + async goBack(): Promise { + await this.page.goBack(); + await this.waitForPageLoad(); + } + + // Common verification methods + async verifyPageTitle(expectedTitle: string | RegExp): Promise { + await expect(this.page).toHaveTitle(expectedTitle); + } + + async verifyLoadingState(): Promise { + await expect(this.loadingIndicator).toBeVisible(); + } + + async verifyNoLoadingState(): Promise { + await expect(this.loadingIndicator).not.toBeVisible(); + } + + // Common form interaction methods + async clearInput(input: Locator): Promise { + await input.clear(); + } + + async fillInput(input: Locator, value: string): Promise { + await input.fill(value); + } + + async clickButton(button: Locator): Promise { + await button.click(); + } + + // Common validation methods + async verifyElementVisible(element: Locator): Promise { + await expect(element).toBeVisible(); + } + + async verifyElementNotVisible(element: Locator): Promise { + await expect(element).not.toBeVisible(); + } + + async verifyElementText(element: Locator, expectedText: string): Promise { + await expect(element).toHaveText(expectedText); + } + + async verifyElementContainsText(element: Locator, expectedText: string): Promise { + await expect(element).toContainText(expectedText); + } + + // Common accessibility methods + async verifyKeyboardNavigation(elements: Locator[]): Promise { + for (const element of elements) { + await this.page.keyboard.press("Tab"); + await expect(element).toBeFocused(); + } + } + + async verifyAriaLabels(elements: { locator: Locator; expectedLabel: string }[]): Promise { + for (const { locator, expectedLabel } of elements) { + await expect(locator).toHaveAttribute("aria-label", expectedLabel); + } + } + + // Common utility methods + async getElementText(element: Locator): Promise { + return await element.textContent() || ""; + } + + async getElementValue(element: Locator): Promise { + return await element.inputValue(); + } + + async isElementVisible(element: Locator): Promise { + return await element.isVisible(); + } + + async isElementEnabled(element: Locator): Promise { + return await element.isEnabled(); + } + + // Common error handling methods + async getFormErrors(): Promise { + const errorElements = await this.page.locator('[role="alert"], .error-message, [data-testid="error"]').all(); + const errors: string[] = []; + + for (const element of errorElements) { + const text = await element.textContent(); + if (text) { + errors.push(text.trim()); + } + } + + return errors; + } + + async verifyNoErrors(): Promise { + const errors = await this.getFormErrors(); + expect(errors).toHaveLength(0); + } + + // Common wait methods + async waitForElement(element: Locator, timeout: number = 5000): Promise { + await element.waitFor({ timeout }); + } + + async waitForElementToDisappear(element: Locator, timeout: number = 5000): Promise { + await element.waitFor({ state: "hidden", timeout }); + } + + async waitForUrl(expectedUrl: string | RegExp, timeout: number = 5000): Promise { + await this.page.waitForURL(expectedUrl, { timeout }); + } + + // Common screenshot methods + async takeScreenshot(name: string): Promise { + await this.page.screenshot({ path: `screenshots/${name}.png` }); + } + + async takeElementScreenshot(element: Locator, name: string): Promise { + await element.screenshot({ path: `screenshots/${name}.png` }); + } +} diff --git a/ui/tests/helpers.ts b/ui/tests/helpers.ts index db9c6facbb..1e93bd396d 100644 --- a/ui/tests/helpers.ts +++ b/ui/tests/helpers.ts @@ -1,5 +1,5 @@ import { Page, expect } from "@playwright/test"; -import { SignInPage, SignInCredentials } from "./page-objects/sign-in-page"; +import { SignInPage, SignInCredentials } from "./sign-in/sign-in-page"; export const ERROR_MESSAGES = { INVALID_CREDENTIALS: "Invalid email or password", @@ -162,6 +162,18 @@ export async function authenticateAndSaveState( await page.context().storageState({ path: storagePath }); } +/** + * Generate a random base36 suffix of specified length + * Used for creating unique test data to avoid conflicts + */ +export function makeSuffix(len: number): string { + let s = ""; + while (s.length < len) { + s += Math.random().toString(36).slice(2); + } + return s.slice(0, len); +} + export async function getSession(page: Page) { const response = await page.request.get("/api/auth/session"); return response.json(); diff --git a/ui/tests/page-objects/home-page.ts b/ui/tests/home/home-page.ts similarity index 78% rename from ui/tests/page-objects/home-page.ts rename to ui/tests/home/home-page.ts index 077591eb4e..e696e02ab5 100644 --- a/ui/tests/page-objects/home-page.ts +++ b/ui/tests/home/home-page.ts @@ -1,7 +1,7 @@ import { Page, Locator, expect } from "@playwright/test"; +import { BasePage } from "../base-page"; -export class HomePage { - readonly page: Page; +export class HomePage extends BasePage { // Main content elements readonly mainContent: Locator; @@ -18,15 +18,14 @@ export class HomePage { readonly overviewSection: Locator; // UI elements - readonly themeToggle: Locator; readonly logo: Locator; constructor(page: Page) { - this.page = page; + super(page); // Main content elements this.mainContent = page.locator("main"); - this.breadcrumbs = page.getByLabel("Breadcrumbs"); + this.breadcrumbs = page.getByRole("navigation", { name: "Breadcrumbs" }); this.overviewHeading = page.getByRole("heading", { name: "Overview", exact: true }); // Navigation elements @@ -39,18 +38,12 @@ export class HomePage { this.overviewSection = page.locator('[data-testid="overview-section"]'); // UI elements - this.themeToggle = page.getByLabel("Toggle theme"); this.logo = page.locator('svg[width="300"]'); } // Navigation methods async goto(): Promise { - await this.page.goto("/"); - await this.waitForPageLoad(); - } - - async waitForPageLoad(): Promise { - await this.page.waitForLoadState("networkidle"); + await super.goto("/"); } // Verification methods @@ -58,7 +51,6 @@ export class HomePage { await expect(this.page).toHaveURL("/"); await expect(this.mainContent).toBeVisible(); await expect(this.overviewHeading).toBeVisible(); - await this.waitForPageLoad(); } async verifyBreadcrumbs(): Promise { @@ -94,15 +86,6 @@ export class HomePage { } // Utility methods - async refresh(): Promise { - await this.page.reload(); - await this.waitForPageLoad(); - } - - async goBack(): Promise { - await this.page.goBack(); - await this.waitForPageLoad(); - } // Accessibility methods async verifyKeyboardNavigation(): Promise { @@ -111,11 +94,6 @@ export class HomePage { await expect(this.themeToggle).toBeFocused(); } - // Wait methods - async waitForRedirect(expectedUrl: string): Promise { - await this.page.waitForURL(expectedUrl); - } - async waitForContentLoad(): Promise { await this.page.waitForFunction(() => { const main = document.querySelector("main"); diff --git a/ui/tests/auth-login.spec.ts b/ui/tests/sign-in/auth-login.spec.ts similarity index 99% rename from ui/tests/auth-login.spec.ts rename to ui/tests/sign-in/auth-login.spec.ts index fbe3fedebb..3f81d5b2b1 100644 --- a/ui/tests/auth-login.spec.ts +++ b/ui/tests/sign-in/auth-login.spec.ts @@ -20,7 +20,7 @@ import { ERROR_MESSAGES, URLS, verifyLoadingState, -} from "./helpers"; +} from "../helpers"; test.describe("Login Flow", () => { test.beforeEach(async ({ page }) => { diff --git a/ui/tests/page-objects/sign-in-page.ts b/ui/tests/sign-in/sign-in-page.ts similarity index 76% rename from ui/tests/page-objects/sign-in-page.ts rename to ui/tests/sign-in/sign-in-page.ts index 2c426606c7..a694359422 100644 --- a/ui/tests/page-objects/sign-in-page.ts +++ b/ui/tests/sign-in/sign-in-page.ts @@ -1,5 +1,6 @@ import { Page, Locator, expect } from "@playwright/test"; -import { HomePage } from "./home-page"; +import { BasePage } from "../base-page"; +import { HomePage } from "../home/home-page"; export interface SignInCredentials { email: string; @@ -11,8 +12,7 @@ export interface SocialAuthConfig { githubEnabled: boolean; } -export class SignInPage { - readonly page: Page; +export class SignInPage extends BasePage { readonly homePage: HomePage; // Form elements @@ -31,59 +31,48 @@ export class SignInPage { readonly backButton: Locator; // UI elements - readonly title: Locator; readonly logo: Locator; - readonly themeToggle: Locator; // Error messages readonly errorMessages: Locator; - readonly loadingIndicator: Locator; // SAML specific elements readonly samlModeTitle: Locator; readonly samlEmailInput: Locator; constructor(page: Page) { - this.page = page; + super(page); this.homePage = new HomePage(page); // Form elements - this.emailInput = page.getByLabel("Email"); - this.passwordInput = page.getByLabel("Password"); + this.emailInput = page.getByRole("textbox", { name: "Email" }); + this.passwordInput = page.getByRole("textbox", { name: "Password" }); this.loginButton = page.getByRole("button", { name: "Log in" }); this.form = page.locator("form"); // Social authentication buttons - this.googleButton = page.getByText("Continue with Google"); - this.githubButton = page.getByText("Continue with Github"); - this.samlButton = page.getByText("Continue with SAML SSO"); + this.googleButton = page.getByRole("button", { name: "Continue with Google" }); + this.githubButton = page.getByRole("button", { name: "Continue with Github" }); + this.samlButton = page.getByRole("button", { name: "Continue with SAML SSO" }); // Navigation elements this.signUpLink = page.getByRole("link", { name: "Sign up" }); - this.backButton = page.getByText("Back"); + this.backButton = page.getByRole("button", { name: "Back" }); // UI elements - this.title = page.getByText("Sign in", { exact: true }); this.logo = page.locator('svg[width="300"]'); - this.themeToggle = page.getByLabel("Toggle theme"); // Error messages this.errorMessages = page.locator('[role="alert"], .error-message, [data-testid="error"]'); - this.loadingIndicator = page.getByText("Loading"); // SAML specific elements - this.samlModeTitle = page.getByText("Sign in with SAML SSO"); - this.samlEmailInput = page.getByLabel("Email"); + this.samlModeTitle = page.getByRole("heading", { name: "Sign in with SAML SSO" }); + this.samlEmailInput = page.getByRole("textbox", { name: "Email" }); } // Navigation methods async goto(): Promise { - await this.page.goto("/sign-in"); - await this.waitForPageLoad(); - } - - async waitForPageLoad(): Promise { - await this.page.waitForLoadState("networkidle"); + await super.goto("/sign-in"); } // Form interaction methods @@ -148,8 +137,7 @@ export class SignInPage { async verifyPageLoaded(): Promise { await expect(this.page).toHaveTitle(/Prowler/); await expect(this.logo).toBeVisible(); - await expect(this.title).toBeVisible(); - await this.waitForPageLoad(); + await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible(); } async verifyFormElements(): Promise { @@ -169,7 +157,7 @@ export class SignInPage { } async verifyNavigationLinks(): Promise { - await expect(this.page.getByText("Need to create an account?")).toBeVisible(); + await expect(this.page.getByRole('link', { name: /Need to create an account\?/i })).toBeVisible(); await expect(this.signUpLink).toBeVisible(); } @@ -178,7 +166,7 @@ export class SignInPage { } async verifyLoginError(errorMessage: string = "Invalid email or password"): Promise { - await expect(this.page.getByText(errorMessage).first()).toBeVisible(); + await expect(this.page.getByRole("alert", { name: errorMessage })).toBeVisible(); await expect(this.page).toHaveURL("/sign-in"); } @@ -189,19 +177,19 @@ export class SignInPage { } async verifyNormalModeActive(): Promise { - await expect(this.title).toBeVisible(); + await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible(); await expect(this.passwordInput).toBeVisible(); } async verifyLoadingState(): Promise { await expect(this.loginButton).toHaveAttribute("aria-disabled", "true"); - await expect(this.loadingIndicator).toBeVisible(); + await super.verifyLoadingState(); } async verifyFormValidation(): Promise { // Check for common validation messages - const emailError = this.page.getByText("Please enter a valid email address."); - const passwordError = this.page.getByText("Password is required."); + const emailError = this.page.getByRole("alert", { name: "Please enter a valid email address." }); + const passwordError = this.page.getByRole("alert", { name: "Password is required." }); // At least one validation error should be visible await expect(emailError.or(passwordError)).toBeVisible(); @@ -240,30 +228,7 @@ export class SignInPage { return emailValue.length > 0 && passwordValue.length > 0; } - async getFormErrors(): Promise { - const errorElements = await this.errorMessages.all(); - const errors: string[] = []; - - for (const element of errorElements) { - const text = await element.textContent(); - if (text) { - errors.push(text.trim()); - } - } - - return errors; - } - // Browser interaction methods - async refresh(): Promise { - await this.page.reload(); - await this.waitForPageLoad(); - } - - async goBack(): Promise { - await this.page.goBack(); - await this.waitForPageLoad(); - } // Session management methods async logout(): Promise { @@ -272,7 +237,7 @@ export class SignInPage { async verifyLogoutSuccess(): Promise { await expect(this.page).toHaveURL("/sign-in"); - await expect(this.title).toBeVisible(); + await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible(); } // Advanced interaction methods @@ -295,7 +260,7 @@ export class SignInPage { // Error handling methods async handleSamlError(): Promise { - const samlError = this.page.getByText("SAML Authentication Error"); + const samlError = this.page.getByRole("alert", { name: "SAML Authentication Error" }); if (await samlError.isVisible()) { // Handle SAML error if present console.log("SAML authentication error detected"); diff --git a/ui/tests/sign-up/sign-up-page.ts b/ui/tests/sign-up/sign-up-page.ts new file mode 100644 index 0000000000..30d8f4d50d --- /dev/null +++ b/ui/tests/sign-up/sign-up-page.ts @@ -0,0 +1,117 @@ +import { Page, Locator, expect } from "@playwright/test"; +import { BasePage } from "../base-page"; + +export interface SignUpData { + name: string; + email: string; + password: string; + confirmPassword: string; + company?: string; + invitationToken?: string | null; + acceptTerms?: boolean; +} + +export class SignUpPage extends BasePage { + + // Form inputs + readonly nameInput: Locator; + readonly companyInput: Locator; + readonly emailInput: Locator; + readonly passwordInput: Locator; + readonly confirmPasswordInput: Locator; + readonly invitationTokenInput: Locator; + + // UI elements + readonly submitButton: Locator; + readonly loginLink: Locator; + readonly termsCheckbox: Locator; + + constructor(page: Page) { + super(page); + + // Prefer stable name attributes to avoid label ambiguity in composed inputs + this.nameInput = page.locator('input[name="name"]'); + this.companyInput = page.locator('input[name="company"]'); + this.emailInput = page.getByRole("textbox", { name: "Email" }); + this.passwordInput = page.locator('input[name="password"]'); + this.confirmPasswordInput = page.locator('input[name="confirmPassword"]'); + this.invitationTokenInput = page.locator('input[name="invitationToken"]'); + + this.submitButton = page.getByRole("button", { name: "Sign up" }); + this.loginLink = page.getByRole("link", { name: "Log in" }); + this.termsCheckbox = page.getByRole("checkbox", { name: /I agree with the/i }); + } + + async goto(): Promise { + await super.goto("/sign-up"); + } + + async verifyPageLoaded(): Promise { + await expect(this.page.getByRole("heading", { name: "Sign up" })).toBeVisible(); + await expect(this.emailInput).toBeVisible(); + await expect(this.submitButton).toBeVisible(); + await this.waitForPageLoad(); + } + + async fillName(name: string): Promise { + await this.nameInput.fill(name); + } + + async fillCompany(company?: string): Promise { + if (company) { + await this.companyInput.fill(company); + } + } + + async fillEmail(email: string): Promise { + await this.emailInput.fill(email); + } + + async fillPassword(password: string): Promise { + await this.passwordInput.fill(password); + } + + async fillConfirmPassword(confirmPassword: string): Promise { + await this.confirmPasswordInput.fill(confirmPassword); + } + + async fillInvitationToken(token?: string | null): Promise { + if (token) { + await this.invitationTokenInput.fill(token); + } + } + + async acceptTermsIfPresent(accept: boolean = true): Promise { + // Only in cloud env; check presence before interacting + if (await this.termsCheckbox.isVisible()) { + if (accept) { + await this.termsCheckbox.click(); + } + } + } + + async submit(): Promise { + await this.submitButton.click(); + } + + async signup(data: SignUpData): Promise { + await this.fillName(data.name); + await this.fillCompany(data.company); + await this.fillEmail(data.email); + await this.fillPassword(data.password); + await this.fillConfirmPassword(data.confirmPassword); + await this.fillInvitationToken(data.invitationToken ?? undefined); + await this.acceptTermsIfPresent(data.acceptTerms ?? true); + await this.submit(); + } + + async verifyRedirectToLogin(): Promise { + await expect(this.page).toHaveURL("/sign-in"); + } + + async verifyRedirectToEmailVerification(): Promise { + await expect(this.page).toHaveURL("/email-verification"); + } +} + + diff --git a/ui/tests/sign-up/sign-up.md b/ui/tests/sign-up/sign-up.md new file mode 100644 index 0000000000..1ebff2a7d9 --- /dev/null +++ b/ui/tests/sign-up/sign-up.md @@ -0,0 +1,43 @@ +### E2E Tests: User Sign-Up + +**Suite ID:** `SIGNUP-E2E` +**Feature:** New user registration flow. + +--- + +## Test Case: `SIGNUP-E2E-001` - Successful new user registration and login + +**Priority:** `critical` + +**Tags:** +- type → @e2e +- feature → @signup + +**Description/Objetive:** Registers a new user with valid data, verifies redirect to Login (OSS), and confirms the user can authenticate. + +**Preconditions:** +- Application is running, email domain & password is acceptable for sign-up. +- No existing data in Prowler is required; the test can run on a clean state. +- `E2E_NEW_PASSWORD` environment variable must be set with a valid password for the test. + +### Flow Steps: +1. Navigate to the Sign up page. +2. Fill the form with valid data (unique email, valid password, terms accepted). +3. Submit the form. +4. Verify redirect to the Login page. +5. Log in with the newly created credentials. + +### Expected Result: +- Sign-up succeeds and redirects to Login. +- User can log in successfully using the created credentials and reach the home page. + +### Key verification points: +- After submitting sign-up, the URL changes to `/sign-in`. +- The newly created credentials can be used to sign in successfully. +- After login, the user lands on the home (`/`) and main content is visible. + +### Notes: +- Test data uses a random base36 suffix to avoid collisions with email. +- The test requires the `E2E_NEW_PASSWORD` environment variable to be set before running. + + diff --git a/ui/tests/sign-up/sign-up.spec.ts b/ui/tests/sign-up/sign-up.spec.ts new file mode 100644 index 0000000000..19d831705c --- /dev/null +++ b/ui/tests/sign-up/sign-up.spec.ts @@ -0,0 +1,49 @@ +import { test } from "@playwright/test"; +import { SignUpPage } from "./sign-up-page"; +import { SignInPage } from "../sign-in/sign-in-page"; +import { makeSuffix } from "../helpers"; + +test.describe("Sign Up Flow", () => { + test( + "should register a new user successfully", + { tag: ["@critical", "@e2e", "@signup", "@SIGNUP-E2E-001"] }, + async ({ page }) => { + const password = process.env.E2E_NEW_PASSWORD; + + if (!password) { + throw new Error("E2E_NEW_PASSWORD environment variable is not set"); + } + + const signUpPage = new SignUpPage(page); + await signUpPage.goto(); + + // Generate unique test data + const suffix = makeSuffix(10); + const uniqueEmail = `e2e+${suffix}@prowler.com`; + + // Fill and submit the sign-up form + await signUpPage.signup({ + name: `E2E User ${suffix}`, + company: `Test E2E Co ${suffix}`, + email: uniqueEmail, + password: password, + confirmPassword: password, + acceptTerms: true, + }); + + // Verify no errors occurred during sign-up + await signUpPage.verifyNoErrors(); + + // Verify redirect to login page (OSS environment) + await signUpPage.verifyRedirectToLogin(); + + // Verify the newly created user can log in successfully + const signInPage = new SignInPage(page); + await signInPage.login({ + email: uniqueEmail, + password: password, + }); + await signInPage.verifySuccessfulLogin(); + }, + ); +});