diff --git a/ui/tests/helpers.ts b/ui/tests/helpers.ts index fbe151d17d..2bc9aa4672 100644 --- a/ui/tests/helpers.ts +++ b/ui/tests/helpers.ts @@ -188,10 +188,3 @@ export async function verifySessionValid(page: Page) { expect(session.refreshToken).toBeTruthy(); return session; } - -export async function deleteProviderIfExists(page: Page, accountId: string) { - const providersPage = new ProvidersPage(page); - if (await providersPage.verifyProviderExists(accountId)) { - await providersPage.actionDeleteProvider(accountId); - } -} diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts index 488b926c25..ed22ad5cd3 100644 --- a/ui/tests/providers/providers-page.ts +++ b/ui/tests/providers/providers-page.ts @@ -1,629 +1,439 @@ -import { Page, Locator, expect } from "@playwright/test"; -import { BasePage } from "../base-page"; +import { test } from "@playwright/test"; +import * as helpers from "../helpers"; +import { + ProvidersPage, + AWSProviderData, + AWSProviderCredential, + AWS_CREDENTIAL_OPTIONS, + AZUREProviderData, + AZUREProviderCredential, + AZURE_CREDENTIAL_OPTIONS, + M365ProviderData, + M365ProviderCredential, + M365_CREDENTIAL_OPTIONS, +} from "./providers-page"; +import { ScansPage } from "../scans/scans-page"; +import fs from "fs"; -// AWS provider data -export interface AWSProviderData { - accountId: string; - alias?: string; -} +test.describe("Add Provider", () => { + 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; -// AZURE provider data -export interface AZUREProviderData { - subscriptionId: string; - alias?: string; -} + // Validate required environment variables + if (!accountId) { + throw new Error( + "E2E_AWS_PROVIDER_ACCOUNT_ID environment variable is not set", + ); + } -// M365 provider data -export interface M365ProviderData { - domainId: string; - alias?: 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; -} - -// AZURE credential options -export const AZURE_CREDENTIAL_OPTIONS = { - AZURE_CREDENTIALS: "credentials" -} as const; - -// AZURE credential type -type AZURECredentialType = (typeof AZURE_CREDENTIAL_OPTIONS)[keyof typeof AZURE_CREDENTIAL_OPTIONS]; - -// AZURE provider credential -export interface AZUREProviderCredential { - type: AZURECredentialType; - clientId:string; - clientSecret:string; - tenantId:string; -} - -// M365 credential options -export const M365_CREDENTIAL_OPTIONS = { - M365_CREDENTIALS: "credentials", - M365_CERTIFICATE_CREDENTIALS: "certificate" -} as const; - -// M365 credential type -type M365CredentialType = (typeof M365_CREDENTIAL_OPTIONS)[keyof typeof M365_CREDENTIAL_OPTIONS]; - -// M365 provider credential -export interface M365ProviderCredential { - type: M365CredentialType; - clientId:string; - clientSecret?:string; - tenantId:string; - certificateContent?: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; - - // M365 credentials type selection - readonly m365StaticCredentialsRadio: Locator; - readonly m365CertificateCredentialsRadio: Locator; - - // AWS role credentials form - readonly roleArnInput: Locator; - readonly externalIdInput: Locator; - - // AWS static credentials form - readonly accessKeyIdInput: Locator; - readonly secretAccessKeyInput: Locator; - - // AZURE provider form elements - readonly azureSubscriptionIdInput: Locator; - readonly azureClientIdInput: Locator; - readonly azureClientSecretInput: Locator; - readonly azureTenantIdInput: Locator; - - // M365 provider form elements - readonly m365domainIdInput: Locator; - readonly m365ClientIdInput: Locator; - readonly m365ClientSecretInput: Locator; - readonly m365TenantIdInput: Locator; - readonly m365CertificateContentInput: 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" }); - - // AZURE provider form inputs - this.azureSubscriptionIdInput = page.getByRole("textbox", { name: "Subscription ID" }); - this.azureClientIdInput = page.getByRole("textbox", { name: "Client ID" }); - this.azureClientSecretInput = page.getByRole("textbox", { name: "Client Secret" }); - this.azureTenantIdInput = page.getByRole("textbox", { name: "Tenant ID" }); - - // M365 provider form inputs - this.m365domainIdInput = page.getByRole("textbox", { name: "Domain ID" }); - this.m365ClientIdInput = page.getByRole("textbox", { name: "Client ID" }); - this.m365ClientSecretInput = page.getByRole("textbox", { name: "Client Secret" }); - this.m365TenantIdInput = page.getByRole("textbox", { name: "Tenant ID" }); - this.m365CertificateContentInput = page.getByRole("textbox", { name: "Certificate Content" }); - - // Alias input - 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, + // 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); }); - // 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, - }); - - // Radios for selecting M365 credentials method - this.m365StaticCredentialsRadio = page.getByRole("radio", { - name: /App Client Secret Credentials/i, - }); - this.m365CertificateCredentialsRadio = page.getByRole("radio", { - name: /App Certificate 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 - this.deleteProviderConfirmationButton = page.locator( - 'button[aria-label="Delete"]', - ); - } - - 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 selectAZUREProvider(): Promise { - - // Prefer label-based click for radios, force if overlay intercepts - await this.azureProviderRadio.click({ force: true }); - await this.waitForPageLoad(); - } - - async selectM365Provider(): Promise { - // Select the M365 provider - - await this.m365ProviderRadio.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 fillAZUREProviderDetails(data: AZUREProviderData): Promise { - // Fill the AWS provider details - - await this.azureSubscriptionIdInput.fill(data.subscriptionId); - - if (data.alias) { - await this.aliasInput.fill(data.alias); - } - } - - async fillM365ProviderDetails(data: M365ProviderData): Promise { - // Fill the M365 provider details - - await this.m365domainIdInput.fill(data.domainId); - - 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(); - 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 selectM365CredentialsType(type: M365CredentialType): Promise { - // Ensure we are on the add-credentials page where the selector exists - - await expect(this.page).toHaveURL(/\/providers\/add-credentials/); - if (type === M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS) { - await this.m365StaticCredentialsRadio.click({ force: true }); - } else if (type === M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS) { - await this.m365CertificateCredentialsRadio.click({ force: true }); - } else { - throw new Error(`Invalid M365 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 fillAZURECredentials(credentials: AZUREProviderCredential): Promise { - // Fill the azure credentials form - - if (credentials.clientId) { - await this.azureClientIdInput.fill(credentials.clientId); - } - if (credentials.clientSecret) { - await this.azureClientSecretInput.fill(credentials.clientSecret); - } - if (credentials.tenantId) { - await this.azureTenantIdInput.fill(credentials.tenantId); - } - } - - async fillM365Credentials(credentials: M365ProviderCredential): Promise { - // Fill the m365 credentials form - - if (credentials.clientId) { - await this.m365ClientIdInput.fill(credentials.clientId); - } - if (credentials.clientSecret) { - await this.m365ClientSecretInput.fill(credentials.clientSecret); - } - if (credentials.tenantId) { - await this.m365TenantIdInput.fill(credentials.tenantId); - } - } - - async fillM365CertificateCredentials(credentials: M365ProviderCredential): Promise { - // Fill the m365 certificate credentials form - - if (credentials.clientId) { - await this.m365ClientIdInput.fill(credentials.clientId); - } - if (credentials.certificateContent) { - await this.m365CertificateContentInput.fill(credentials.certificateContent); - } - if (credentials.tenantId) { - await this.m365TenantIdInput.fill(credentials.tenantId); - } - } - - - 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 verifyM365CredentialsPageLoaded(): Promise { - // Verify the M365 credentials page is loaded - - await expect(this.page).toHaveTitle(/Prowler/); - await expect(this.m365StaticCredentialsRadio).toBeVisible(); - await expect(this.m365CertificateCredentialsRadio).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 verifyProviderExists(providerUID: string): Promise { - // Filter search by providerUID - - await this.filterProvidersByProviderUID(providerUID); - // Verify if table has 1 row - if (!(await this.verifySingleRowForProviderUID(providerUID))) { - return false; - } - return true; - } - - async verifyLoadProviderPageAfterNewProvider(): Promise { - // Verify the provider page is loaded - - 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 filterProvidersByProviderUID(providerUID: string): Promise { - // Filter providers by providerUID - - // Go to the providers page - await this.goto(); - - // Verify the providers table is visible - await expect(this.providersTable).toBeVisible(); - - // Get the search input - const searchInput = this.page.getByPlaceholder(/search|filter/i); - if (!searchInput) { - throw new Error("No search input available"); - } - - // Clear existing content and type the providerUID - await searchInput.fill(""); - await searchInput.type(providerUID, { delay: 20 }); - - // Try to submit the filter if the input acts on Enter - try { - await searchInput.press("Enter"); - } catch (error) { - // Enter press might not be needed for all UIs - } - - // Wait for table to update - await this.waitForPageLoad(); - } - - async actionDeleteProvider(providerUID: string): Promise { - // Delete the provider - - // Filter search by providerUID - await this.filterProvidersByProviderUID(providerUID); - - // Verify the providers table is visible - await expect(this.providersTable).toBeVisible(); - - // Find the first row in the filtered results - const firstRow = this.providersTable.locator("tbody tr").first(); - - // Verify the first row is visible - await expect(firstRow).toBeVisible(); - - // Get button in the last cell - const lastCell = firstRow.locator("td").last(); - const actionButton = lastCell.locator("button").first(); - - // Verify the action button is visible - await actionButton.click(); - - // Wait for the dropdown menu to appear - await this.page.waitForSelector('[role="menu"], [role="menuitem"]', { - timeout: 5000, - }); - - // Find the delete menu item - const deleteMenuItem = this.page.getByRole("menuitem", { - name: /delete.*provider/i, - }); - - // Verify the delete menu item is visible - await expect(deleteMenuItem).toBeVisible(); - - // Click the delete menu item with force to handle unstable elements - await deleteMenuItem.click({ force: true }); - - // Wait for the delete confirmation modal to appear - await this.page.waitForSelector( - '[role="dialog"], .modal, [data-testid*="modal"]', - { timeout: 10000 }, + // 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(); + }, ); - // Find the delete confirmation button with multiple approaches - let deleteButton = this.deleteProviderConfirmationButton; + 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", + ); + } - await expect(deleteButton).toBeVisible(); + // Prepare test data for AWS provider + const awsProviderData: AWSProviderData = { + accountId: accountId, + alias: "Test E2E AWS Account - Credentials", + }; - // Click the delete button with force to handle unstable elements - await deleteButton.click({ force: true }); + // Prepare role-based credentials + const roleCredentials: AWSProviderCredential = { + type: AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN, + accessKeyId: accessKey, + secretAccessKey: secretKey, + roleArn: roleArn, + }; - // Wait for the modal to disappear and the page to update - const modalToCheck = this.page - .locator('[role="dialog"], .modal, [data-testid*="modal"]') - .first(); + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); - // Verify the modal is not visible - await expect(modalToCheck).not.toBeVisible(); - await this.waitForPageLoad(); - } -} + // 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(); + }, + ); + }); + + test.describe.serial("Add AZURE Provider", () => { + // Providers page object + let providersPage: ProvidersPage; + let scansPage: ScansPage; + + // Test data from environment variables + const subscriptionId = process.env.E2E_AZURE_SUBSCRIPTION_ID; + const clientId = process.env.E2E_AZURE_CLIENT_ID; + const clientSecret = process.env.E2E_AZURE_SECRET_ID; + const tenantId = process.env.E2E_AZURE_TENANT_ID; + + // Validate required environment variables + if (!subscriptionId || !clientId || !clientSecret || !tenantId) { + throw new Error( + "E2E_AZURE_SUBSCRIPTION_ID, E2E_AZURE_CLIENT_ID, E2E_AZURE_SECRET_ID, and E2E_AZURE_TENANT_ID environment variables are 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(subscriptionId); + }); + + // Use admin authentication for provider management + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new Azure provider with static credentials", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@azure", + "@serial", + "@PROVIDER-E2E-003", + ], + }, + async ({ page }) => { + // Prepare test data for AZURE provider + const azureProviderData: AZUREProviderData = { + subscriptionId: subscriptionId, + alias: "Test E2E AZURE Account - Credentials", + }; + + // Prepare static credentials + const azureCredentials: AZUREProviderCredential = { + type: AZURE_CREDENTIAL_OPTIONS.AZURE_CREDENTIALS, + clientId: clientId, + clientSecret: clientSecret, + tenantId: tenantId, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select AZURE provider + await providersPage.selectAZUREProvider(); + + // Fill provider details + await providersPage.fillAZUREProviderDetails(azureProviderData); + await providersPage.clickNext(); + + // Fill static credentials details + await providersPage.fillAZURECredentials(azureCredentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + }); + + test.describe.serial("Add M365 Provider", () => { + // Providers page object + let providersPage: ProvidersPage; + let scansPage: ScansPage; + + // Test data from environment variables + const domainId = process.env.E2E_M365_DOMAIN_ID; + const clientId = process.env.E2E_M365_CLIENT_ID; + const tenantId = process.env.E2E_M365_TENANT_ID; + + // Validate required environment variables + if (!domainId || !clientId || !tenantId) { + throw new Error( + "E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, and E2E_M365_TENANT_ID environment variables are 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(domainId); + }); + + // Use admin authentication for provider management + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new M365 provider with static credentials", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@m365", + "@serial", + "@PROVIDER-E2E-004", + ], + }, + async ({ page }) => { + // Validate required environment variables + const clientSecret = process.env.E2E_M365_SECRET_ID; + + if (!clientSecret) { + throw new Error("E2E_M365_SECRET_ID environment variable is not set"); + } + // Prepare test data for M365 provider + const m365ProviderData: M365ProviderData = { + domainId: domainId, + alias: "Test E2E M365 Account - Credentials", + }; + + // Prepare static credentials + const m365Credentials: M365ProviderCredential = { + type: M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS, + clientId: clientId, + clientSecret: clientSecret, + tenantId: tenantId, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select M365 provider + await providersPage.selectM365Provider(); + + // Fill provider details + await providersPage.fillM365ProviderDetails(m365ProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectM365CredentialsType( + M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS, + ); + + // Verify M365 credentials page is loaded + await providersPage.verifyM365CredentialsPageLoaded(); + + // Fill static credentials details + await providersPage.fillM365Credentials(m365Credentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + + test( + "should add a new M365 provider with certificate", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@m365", + "@serial", + "@PROVIDER-E2E-005", + ], + }, + async ({ page }) => { + // Validate required environment variables + const certificateContent = process.env.E2E_M365_CERTIFICATE_CONTENT; + + if (!certificateContent) { + throw new Error( + "E2E_M365_CERTIFICATE_CONTENT environment variable is not set", + ); + } + + // Prepare test data for M365 provider + const m365ProviderData: M365ProviderData = { + domainId: domainId, + alias: "Test E2E M365 Account - Certificate", + }; + + // Prepare static credentials + const m365Credentials: M365ProviderCredential = { + type: M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS, + clientId: clientId, + tenantId: tenantId, + certificateContent: certificateContent, + }; + + // Navigate to providers page + await providersPage.goto(); + await providersPage.verifyPageLoaded(); + + // Start adding new provider + await providersPage.clickAddProvider(); + await providersPage.verifyConnectAccountPageLoaded(); + + // Select M365 provider + await providersPage.selectM365Provider(); + + // Fill provider details + await providersPage.fillM365ProviderDetails(m365ProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectM365CredentialsType( + M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS, + ); + + // Verify M365 certificate credentials page is loaded + await providersPage.verifyM365CertificateCredentialsPageLoaded(); + + // Fill static credentials details + await providersPage.fillM365CertificateCredentials(m365Credentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + }); +}); \ No newline at end of file diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts index 95dca55dc9..de12cad506 100644 --- a/ui/tests/providers/providers.spec.ts +++ b/ui/tests/providers/providers.spec.ts @@ -12,12 +12,14 @@ import { M365ProviderCredential, M365_CREDENTIAL_OPTIONS, } from "./providers-page"; +import { ScansPage } from "../scans/scans-page"; +import fs from "fs"; -test.describe.serial("Add Provider", () => { - test.describe("Add AWS Provider", () => { +test.describe("Add Provider", () => { + 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; @@ -35,7 +37,7 @@ test.describe.serial("Add Provider", () => { test.beforeEach(async ({ page }) => { providersPage = new ProvidersPage(page); // Clean up existing provider to ensure clean test state - await helpers.deleteProviderIfExists(page, accountId); + await providersPage.deleteProviderIfExists(accountId); }); // Use admin authentication for provider management @@ -104,7 +106,8 @@ test.describe.serial("Add Provider", () => { await providersPage.clickNext(); // Wait for redirect to provider page - await providersPage.verifyLoadProviderPageAfterNewProvider(); + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); }, ); @@ -172,14 +175,16 @@ test.describe.serial("Add Provider", () => { await providersPage.clickNext(); // Wait for redirect to provider page - await providersPage.verifyLoadProviderPageAfterNewProvider(); + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); }, ); }); - test.describe("Add AZURE Provider", () => { + test.describe.serial("Add AZURE Provider", () => { // Providers page object let providersPage: ProvidersPage; + let scansPage: ScansPage; // Test data from environment variables const subscriptionId = process.env.E2E_AZURE_SUBSCRIPTION_ID; @@ -198,7 +203,7 @@ test.describe.serial("Add Provider", () => { test.beforeEach(async ({ page }) => { providersPage = new ProvidersPage(page); // Clean up existing provider to ensure clean test state - await helpers.deleteProviderIfExists(page, subscriptionId); + await providersPage.deleteProviderIfExists(subscriptionId); }); // Use admin authentication for provider management @@ -254,8 +259,9 @@ test.describe.serial("Add Provider", () => { await providersPage.verifyLaunchScanPageLoaded(); await providersPage.clickNext(); - // Wait for redirect to provider page - await providersPage.verifyLoadProviderPageAfterNewProvider(); + // Wait for redirect to scan 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(); + } +}