mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
Compare commits
12 Commits
feat/PROWL
...
PROWLER-25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbe17b0fff | ||
|
|
73384ef326 | ||
|
|
6ad22ec274 | ||
|
|
512f0bb6df | ||
|
|
cef7fcc24b | ||
|
|
fcf42937aa | ||
|
|
2c9d8ad8ea | ||
|
|
f424342e7e | ||
|
|
9b7e4f59e1 | ||
|
|
d21222aa3a | ||
|
|
bdbb2fad78 | ||
|
|
cf7b66101c |
5
.github/workflows/ui-e2e-tests.yml
vendored
5
.github/workflows/ui-e2e-tests.yml
vendored
@@ -18,6 +18,11 @@ jobs:
|
||||
AUTH_TRUST_HOST: true
|
||||
NEXTAUTH_URL: 'http://localhost:3000'
|
||||
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1'
|
||||
E2E_GITHUB_USER: ${{ secrets.E2E_GITHUB_USER }}
|
||||
E2E_GITHUB_PASSWORD: ${{ secrets.E2E_GITHUB_PASSWORD }}
|
||||
SOCIAL_GITHUB_OAUTH_CLIENT_ID: ${{ secrets.E2E_SOCIAL_GITHUB_OAUTH_CLIENT_ID }}
|
||||
SOCIAL_GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.E2E_SOCIAL_GITHUB_OAUTH_CLIENT_SECRET }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
3
ui/.gitignore
vendored
3
ui/.gitignore
vendored
@@ -33,4 +33,5 @@ yarn-error.log*
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
next-env.d.ts
|
||||
playwright/.auth
|
||||
|
||||
@@ -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",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:debug": "playwright test --debug",
|
||||
"test:e2e:headed": "playwright test --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"
|
||||
},
|
||||
|
||||
@@ -20,9 +20,73 @@ export default defineConfig({
|
||||
},
|
||||
|
||||
projects: [
|
||||
// ===========================================
|
||||
// Authentication Setup Projects
|
||||
// ===========================================
|
||||
// These projects handle user authentication for different permission levels
|
||||
// Each setup creates authenticated state files that can be reused by test suites
|
||||
|
||||
// Admin user authentication setup
|
||||
// Creates authenticated state for admin users with full system permissions
|
||||
{
|
||||
name: "admin.auth.setup",
|
||||
testMatch: "admin.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Scans management user authentication setup
|
||||
// Creates authenticated state for users with scan management permissions
|
||||
{
|
||||
name: "manage-scans.auth.setup",
|
||||
testMatch: "manage-scans.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Integrations management user authentication setup
|
||||
// Creates authenticated state for users with integration management permissions
|
||||
{
|
||||
name: "manage-integrations.auth.setup",
|
||||
testMatch: "manage-integrations.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Account management user authentication setup
|
||||
// Creates authenticated state for users with account management permissions
|
||||
{
|
||||
name: "manage-account.auth.setup",
|
||||
testMatch: "manage-account.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Cloud providers management user authentication setup
|
||||
// Creates authenticated state for users with cloud provider management permissions
|
||||
{
|
||||
name: "manage-cloud-providers.auth.setup",
|
||||
testMatch: "manage-cloud-providers.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Unlimited visibility user authentication setup
|
||||
// Creates authenticated state for users with unlimited visibility permissions
|
||||
{
|
||||
name: "unlimited-visibility.auth.setup",
|
||||
testMatch: "unlimited-visibility.auth.setup.ts",
|
||||
},
|
||||
|
||||
// Invite and manage users authentication setup
|
||||
// Creates authenticated state for users with user invitation and management permissions
|
||||
{
|
||||
name: "invite-and-manage-users.auth.setup",
|
||||
testMatch: "invite-and-manage-users.auth.setup.ts",
|
||||
},
|
||||
// ===========================================
|
||||
// Test Suite Projects
|
||||
// ===========================================
|
||||
// These projects run the actual test suites
|
||||
{
|
||||
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",
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
159
ui/tests/base-page.ts
Normal file
159
ui/tests/base-page.ts
Normal file
@@ -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.getByText("Loading");
|
||||
this.themeToggle = page.getByLabel("Toggle theme");
|
||||
}
|
||||
|
||||
// Common navigation methods
|
||||
async goto(url: string): Promise<void> {
|
||||
await this.page.goto(url);
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async waitForPageLoad(): Promise<void> {
|
||||
await this.page.waitForLoadState("networkidle");
|
||||
}
|
||||
|
||||
async refresh(): Promise<void> {
|
||||
await this.page.reload();
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async goBack(): Promise<void> {
|
||||
await this.page.goBack();
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
// Common verification methods
|
||||
async verifyPageTitle(expectedTitle: string | RegExp): Promise<void> {
|
||||
await expect(this.page).toHaveTitle(expectedTitle);
|
||||
}
|
||||
|
||||
async verifyLoadingState(): Promise<void> {
|
||||
await expect(this.loadingIndicator).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyNoLoadingState(): Promise<void> {
|
||||
await expect(this.loadingIndicator).not.toBeVisible();
|
||||
}
|
||||
|
||||
// Common form interaction methods
|
||||
async clearInput(input: Locator): Promise<void> {
|
||||
await input.clear();
|
||||
}
|
||||
|
||||
async fillInput(input: Locator, value: string): Promise<void> {
|
||||
await input.fill(value);
|
||||
}
|
||||
|
||||
async clickButton(button: Locator): Promise<void> {
|
||||
await button.click();
|
||||
}
|
||||
|
||||
// Common validation methods
|
||||
async verifyElementVisible(element: Locator): Promise<void> {
|
||||
await expect(element).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyElementNotVisible(element: Locator): Promise<void> {
|
||||
await expect(element).not.toBeVisible();
|
||||
}
|
||||
|
||||
async verifyElementText(element: Locator, expectedText: string): Promise<void> {
|
||||
await expect(element).toHaveText(expectedText);
|
||||
}
|
||||
|
||||
async verifyElementContainsText(element: Locator, expectedText: string): Promise<void> {
|
||||
await expect(element).toContainText(expectedText);
|
||||
}
|
||||
|
||||
// Common accessibility methods
|
||||
async verifyKeyboardNavigation(elements: Locator[]): Promise<void> {
|
||||
for (const element of elements) {
|
||||
await this.page.keyboard.press("Tab");
|
||||
await expect(element).toBeFocused();
|
||||
}
|
||||
}
|
||||
|
||||
async verifyAriaLabels(elements: { locator: Locator; expectedLabel: string }[]): Promise<void> {
|
||||
for (const { locator, expectedLabel } of elements) {
|
||||
await expect(locator).toHaveAttribute("aria-label", expectedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
// Common utility methods
|
||||
async getElementText(element: Locator): Promise<string> {
|
||||
return await element.textContent() || "";
|
||||
}
|
||||
|
||||
async getElementValue(element: Locator): Promise<string> {
|
||||
return await element.inputValue();
|
||||
}
|
||||
|
||||
async isElementVisible(element: Locator): Promise<boolean> {
|
||||
return await element.isVisible();
|
||||
}
|
||||
|
||||
async isElementEnabled(element: Locator): Promise<boolean> {
|
||||
return await element.isEnabled();
|
||||
}
|
||||
|
||||
// Common error handling methods
|
||||
async getFormErrors(): Promise<string[]> {
|
||||
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<void> {
|
||||
const errors = await this.getFormErrors();
|
||||
expect(errors).toHaveLength(0);
|
||||
}
|
||||
|
||||
// Common wait methods
|
||||
async waitForElement(element: Locator, timeout: number = 5000): Promise<void> {
|
||||
await element.waitFor({ timeout });
|
||||
}
|
||||
|
||||
async waitForElementToDisappear(element: Locator, timeout: number = 5000): Promise<void> {
|
||||
await element.waitFor({ state: "hidden", timeout });
|
||||
}
|
||||
|
||||
async waitForUrl(expectedUrl: string | RegExp, timeout: number = 5000): Promise<void> {
|
||||
await this.page.waitForURL(expectedUrl, { timeout });
|
||||
}
|
||||
|
||||
// Common screenshot methods
|
||||
async takeScreenshot(name: string): Promise<void> {
|
||||
await this.page.screenshot({ path: `screenshots/${name}.png` });
|
||||
}
|
||||
|
||||
async takeElementScreenshot(element: Locator, name: string): Promise<void> {
|
||||
await element.screenshot({ path: `screenshots/${name}.png` });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Page, expect } from "@playwright/test";
|
||||
import { SignInPage, SignInCredentials } from "./sign-in/sign-in-page";
|
||||
|
||||
export const ERROR_MESSAGES = {
|
||||
INVALID_CREDENTIALS: "Invalid email or password",
|
||||
@@ -138,6 +139,41 @@ export async function verifyDashboardRoute(page: Page) {
|
||||
await expect(page).toHaveURL("/");
|
||||
}
|
||||
|
||||
export async function authenticateAndSaveState(
|
||||
page: Page,
|
||||
email: string,
|
||||
password: string,
|
||||
storagePath: string,
|
||||
) {
|
||||
if (!email || !password) {
|
||||
throw new Error('Email and password are required for authentication and save state');
|
||||
}
|
||||
|
||||
// Create SignInPage instance
|
||||
const signInPage = new SignInPage(page);
|
||||
const credentials: SignInCredentials = { email, password };
|
||||
|
||||
// Perform authentication steps using Page Object Model
|
||||
await signInPage.goto();
|
||||
await signInPage.login(credentials);
|
||||
await signInPage.verifySuccessfulLogin();
|
||||
|
||||
// Save authentication state
|
||||
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();
|
||||
|
||||
103
ui/tests/home/home-page.ts
Normal file
103
ui/tests/home/home-page.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Page, Locator, expect } from "@playwright/test";
|
||||
import { BasePage } from "../base-page";
|
||||
|
||||
export class HomePage extends BasePage {
|
||||
|
||||
// Main content elements
|
||||
readonly mainContent: Locator;
|
||||
readonly breadcrumbs: Locator;
|
||||
readonly overviewHeading: Locator;
|
||||
|
||||
// Navigation elements
|
||||
readonly navigationMenu: Locator;
|
||||
readonly userMenu: Locator;
|
||||
readonly signOutButton: Locator;
|
||||
|
||||
// Dashboard elements
|
||||
readonly dashboardCards: Locator;
|
||||
readonly overviewSection: Locator;
|
||||
|
||||
// UI elements
|
||||
readonly logo: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
||||
// Main content elements
|
||||
this.mainContent = page.locator("main");
|
||||
this.breadcrumbs = page.getByLabel("Breadcrumbs");
|
||||
this.overviewHeading = page.getByRole("heading", { name: "Overview", exact: true });
|
||||
|
||||
// Navigation elements
|
||||
this.navigationMenu = page.locator("nav");
|
||||
this.userMenu = page.getByRole("button", { name: /user menu/i });
|
||||
this.signOutButton = page.getByRole("button", { name: "Sign out" });
|
||||
|
||||
// Dashboard elements
|
||||
this.dashboardCards = page.locator('[data-testid="dashboard-card"]');
|
||||
this.overviewSection = page.locator('[data-testid="overview-section"]');
|
||||
|
||||
// UI elements
|
||||
this.logo = page.locator('svg[width="300"]');
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
async goto(): Promise<void> {
|
||||
await super.goto("/");
|
||||
}
|
||||
|
||||
// Verification methods
|
||||
async verifyPageLoaded(): Promise<void> {
|
||||
await expect(this.page).toHaveURL("/");
|
||||
await expect(this.mainContent).toBeVisible();
|
||||
await expect(this.overviewHeading).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyBreadcrumbs(): Promise<void> {
|
||||
await expect(this.breadcrumbs).toBeVisible();
|
||||
await expect(this.overviewHeading).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyMainContent(): Promise<void> {
|
||||
await expect(this.mainContent).toBeVisible();
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
async navigateToOverview(): Promise<void> {
|
||||
await this.overviewHeading.click();
|
||||
}
|
||||
|
||||
async openUserMenu(): Promise<void> {
|
||||
await this.userMenu.click();
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
await this.openUserMenu();
|
||||
await this.signOutButton.click();
|
||||
}
|
||||
|
||||
// Dashboard methods
|
||||
async verifyDashboardCards(): Promise<void> {
|
||||
await expect(this.dashboardCards.first()).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyOverviewSection(): Promise<void> {
|
||||
await expect(this.overviewSection).toBeVisible();
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
|
||||
// Accessibility methods
|
||||
async verifyKeyboardNavigation(): Promise<void> {
|
||||
// Test tab navigation through main elements
|
||||
await this.page.keyboard.press("Tab");
|
||||
await expect(this.themeToggle).toBeFocused();
|
||||
}
|
||||
|
||||
async waitForContentLoad(): Promise<void> {
|
||||
await this.page.waitForFunction(() => {
|
||||
const main = document.querySelector("main");
|
||||
return main && main.offsetHeight > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
16
ui/tests/setups/admin.auth.setup.ts
Normal file
16
ui/tests/setups/admin.auth.setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test as authAdminSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const adminUserFile = 'playwright/.auth/admin_user.json';
|
||||
|
||||
authAdminSetup('authenticate as admin e2e user', async ({ page }) => {
|
||||
|
||||
const adminEmail = process.env.E2E_ADMIN_USER;
|
||||
const adminPassword = process.env.E2E_ADMIN_PASSWORD;
|
||||
|
||||
if (!adminEmail || !adminPassword) {
|
||||
throw new Error('E2E_ADMIN_USER and E2E_ADMIN_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, adminEmail, adminPassword, adminUserFile);
|
||||
});
|
||||
15
ui/tests/setups/invite-and-manage-users.auth.setup.ts
Normal file
15
ui/tests/setups/invite-and-manage-users.auth.setup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test as authInviteAndManageUsersSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const inviteAndManageUsersUserFile = 'playwright/.auth/invite_and_manage_users_user.json';
|
||||
|
||||
authInviteAndManageUsersSetup('authenticate as invite and manage users e2e user', async ({ page }) => {
|
||||
const inviteAndManageUsersEmail = process.env.E2E_INVITE_AND_MANAGE_USERS_USER;
|
||||
const inviteAndManageUsersPassword = process.env.E2E_INVITE_AND_MANAGE_USERS_PASSWORD;
|
||||
|
||||
if (!inviteAndManageUsersEmail || !inviteAndManageUsersPassword) {
|
||||
throw new Error('E2E_INVITE_AND_MANAGE_USERS_USER and E2E_INVITE_AND_MANAGE_USERS_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, inviteAndManageUsersEmail, inviteAndManageUsersPassword, inviteAndManageUsersUserFile);
|
||||
});
|
||||
16
ui/tests/setups/manage-account.auth.setup.ts
Normal file
16
ui/tests/setups/manage-account.auth.setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test as authManageAccountSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const manageAccountUserFile = 'playwright/.auth/manage_account_user.json';
|
||||
|
||||
authManageAccountSetup('authenticate as manage account e2e user', async ({ page }) => {
|
||||
const accountEmail = process.env.E2E_MANAGE_ACCOUNT_USER;
|
||||
const accountPassword = process.env.E2E_MANAGE_ACCOUNT_PASSWORD;
|
||||
|
||||
|
||||
if (!accountEmail || !accountPassword) {
|
||||
throw new Error('E2E_MANAGE_ACCOUNT_USER and E2E_MANAGE_ACCOUNT_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, accountEmail, accountPassword, manageAccountUserFile);
|
||||
});
|
||||
16
ui/tests/setups/manage-cloud-providers.auth.setup.ts
Normal file
16
ui/tests/setups/manage-cloud-providers.auth.setup.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { test as authManageCloudProvidersSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const manageCloudProvidersUserFile = 'playwright/.auth/manage_cloud_providers_user.json';
|
||||
|
||||
authManageCloudProvidersSetup('authenticate as manage cloud providers e2e user', async ({ page }) => {
|
||||
const cloudProvidersEmail = process.env.E2E_MANAGE_CLOUD_PROVIDERS_USER;
|
||||
const cloudProvidersPassword = process.env.E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD;
|
||||
|
||||
|
||||
if (!cloudProvidersEmail || !cloudProvidersPassword) {
|
||||
throw new Error('E2E_MANAGE_CLOUD_PROVIDERS_USER and E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, cloudProvidersEmail, cloudProvidersPassword, manageCloudProvidersUserFile);
|
||||
});
|
||||
15
ui/tests/setups/manage-integrations.auth.setup.ts
Normal file
15
ui/tests/setups/manage-integrations.auth.setup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test as authManageIntegrationsSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const manageIntegrationsUserFile = 'playwright/.auth/manage_integrations_user.json';
|
||||
|
||||
authManageIntegrationsSetup('authenticate as integrations e2e user', async ({ page }) => {
|
||||
const integrationsEmail = process.env.E2E_MANAGE_INTEGRATIONS_USER;
|
||||
const integrationsPassword = process.env.E2E_MANAGE_INTEGRATIONS_PASSWORD;
|
||||
|
||||
if (!integrationsEmail || !integrationsPassword) {
|
||||
throw new Error('E2E_MANAGE_INTEGRATIONS_USER and E2E_MANAGE_INTEGRATIONS_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, integrationsEmail, integrationsPassword, manageIntegrationsUserFile);
|
||||
});
|
||||
15
ui/tests/setups/manage-scans.auth.setup.ts
Normal file
15
ui/tests/setups/manage-scans.auth.setup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test as authManageScansSetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const manageScansUserFile = 'playwright/.auth/manage_scans_user.json';
|
||||
|
||||
authManageScansSetup('authenticate as scans e2e user', async ({ page }) => {
|
||||
const scansEmail = process.env.E2E_MANAGE_SCANS_USER;
|
||||
const scansPassword = process.env.E2E_MANAGE_SCANS_PASSWORD;
|
||||
|
||||
if (!scansEmail || !scansPassword) {
|
||||
throw new Error('E2E_MANAGE_SCANS_USER and E2E_MANAGE_SCANS_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, scansEmail, scansPassword, manageScansUserFile);
|
||||
});
|
||||
15
ui/tests/setups/unlimited-visibility.auth.setup.ts
Normal file
15
ui/tests/setups/unlimited-visibility.auth.setup.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test as authUnlimitedVisibilitySetup } from '@playwright/test';
|
||||
import { authenticateAndSaveState } from '@/tests/helpers';
|
||||
|
||||
const unlimitedVisibilityUserFile = 'playwright/.auth/unlimited_visibility_user.json';
|
||||
|
||||
authUnlimitedVisibilitySetup('authenticate as unlimited visibility e2e user', async ({ page }) => {
|
||||
const unlimitedVisibilityEmail = process.env.E2E_UNLIMITED_VISIBILITY_USER;
|
||||
const unlimitedVisibilityPassword = process.env.E2E_UNLIMITED_VISIBILITY_PASSWORD;
|
||||
|
||||
if (!unlimitedVisibilityEmail || !unlimitedVisibilityPassword) {
|
||||
throw new Error('E2E_UNLIMITED_VISIBILITY_USER and E2E_UNLIMITED_VISIBILITY_PASSWORD environment variables are required');
|
||||
}
|
||||
|
||||
await authenticateAndSaveState(page, unlimitedVisibilityEmail, unlimitedVisibilityPassword, unlimitedVisibilityUserFile);
|
||||
});
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
ERROR_MESSAGES,
|
||||
URLS,
|
||||
verifyLoadingState,
|
||||
} from "./helpers";
|
||||
} from "../helpers";
|
||||
|
||||
test.describe("Login Flow", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
281
ui/tests/sign-in/sign-in-page.ts
Normal file
281
ui/tests/sign-in/sign-in-page.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { Page, Locator, expect } from "@playwright/test";
|
||||
import { BasePage } from "../base-page";
|
||||
import { HomePage } from "../home/home-page";
|
||||
|
||||
export interface SignInCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface SocialAuthConfig {
|
||||
googleEnabled: boolean;
|
||||
githubEnabled: boolean;
|
||||
}
|
||||
|
||||
export class SignInPage extends BasePage {
|
||||
readonly homePage: HomePage;
|
||||
|
||||
// Form elements
|
||||
readonly emailInput: Locator;
|
||||
readonly passwordInput: Locator;
|
||||
readonly loginButton: Locator;
|
||||
readonly form: Locator;
|
||||
|
||||
// Social authentication buttons
|
||||
readonly googleButton: Locator;
|
||||
readonly githubButton: Locator;
|
||||
readonly samlButton: Locator;
|
||||
|
||||
// Navigation elements
|
||||
readonly signUpLink: Locator;
|
||||
readonly backButton: Locator;
|
||||
|
||||
// UI elements
|
||||
readonly logo: Locator;
|
||||
|
||||
// Error messages
|
||||
readonly errorMessages: Locator;
|
||||
|
||||
// SAML specific elements
|
||||
readonly samlModeTitle: Locator;
|
||||
readonly samlEmailInput: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
this.homePage = new HomePage(page);
|
||||
|
||||
// Form elements
|
||||
this.emailInput = page.getByLabel("Email");
|
||||
this.passwordInput = page.getByLabel("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");
|
||||
|
||||
// Navigation elements
|
||||
this.signUpLink = page.getByRole("link", { name: "Sign up" });
|
||||
this.backButton = page.getByText("Back");
|
||||
|
||||
// UI elements
|
||||
this.logo = page.locator('svg[width="300"]');
|
||||
|
||||
// Error messages
|
||||
this.errorMessages = page.locator('[role="alert"], .error-message, [data-testid="error"]');
|
||||
|
||||
// SAML specific elements
|
||||
this.samlModeTitle = page.getByText("Sign in with SAML SSO");
|
||||
this.samlEmailInput = page.getByLabel("Email");
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
async goto(): Promise<void> {
|
||||
await super.goto("/sign-in");
|
||||
}
|
||||
|
||||
// Form interaction methods
|
||||
async fillEmail(email: string): Promise<void> {
|
||||
await this.emailInput.fill(email);
|
||||
}
|
||||
|
||||
async fillPassword(password: string): Promise<void> {
|
||||
await this.passwordInput.fill(password);
|
||||
}
|
||||
|
||||
async fillCredentials(credentials: SignInCredentials): Promise<void> {
|
||||
await this.fillEmail(credentials.email);
|
||||
await this.fillPassword(credentials.password);
|
||||
}
|
||||
|
||||
async submitForm(): Promise<void> {
|
||||
await this.loginButton.click();
|
||||
}
|
||||
|
||||
async login(credentials: SignInCredentials): Promise<void> {
|
||||
await this.fillCredentials(credentials);
|
||||
await this.submitForm();
|
||||
}
|
||||
|
||||
// Social authentication methods
|
||||
async clickGoogleAuth(): Promise<void> {
|
||||
await this.googleButton.click();
|
||||
}
|
||||
|
||||
async clickGithubAuth(): Promise<void> {
|
||||
await this.githubButton.click();
|
||||
}
|
||||
|
||||
async clickSamlAuth(): Promise<void> {
|
||||
await this.samlButton.click();
|
||||
}
|
||||
|
||||
// SAML SSO methods
|
||||
async toggleSamlMode(): Promise<void> {
|
||||
await this.clickSamlAuth();
|
||||
}
|
||||
|
||||
async goBackFromSaml(): Promise<void> {
|
||||
await this.backButton.click();
|
||||
}
|
||||
|
||||
async fillSamlEmail(email: string): Promise<void> {
|
||||
await this.samlEmailInput.fill(email);
|
||||
}
|
||||
|
||||
async submitSamlForm(): Promise<void> {
|
||||
await this.submitForm();
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
async goToSignUp(): Promise<void> {
|
||||
await this.signUpLink.click();
|
||||
}
|
||||
|
||||
// Validation and assertion methods
|
||||
async verifyPageLoaded(): Promise<void> {
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.logo).toBeVisible();
|
||||
await expect(this.page.getByText("Sign in", { exact: true })).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyFormElements(): Promise<void> {
|
||||
await expect(this.emailInput).toBeVisible();
|
||||
await expect(this.passwordInput).toBeVisible();
|
||||
await expect(this.loginButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifySocialButtons(config: SocialAuthConfig): Promise<void> {
|
||||
if (config.googleEnabled) {
|
||||
await expect(this.googleButton).toBeVisible();
|
||||
}
|
||||
if (config.githubEnabled) {
|
||||
await expect(this.githubButton).toBeVisible();
|
||||
}
|
||||
await expect(this.samlButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyNavigationLinks(): Promise<void> {
|
||||
await expect(this.page.getByText("Need to create an account?")).toBeVisible();
|
||||
await expect(this.signUpLink).toBeVisible();
|
||||
}
|
||||
|
||||
async verifySuccessfulLogin(): Promise<void> {
|
||||
await this.homePage.verifyPageLoaded();
|
||||
}
|
||||
|
||||
async verifyLoginError(errorMessage: string = "Invalid email or password"): Promise<void> {
|
||||
await expect(this.page.getByText(errorMessage).first()).toBeVisible();
|
||||
await expect(this.page).toHaveURL("/sign-in");
|
||||
}
|
||||
|
||||
async verifySamlModeActive(): Promise<void> {
|
||||
await expect(this.samlModeTitle).toBeVisible();
|
||||
await expect(this.passwordInput).not.toBeVisible();
|
||||
await expect(this.backButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyNormalModeActive(): Promise<void> {
|
||||
await expect(this.page.getByText("Sign in", { exact: true })).toBeVisible();
|
||||
await expect(this.passwordInput).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyLoadingState(): Promise<void> {
|
||||
await expect(this.loginButton).toHaveAttribute("aria-disabled", "true");
|
||||
await super.verifyLoadingState();
|
||||
}
|
||||
|
||||
async verifyFormValidation(): Promise<void> {
|
||||
// Check for common validation messages
|
||||
const emailError = this.page.getByText("Please enter a valid email address.");
|
||||
const passwordError = this.page.getByText("Password is required.");
|
||||
|
||||
// At least one validation error should be visible
|
||||
await expect(emailError.or(passwordError)).toBeVisible();
|
||||
}
|
||||
|
||||
// Accessibility methods
|
||||
async verifyKeyboardNavigation(): Promise<void> {
|
||||
// Test tab navigation through form elements
|
||||
await this.page.keyboard.press("Tab"); // Theme toggle
|
||||
await this.page.keyboard.press("Tab"); // Email field
|
||||
await expect(this.emailInput).toBeFocused();
|
||||
|
||||
await this.page.keyboard.press("Tab"); // Password field
|
||||
await expect(this.passwordInput).toBeFocused();
|
||||
|
||||
await this.page.keyboard.press("Tab"); // Show password button
|
||||
await this.page.keyboard.press("Tab"); // Login button
|
||||
await expect(this.loginButton).toBeFocused();
|
||||
}
|
||||
|
||||
async verifyAriaLabels(): Promise<void> {
|
||||
await expect(this.page.getByRole("textbox", { name: "Email" })).toBeVisible();
|
||||
await expect(this.page.getByRole("textbox", { name: "Password" })).toBeVisible();
|
||||
await expect(this.page.getByRole("button", { name: "Log in" })).toBeVisible();
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
async clearForm(): Promise<void> {
|
||||
await this.emailInput.clear();
|
||||
await this.passwordInput.clear();
|
||||
}
|
||||
|
||||
async isFormValid(): Promise<boolean> {
|
||||
const emailValue = await this.emailInput.inputValue();
|
||||
const passwordValue = await this.passwordInput.inputValue();
|
||||
return emailValue.length > 0 && passwordValue.length > 0;
|
||||
}
|
||||
|
||||
// Browser interaction methods
|
||||
|
||||
// Session management methods
|
||||
async logout(): Promise<void> {
|
||||
await this.homePage.signOut();
|
||||
}
|
||||
|
||||
async verifyLogoutSuccess(): Promise<void> {
|
||||
await expect(this.page).toHaveURL("/sign-in");
|
||||
await expect(this.page.getByText("Sign in", { exact: true })).toBeVisible();
|
||||
}
|
||||
|
||||
// Advanced interaction methods
|
||||
async fillFormWithValidation(credentials: SignInCredentials): Promise<void> {
|
||||
// Fill email first and check for validation
|
||||
await this.fillEmail(credentials.email);
|
||||
await this.page.keyboard.press("Tab"); // Trigger validation
|
||||
|
||||
// Fill password
|
||||
await this.fillPassword(credentials.password);
|
||||
}
|
||||
|
||||
async submitFormWithEnterKey(): Promise<void> {
|
||||
await this.passwordInput.press("Enter");
|
||||
}
|
||||
|
||||
async submitFormWithButtonClick(): Promise<void> {
|
||||
await this.submitForm();
|
||||
}
|
||||
|
||||
// Error handling methods
|
||||
async handleSamlError(): Promise<void> {
|
||||
const samlError = this.page.getByText("SAML Authentication Error");
|
||||
if (await samlError.isVisible()) {
|
||||
// Handle SAML error if present
|
||||
console.log("SAML authentication error detected");
|
||||
}
|
||||
}
|
||||
|
||||
// Wait methods
|
||||
async waitForFormSubmission(): Promise<void> {
|
||||
await this.page.waitForFunction(() => {
|
||||
const button = document.querySelector('button[aria-disabled="true"]');
|
||||
return button === null;
|
||||
});
|
||||
}
|
||||
|
||||
async waitForRedirect(expectedUrl: string): Promise<void> {
|
||||
await this.page.waitForURL(expectedUrl);
|
||||
}
|
||||
}
|
||||
201
ui/tests/sign-up/sign-up-page.ts
Normal file
201
ui/tests/sign-up/sign-up-page.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
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;
|
||||
|
||||
// Social login buttons
|
||||
readonly githubButton: 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.getByLabel("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.getByText("I agree with the");
|
||||
|
||||
// Social login buttons
|
||||
this.githubButton = page.getByRole("button", { name: "Continue with Github" });
|
||||
}
|
||||
|
||||
async goto(): Promise<void> {
|
||||
await super.goto("/sign-up");
|
||||
}
|
||||
|
||||
async verifyPageLoaded(): Promise<void> {
|
||||
// Verify unique title - only appears on sign-up page
|
||||
await expect(this.page.locator('p').getByText("Sign up", { exact: true }).first()).toBeVisible();
|
||||
|
||||
// Verify all required form fields are present
|
||||
await expect(this.nameInput).toBeVisible();
|
||||
await expect(this.emailInput).toBeVisible();
|
||||
await expect(this.passwordInput).toBeVisible();
|
||||
await expect(this.confirmPasswordInput).toBeVisible();
|
||||
|
||||
// Verify primary action button
|
||||
await expect(this.submitButton).toBeVisible();
|
||||
|
||||
// Verify distinctive separator between form and social login
|
||||
await expect(this.page.getByText("OR", { exact: true })).toBeVisible();
|
||||
|
||||
// Verify social login options are available (distinctive of sign-up vs other pages)
|
||||
await expect(this.page.getByText("Continue with Github")).toBeVisible();
|
||||
await expect(this.page.getByText("Continue with Google")).toBeVisible();
|
||||
|
||||
// Verify sign-up specific link (different from sign-in page)
|
||||
await expect(this.page.getByText("Already have an account?")).toBeVisible();
|
||||
await expect(this.loginLink).toBeVisible();
|
||||
|
||||
// Verify correct URL
|
||||
expect(this.page.url()).toContain('/sign-up');
|
||||
}
|
||||
|
||||
async fillName(name: string): Promise<void> {
|
||||
await this.nameInput.fill(name);
|
||||
}
|
||||
|
||||
async fillCompany(company?: string): Promise<void> {
|
||||
if (company) {
|
||||
await this.companyInput.fill(company);
|
||||
}
|
||||
}
|
||||
|
||||
async fillEmail(email: string): Promise<void> {
|
||||
await this.emailInput.fill(email);
|
||||
}
|
||||
|
||||
async fillPassword(password: string): Promise<void> {
|
||||
await this.passwordInput.fill(password);
|
||||
}
|
||||
|
||||
async fillConfirmPassword(confirmPassword: string): Promise<void> {
|
||||
await this.confirmPasswordInput.fill(confirmPassword);
|
||||
}
|
||||
|
||||
async fillInvitationToken(token?: string | null): Promise<void> {
|
||||
if (token) {
|
||||
await this.invitationTokenInput.fill(token);
|
||||
}
|
||||
}
|
||||
|
||||
async acceptTermsIfPresent(accept: boolean = true): Promise<void> {
|
||||
// Only in cloud env; check presence before interacting
|
||||
if (await this.termsCheckbox.isVisible()) {
|
||||
if (accept) {
|
||||
await this.termsCheckbox.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async signup(data: SignUpData): Promise<void> {
|
||||
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<void> {
|
||||
await expect(this.page).toHaveURL("/sign-in");
|
||||
}
|
||||
|
||||
async verifyRedirectToEmailVerification(): Promise<void> {
|
||||
await expect(this.page).toHaveURL("/email-verification");
|
||||
}
|
||||
|
||||
// Social login methods
|
||||
async clickGithubLogin(): Promise<void> {
|
||||
await this.githubButton.click();
|
||||
}
|
||||
|
||||
async verifyGithubButtonVisible(): Promise<void> {
|
||||
await expect(this.githubButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyGithubButtonEnabled(): Promise<void> {
|
||||
await expect(this.githubButton).toBeEnabled();
|
||||
}
|
||||
|
||||
async verifyRedirectToGithubOAuth(): Promise<void> {
|
||||
// Verify redirect to Github OAuth page
|
||||
await expect(this.page).toHaveURL(/github\.com\/login/);
|
||||
}
|
||||
|
||||
async verifyGithubOAuthFlow(): Promise<void> {
|
||||
// Verify Github OAuth page elements
|
||||
await expect(this.page.getByText("Sign in to GitHub")).toBeVisible();
|
||||
await expect(this.page.getByText("to continue to Prowler")).toBeVisible();
|
||||
}
|
||||
|
||||
async fillGithubCredentials(username: string, password: string): Promise<void> {
|
||||
// Fill Github login form based on MCP exploration
|
||||
await this.page.getByRole("textbox", { name: "Username or email address" }).fill(username);
|
||||
await this.page.getByRole("textbox", { name: "Password" }).fill(password);
|
||||
}
|
||||
|
||||
async submitGithubLogin(): Promise<void> {
|
||||
// Click Github Sign in button
|
||||
await this.page.locator('input[type="submit"][name="commit"][value="Sign in"]').click();
|
||||
}
|
||||
|
||||
async completeGithubOAuth(username: string, password: string): Promise<void> {
|
||||
// Complete the Github OAuth flow
|
||||
await this.fillGithubCredentials(username, password);
|
||||
await this.submitGithubLogin();
|
||||
}
|
||||
|
||||
async verifyGithubApplicationInfo(): Promise<void> {
|
||||
// Verify Prowler application info is displayed on GitHub OAuth page
|
||||
await expect(this.page.locator('img[alt*="Prowler"]')).toBeVisible();
|
||||
|
||||
// Verify the OAuth consent message shows Prowler app name
|
||||
await expect(this.page.getByText(/to continue to.*Prowler/i)).toBeVisible();
|
||||
|
||||
// Verify "Sign in to GitHub" text is present
|
||||
await expect(this.page.getByText("Sign in to GitHub")).toBeVisible();
|
||||
|
||||
// Verify GitHub OAuth form elements are present
|
||||
await expect(this.page.getByRole("textbox", { name: /username or email/i })).toBeVisible();
|
||||
await expect(this.page.getByRole("textbox", { name: /password/i })).toBeVisible();
|
||||
await expect(this.page.locator('input[type="submit"][name="commit"][value="Sign in"]')).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
96
ui/tests/sign-up/sign-up.md
Normal file
96
ui/tests/sign-up/sign-up.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# 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, 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.
|
||||
|
||||
### 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.
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `SIGNUP-E2E-002` - Github Social Sign-up OAuth Flow
|
||||
|
||||
**Priority:** `critical`
|
||||
|
||||
**Tags:**
|
||||
- type → @e2e
|
||||
- feature → @signup
|
||||
- social → @social
|
||||
|
||||
**Description/Objective:** Validates that users can complete the full Github OAuth flow for social sign-up, including authentication and successful return to Prowler
|
||||
|
||||
**Preconditions:**
|
||||
- Application is running
|
||||
- Github OAuth app is configured
|
||||
- E2E_GITHUB_USER and E2E_GITHUB_PASSWORD environment variables are set with valid Github credentials
|
||||
|
||||
### Flow Steps:
|
||||
1. Navigate to sign-up page
|
||||
2. Verify page loads with social login options
|
||||
3. Verify Github login button is visible and enabled
|
||||
4. Click "Continue with Github" button
|
||||
5. Verify redirect to Github OAuth page
|
||||
6. Verify OAuth configuration parameters
|
||||
7. Fill Github credentials (username and password)
|
||||
8. Submit Github login form
|
||||
9. Verify successful redirect back to Prowler
|
||||
|
||||
### Expected Result:
|
||||
- User is redirected to Github OAuth authorization page
|
||||
- OAuth URL contains correct client_id, redirect_uri, and scope parameters
|
||||
- Github OAuth page displays proper application information
|
||||
- User can successfully authenticate with Github credentials
|
||||
- User is redirected back to Prowler application after successful authentication
|
||||
|
||||
|
||||
### Key verification points:
|
||||
- Github button is visible and clickable on sign-up page
|
||||
- Redirect to github.com/login occurs correctly
|
||||
- OAuth URL structure follows GitHub OAuth format (https://github.com/login)
|
||||
- GitHub OAuth page displays Prowler application logo and information
|
||||
- GitHub OAuth page shows correct consent message "to continue to Prowler"
|
||||
- GitHub OAuth page shows "Sign in to GitHub" header
|
||||
- GitHub login form elements are present and accessible (username/email, password, sign in button)
|
||||
- Github login form accepts credentials correctly
|
||||
- Successful authentication redirects back to Prowler home
|
||||
- After redirect, verify authenticated area is visible (e.g., main dashboard content)
|
||||
|
||||
### Notes:
|
||||
- Test requires E2E_GITHUB_USER and E2E_GITHUB_PASSWORD environment variables
|
||||
- Test completes full OAuth flow including Github authentication
|
||||
- Test verifies successful social sign-up integration
|
||||
- Github credentials must be valid for test to pass
|
||||
|
||||
|
||||
81
ui/tests/sign-up/sign-up.spec.ts
Normal file
81
ui/tests/sign-up/sign-up.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { SignUpPage } from "./sign-up-page";
|
||||
import { SignInPage } from "../sign-in/sign-in-page";
|
||||
import { makeSuffix, TEST_CREDENTIALS } from "../helpers";
|
||||
|
||||
test.describe("Sign Up Flow", () => {
|
||||
test("should register a new user successfully", { tag: ['@critical', '@e2e', '@signup', '@SIGNUP-E2E-001'] }, async ({ page }) => {
|
||||
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: "Thisisapassword123@",
|
||||
confirmPassword: "Thisisapassword123@",
|
||||
acceptTerms: true,
|
||||
});
|
||||
|
||||
// Verify no errors occurred during sign-up
|
||||
await signUpPage.verifyNoErrors();
|
||||
|
||||
// Verify redirect to login page
|
||||
await signUpPage.verifyRedirectToLogin();
|
||||
|
||||
// Verify the newly created user can log in successfully
|
||||
const signInPage = new SignInPage(page);
|
||||
await signInPage.login({
|
||||
email: uniqueEmail,
|
||||
password: "Thisisapassword123@",
|
||||
});
|
||||
await signInPage.verifySuccessfulLogin();
|
||||
});
|
||||
|
||||
test("should complete Github OAuth flow for social sign-up", { tag: ['@critical', '@e2e', '@signup', '@social', '@SIGNUP-E2E-002'] }, async ({ page }) => {
|
||||
// Verify Github credentials are available
|
||||
const githubUsername = process.env.E2E_GITHUB_USER;
|
||||
const githubPassword = process.env.E2E_GITHUB_PASSWORD;
|
||||
|
||||
if (!githubUsername || !githubPassword) {
|
||||
throw new Error('E2E_GITHUB_USER and E2E_GITHUB_PASSWORD environment variables are required for Github OAuth tests');
|
||||
}
|
||||
|
||||
const signUpPage = new SignUpPage(page);
|
||||
await signUpPage.goto();
|
||||
|
||||
// Verify page loaded correctly
|
||||
await signUpPage.verifyPageLoaded();
|
||||
|
||||
// Verify Github social login button is visible and enabled
|
||||
await signUpPage.verifyGithubButtonVisible();
|
||||
await signUpPage.verifyGithubButtonEnabled();
|
||||
|
||||
// Click on Github login button
|
||||
await signUpPage.clickGithubLogin();
|
||||
|
||||
// Verify redirect to Github OAuth
|
||||
await signUpPage.verifyRedirectToGithubOAuth();
|
||||
|
||||
// Verify Github OAuth page loaded correctly
|
||||
await signUpPage.verifyGithubOAuthFlow();
|
||||
|
||||
|
||||
// Verify GitHub displays correct application information
|
||||
await signUpPage.verifyGithubApplicationInfo();
|
||||
|
||||
// Complete Github OAuth login
|
||||
await signUpPage.completeGithubOAuth(githubUsername, githubPassword);
|
||||
|
||||
// Verify the user is redirected to the home page after successful authentication
|
||||
const signInPage = new SignInPage(page);
|
||||
await signInPage.verifySuccessfulLogin();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user