diff --git a/.github/workflows/ui-e2e-tests.yml b/.github/workflows/ui-e2e-tests.yml index 81f191765c..ad03d644bb 100644 --- a/.github/workflows/ui-e2e-tests.yml +++ b/.github/workflows/ui-e2e-tests.yml @@ -18,6 +18,12 @@ jobs: AUTH_TRUST_HOST: true NEXTAUTH_URL: 'http://localhost:3000' NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1' + E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }} + E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }} + E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }} + E2E_AWS_PROVIDER_ACCESS_KEY: ${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }} + E2E_AWS_PROVIDER_SECRET_KEY: ${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }} + E2E_AWS_PROVIDER_ROLE_ARN: ${{ secrets.E2E_AWS_PROVIDER_ROLE_ARN }} E2E_NEW_PASSWORD: ${{ secrets.E2E_NEW_PASSWORD }} steps: - name: Checkout repository diff --git a/ui/package.json b/ui/package.json index e043f89ac2..fb46f00eec 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 --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": "playwright test --project=chromium --project=sign-up --project=providers", + "test:e2e:ui": "playwright test --project=chromium --project=sign-up --project=providers --ui", + "test:e2e:debug": "playwright test --project=chromium --project=sign-up --project=providers --debug", + "test:e2e:headed": "playwright test --project=chromium --project=sign-up --project=providers --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 a57952ab98..78f840e125 100644 --- a/ui/playwright.config.ts +++ b/ui/playwright.config.ts @@ -96,6 +96,12 @@ export default defineConfig({ name: "sign-up", testMatch: "sign-up.spec.ts", }, + // This project runs the providers test suite + { + name: "providers", + testMatch: "providers.spec.ts", + dependencies: ["admin.auth.setup"], + }, ], webServer: { diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts new file mode 100644 index 0000000000..096ed35e03 --- /dev/null +++ b/ui/tests/providers/providers-page.ts @@ -0,0 +1,495 @@ +import { Page, Locator, expect } from "@playwright/test"; +import { BasePage } from "../base-page"; + +// AWS provider data +export interface AWSProviderData { + accountId: string; + alias?: string; + roleArn?: string; + externalId?: string; + accessKeyId?: string; + secretAccessKey?: string; +} + +// AWS credential options +export const AWS_CREDENTIAL_OPTIONS = { + AWS_ROLE_ARN: "role", + AWS_CREDENTIALS: "credentials" +} as const; + +// AWS credential type +type AWSCredentialType = (typeof AWS_CREDENTIAL_OPTIONS)[keyof typeof AWS_CREDENTIAL_OPTIONS]; + +// AWS provider credential +export interface AWSProviderCredential { + type: AWSCredentialType; + roleArn?: string; + externalId?: string; + accessKeyId?: string; + secretAccessKey?: string; +} + +// Providers page +export class ProvidersPage extends BasePage { + + // Button to add a new cloud provider + readonly addProviderButton: Locator; + readonly providersTable: Locator; + + // Provider selection elements + readonly awsProviderRadio: Locator; + readonly gcpProviderRadio: Locator; + readonly azureProviderRadio: Locator; + readonly m365ProviderRadio: Locator; + readonly kubernetesProviderRadio: Locator; + readonly githubProviderRadio: Locator; + + // AWS provider form elements + readonly accountIdInput: Locator; + readonly aliasInput: Locator; + readonly nextButton: Locator; + readonly backButton: Locator; + readonly saveButton: Locator; + readonly launchScanButton: Locator; + + // AWS credentials type selection + readonly roleCredentialsRadio: Locator; + readonly staticCredentialsRadio: Locator; + + // AWS role credentials form + readonly roleArnInput: Locator; + readonly externalIdInput: Locator; + + // AWS static credentials form + readonly accessKeyIdInput: Locator; + readonly secretAccessKeyInput: Locator; + + // Delete button + readonly deleteProviderConfirmationButton: Locator; + + constructor(page: Page) { + super(page); + + // Button to add a new cloud provider + this.addProviderButton = page.getByRole("button", { name: "Add Cloud Provider", exact: true }); + + // Table displaying existing providers + this.providersTable = page.getByRole("table"); + + // Radio buttons to select the type of cloud provider + this.awsProviderRadio = page.getByRole("radio", { + name: /Amazon Web Services/i, + }); + this.gcpProviderRadio = page.getByRole("radio", { + name: /Google Cloud Platform/i, + }); + this.azureProviderRadio = page.getByRole("radio", { + name: /Microsoft Azure/i, + }); + this.m365ProviderRadio = page.getByRole("radio", { + name: /Microsoft 365/i, + }); + this.kubernetesProviderRadio = page.getByRole("radio", { + name: /Kubernetes/i, + }); + this.githubProviderRadio = page.getByRole("radio", { name: /GitHub/i }); + + // AWS provider form inputs + this.accountIdInput = page.getByRole("textbox", { name: "Account ID" }); + this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)" }); + + // Navigation buttons in the form (next and back) + this.nextButton = page + .locator("form") + .getByRole("button", { name: "Next", exact: true }); + this.backButton = page.getByRole("button", { name: "Back" }); + + // Button to save the form + this.saveButton = page.getByRole("button", { name: "Save", exact: true }); + + // Button to launch a scan + this.launchScanButton = page.getByRole("button", { + name: "Launch scan", + exact: true, + }); + + // Radios for selecting AWS credentials method + this.roleCredentialsRadio = page.getByRole("radio", { + name: /Connect assuming IAM Role/i, + }); + this.staticCredentialsRadio = page.getByRole("radio", { + name: /Connect via Credentials/i, + }); + + // Inputs for IAM Role credentials + this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" }); + this.externalIdInput = page.getByRole("textbox", { name: "External ID" }); + + // Inputs for static credentials + this.accessKeyIdInput = page.getByRole("textbox", { name: "Access Key ID" }); + this.secretAccessKeyInput = page.getByRole("textbox", { name: "Secret Access Key" }); + + // Delete button in confirmation modal + this.deleteProviderConfirmationButton = page.getByRole("button", { + name: "Delete", + exact: true, + }); + } + + async goto(): Promise { + // Go to the providers page + + await super.goto("/providers"); + } + + async clickAddProvider(): Promise { + // Click the add provider button + + await this.addProviderButton.click(); + await this.waitForPageLoad(); + } + + async selectAWSProvider(): Promise { + // Prefer label-based click for radios, force if overlay intercepts + await this.awsProviderRadio.click({ force: true }); + await this.waitForPageLoad(); + } + + async fillAWSProviderDetails(data: AWSProviderData): Promise { + // Fill the AWS provider details + + await this.accountIdInput.fill(data.accountId); + + if (data.alias) { + await this.aliasInput.fill(data.alias); + } + } + + async clickNext(): Promise { + // The wizard interface may use different labels for its primary action button on each step. + // This function determines which button to click depending on the current URL and page content. + + // Get the current page URL + const url = this.page.url(); + + // If on the "connect-account" step, click the "Next" button + if (/\/providers\/connect-account/.test(url)) { + await this.nextButton.click(); + await this.waitForPageLoad(); + return; + } + + // If on the "add-credentials" step, check for "Save" and "Next" buttons + if (/\/providers\/add-credentials/.test(url)) { + // Some UI implementations use "Save" instead of "Next" for primary action + const saveBtn = this.saveButton; + if (await saveBtn.count()) { + await saveBtn.click(); + await this.waitForPageLoad(); + return; + } + // If "Save" is not present, try clicking the "Next" button + if (await this.nextButton.count()) { + await this.nextButton.click(); + await this.waitForPageLoad(); + return; + } + } + + // If on the "test-connection" step, click the "Launch scan" button + if (/\/providers\/test-connection/.test(url)) { + const buttonByText = this.page + .locator("button") + .filter({ hasText: "Launch scan" }); + + await buttonByText.click(); + await this.waitForPageLoad(); + + // Wait for either success (redirect to scans) or error message to appear + // The error container has multiple p.text-danger elements, we want the first one with the technical error + const errorMessage = this.page.locator("p.text-danger").first(); + + try { + // Wait up to 15 seconds for either the error message or redirect + await Promise.race([ + // Wait for error message to appear + errorMessage.waitFor({ state: "visible", timeout: 15000 }), + // Wait for redirect to scans page (success case) + this.page.waitForURL(/\/scans/, { timeout: 15000 }), + ]); + + // If we're still on test-connection page, check for error + if (/\/providers\/test-connection/.test(this.page.url())) { + const isErrorVisible = await errorMessage.isVisible().catch(() => false); + if (isErrorVisible) { + const errorText = await errorMessage.textContent(); + throw new Error( + `Test connection failed with error: ${errorText?.trim() || "Unknown error"}`, + ); + } + } + } catch (error) { + // If timeout or other error, check if error message is present + const isErrorVisible = await errorMessage.isVisible().catch(() => false); + if (isErrorVisible) { + const errorText = await errorMessage.textContent(); + throw new Error( + `Test connection failed with error: ${errorText?.trim() || "Unknown error"}`, + ); + } + // Re-throw original error if no error message found + throw error; + } + + return; + } + + // Fallback logic: try finding any common primary action buttons in expected order + const candidates = [ + { name: "Next" }, // Try the "Next" button + { name: "Save" }, // Try the "Save" button + { name: "Launch scan" }, // Try the "Launch scan" button + { name: /Continue|Proceed/i }, // Try "Continue" or "Proceed" (case-insensitive) + ] as const; + + // Try each candidate name and click it if found + for (const candidate of candidates) { + // Try each candidate name and click it if found + const btn = this.page.getByRole("button", { + name: candidate.name as any, + }); + + if (await btn.count()) { + await btn.click(); + await this.waitForPageLoad(); + return; + } + } + + // If none of the expected action buttons are present, throw an error + throw new Error( + "Could not find an actionable Next/Save/Launch scan button on this step", + ); + } + + async selectCredentialsType(type: AWSCredentialType): Promise { + // Ensure we are on the add-credentials page where the selector exists + + await expect(this.page).toHaveURL(/\/providers\/add-credentials/); + if (type === AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN) { + await this.roleCredentialsRadio.click({ force: true }); + } else if (type === AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS) { + await this.staticCredentialsRadio.click({ force: true }); + } else { + throw new Error(`Invalid AWS credential type: ${type}`); + } + // Wait for the page to load + await this.waitForPageLoad(); + } + + async fillRoleCredentials(credentials: AWSProviderCredential): Promise { + // Fill the role credentials form + + if (credentials.accessKeyId) { + await this.accessKeyIdInput.fill(credentials.accessKeyId); + } + if (credentials.secretAccessKey) { + await this.secretAccessKeyInput.fill(credentials.secretAccessKey); + } + if (credentials.roleArn) { + await this.roleArnInput.fill(credentials.roleArn); + } + if (credentials.externalId) { + // External ID may be prefilled and disabled; only fill if enabled + if (await this.externalIdInput.isEnabled()) { + await this.externalIdInput.fill(credentials.externalId); + } + } + } + + async fillStaticCredentials(credentials: AWSProviderCredential): Promise { + // Fill the static credentials form + + if (credentials.accessKeyId) { + await this.accessKeyIdInput.fill(credentials.accessKeyId); + } + if (credentials.secretAccessKey) { + await this.secretAccessKeyInput.fill(credentials.secretAccessKey); + } + } + + async verifyPageLoaded(): Promise { + // Verify the providers page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.addProviderButton).toBeVisible(); + await this.page.waitForLoadState('networkidle'); + } + + async verifyConnectAccountPageLoaded(): Promise { + // Verify the connect account page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.awsProviderRadio).toBeVisible(); + } + + async verifyCredentialsPageLoaded(): Promise { + // Verify the credentials page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.roleCredentialsRadio).toBeVisible(); + } + + async verifyLaunchScanPageLoaded(): Promise { + // Verify the launch scan page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.page).toHaveURL(/\/providers\/test-connection/); + + // Verify the Launch scan button is visible + const launchScanButton = this.page + .locator("button") + .filter({ hasText: "Launch scan" }); + await expect(launchScanButton).toBeVisible(); + } + + async verifyLoadProviderPageAfterNewProvider(): Promise { + // Verify the provider page is loaded + + await this.waitForPageLoad(); + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.providersTable).toBeVisible(); + } + + async verifySingleRowForProviderUID(providerUID: string): Promise { + // Verify if table has 1 row and that row contains providerUID + + await expect(this.providersTable).toBeVisible(); + + // Get the matching rows + const matchingRows = this.providersTable.locator("tbody tr", { + hasText: providerUID, + }); + + // Verify the number of matching rows is 1 + const count = await matchingRows.count(); + if (count !== 1) return false; + return true; + } + + async deleteProviderIfExists(providerUID: string): Promise { + // Delete the provider if it exists + + // Navigate to providers page + await this.goto(); + await expect(this.providersTable).toBeVisible({ timeout: 10000 }); + + // Find and use the search input to filter the table + const searchInput = this.page.getByPlaceholder(/search|filter/i); + await expect(searchInput).toBeVisible({ timeout: 5000 }); + + // Clear and search for the specific provider + await searchInput.clear(); + await searchInput.fill(providerUID); + await searchInput.press("Enter"); + + // Wait for the table to finish loading/filtering + await this.waitForPageLoad(); + + // Additional wait for React table to re-render with the server-filtered data + // The filtering happens on the server, but the table component needs time + // to process the response and update the DOM after network idle + await this.page.waitForTimeout(1500); + + // Get all rows from the table + const allRows = this.providersTable.locator("tbody tr"); + + // Helper function to check if a row is the "No results" row + const isNoResultsRow = async (row: Locator): Promise => { + const text = await row.textContent(); + return text?.includes("No results") || text?.includes("No data") || false; + }; + + // Helper function to find the row with the specific UID + const findProviderRow = async (): Promise => { + const count = await allRows.count(); + + for (let i = 0; i < count; i++) { + const row = allRows.nth(i); + + // Skip "No results" rows + if (await isNoResultsRow(row)) { + continue; + } + + // Check if this row contains the UID in the UID column (column 3) + const uidCell = row.locator("td").nth(3); + const uidText = await uidCell.textContent(); + + if (uidText?.includes(providerUID)) { + return row; + } + } + + return null; + }; + + // Wait for filtering to complete (max 0 or 1 data rows) + await expect(async () => { + const targetRow = await findProviderRow(); + const count = await allRows.count(); + + // Count only real data rows (not "No results") + let dataRowCount = 0; + for (let i = 0; i < count; i++) { + if (!(await isNoResultsRow(allRows.nth(i)))) { + dataRowCount++; + } + } + + // Should have 0 or 1 data row + expect(dataRowCount).toBeLessThanOrEqual(1); + }).toPass({ timeout: 20000 }); + + // Find the provider row + const targetRow = await findProviderRow(); + + if (!targetRow) { + // Provider not found, nothing to delete + // Navigate back to providers page to ensure clean state + await this.goto(); + await expect(this.providersTable).toBeVisible({ timeout: 10000 }); + return; + } + + // Find and click the action button (last cell = actions column) + const actionButton = targetRow.locator("td").last().locator("button").first(); + await expect(actionButton).toBeVisible({ timeout: 5000 }); + await actionButton.click(); + + // Wait for dropdown menu to appear and find delete option + const deleteMenuItem = this.page.getByRole("menuitem", { + name: /delete.*provider/i, + }); + await expect(deleteMenuItem).toBeVisible({ timeout: 5000 }); + await deleteMenuItem.click(); + + // Wait for confirmation modal to appear + const modal = this.page.locator('[role="dialog"], .modal, [data-testid*="modal"]').first(); + await expect(modal).toBeVisible({ timeout: 10000 }); + + // Find and click the delete confirmation button + await expect(this.deleteProviderConfirmationButton).toBeVisible({ timeout: 5000 }); + await this.deleteProviderConfirmationButton.click(); + + // Wait for modal to close (this indicates deletion was initiated) + await expect(modal).not.toBeVisible({ timeout: 10000 }); + + // Wait for page to reload + await this.waitForPageLoad(); + + // Navigate back to providers page to ensure clean state + await this.goto(); + await expect(this.providersTable).toBeVisible({ timeout: 10000 }); + } +} diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md new file mode 100644 index 0000000000..3c160e946f --- /dev/null +++ b/ui/tests/providers/providers.md @@ -0,0 +1,109 @@ +### E2E Tests: AWS Provider Management + +**Suite ID:** `PROVIDER-E2E` +**Feature:** AWS Provider Management - Add and configure AWS cloud providers with different authentication methods + +--- + +## Test Case: `PROVIDER-E2E-001` - Add AWS Provider with Static Credentials + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @aws + +**Description/Objective:** Validates the complete flow of adding a new AWS provider using static access key credentials + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY and E2E_AWS_PROVIDER_SECRET_KEY +- Remove any existing provider with the same Account ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Account ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select AWS provider type +4. Fill provider details (account ID and alias) +5. Select "credentials" authentication type +6. Fill static credentials (access key and secret key) +7. Launch initial scan +8. Verify redirect to provider management page + +### Expected Result: + +- AWS provider successfully added with static credentials +- Initial scan launched successfully +- User redirected to provider details page + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays AWS option +- Credentials form accepts static credentials +- Launch scan page appears +- Successful redirect to provider page after scan launch + +### Notes: + +- Test uses environment variables for AWS credentials +- Provider cleanup performed before each test to ensure clean state +- Requires valid AWS account with appropriate permissions + +--- + +## Test Case: `PROVIDER-E2E-002` - Add AWS Provider with Assume Role Credentials Access Key and Secret Key + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @aws + +**Description/Objective:** Validates the complete flow of adding a new AWS provider using role-based authentication with Access Key and Secret Key + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, E2E_AWS_PROVIDER_SECRET_KEY, E2E_AWS_PROVIDER_ROLE_ARN +- Remove any existing provider with the same Account ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Account ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select AWS provider type +4. Fill provider details (account ID and alias) +5. Select "role" authentication type +6. Fill role credentials (access key, secret key, and role ARN) +7. Launch initial scan +8. Verify redirect to provider management page + +### Expected Result: + +- AWS provider successfully added with role credentials +- Initial scan launched successfully +- User redirected to provider details page + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays AWS option +- Role credentials form accepts all required fields +- Launch scan page appears +- Successful redirect to provider page after scan launch + +### Notes: + +- Test uses environment variables for AWS credentials and role ARN +- Provider cleanup performed before each test to ensure clean state +- Requires valid AWS account with role assumption permissions +- Role ARN must be properly configured diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts new file mode 100644 index 0000000000..98c0b14577 --- /dev/null +++ b/ui/tests/providers/providers.spec.ts @@ -0,0 +1,175 @@ +import { test } from "@playwright/test"; +import { ScansPage } from "../scans/scans-page"; +import { + ProvidersPage, + AWSProviderData, + AWSProviderCredential, + AWS_CREDENTIAL_OPTIONS +} from "./providers-page"; + + +test.describe.serial("Add AWS Provider", () => { + // Providers page object + let providersPage: ProvidersPage; + let scansPage: ScansPage; + // Test data from environment variables + const accountId = process.env.E2E_AWS_PROVIDER_ACCOUNT_ID; + const accessKey = process.env.E2E_AWS_PROVIDER_ACCESS_KEY; + const secretKey = process.env.E2E_AWS_PROVIDER_SECRET_KEY; + const roleArn = process.env.E2E_AWS_PROVIDER_ROLE_ARN; + + // Validate required environment variables + if (!accountId) { + throw new Error( + "E2E_AWS_PROVIDER_ACCOUNT_ID environment variable is not set", + ); + } + + // Setup before each test + test.beforeEach(async ({ page }) => { + providersPage = new ProvidersPage(page); + // Clean up existing provider to ensure clean test state + await providersPage.deleteProviderIfExists(accountId); + }); + + // Use admin authentication for provider management + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new AWS provider with static credentials", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@aws", + "@serial", + "@PROVIDER-E2E-001", + ], + }, + async ({ page }) => { + // Validate required environment variables + if (!accountId || !accessKey || !secretKey) { + throw new Error( + "E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, and E2E_AWS_PROVIDER_SECRET_KEY environment variables are not set", + ); + } + + // Prepare test data for AWS provider + const awsProviderData: AWSProviderData = { + accountId: accountId, + alias: "Test E2E AWS Account - Credentials", + }; + + // Prepare static credentials + const staticCredentials: AWSProviderCredential = { + type: AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS, + accessKeyId: accessKey, + secretAccessKey: secretKey, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select AWS provider + await providersPage.selectAWSProvider(); + + // Fill provider details + await providersPage.fillAWSProviderDetails(awsProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectCredentialsType( + AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS, + ); + await providersPage.verifyCredentialsPageLoaded(); + + // Fill static credentials + await providersPage.fillStaticCredentials(staticCredentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to provider page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + + test( + "should add a new AWS provider with assume role credentials with Access Key and Secret Key", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@aws", + "@serial", + "@PROVIDER-E2E-002", + ], + }, + async ({ page }) => { + // Validate required environment variables + if (!accountId || !accessKey || !secretKey || !roleArn) { + throw new Error( + "E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, E2E_AWS_PROVIDER_SECRET_KEY, and E2E_AWS_PROVIDER_ROLE_ARN environment variables are not set", + ); + } + + // Prepare test data for AWS provider + const awsProviderData: AWSProviderData = { + accountId: accountId, + alias: "Test E2E AWS Account - Credentials", + }; + + // Prepare role-based credentials + const roleCredentials: AWSProviderCredential = { + type: AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN, + accessKeyId: accessKey, + secretAccessKey: secretKey, + roleArn: roleArn, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select AWS provider + await providersPage.selectAWSProvider(); + + // Fill provider details + await providersPage.fillAWSProviderDetails(awsProviderData); + await providersPage.clickNext(); + + // Select role credentials type + await providersPage.selectCredentialsType( + AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN, + ); + await providersPage.verifyCredentialsPageLoaded(); + + // Fill role credentials + await providersPage.fillRoleCredentials(roleCredentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to provider page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); +}); + diff --git a/ui/tests/scans/scans-page.ts b/ui/tests/scans/scans-page.ts new file mode 100644 index 0000000000..beb7d3cea9 --- /dev/null +++ b/ui/tests/scans/scans-page.ts @@ -0,0 +1,28 @@ +import { Page, Locator, expect } from "@playwright/test"; +import { BasePage } from "../base-page"; + +// Scan page +export class ScansPage extends BasePage { + + // Main content elements + readonly scanTable: Locator; + constructor(page: Page) { + super(page); + + // Main content elements + this.scanTable = page.locator("table"); + + } + + // Navigation methods + async goto(): Promise { + await super.goto("/scans"); + } + + // Verification methods + async verifyPageLoaded(): Promise { + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.scanTable).toBeVisible(); + await this.waitForPageLoad(); + } +} diff --git a/ui/tests/setups/admin.auth.setup.ts b/ui/tests/setups/admin.auth.setup.ts index 0e24c242c3..64375aff86 100644 --- a/ui/tests/setups/admin.auth.setup.ts +++ b/ui/tests/setups/admin.auth.setup.ts @@ -4,6 +4,7 @@ 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;