diff --git a/ui/tests/providers/providers-page.ts b/ui/tests/providers/providers-page.ts index c2edc25304..42616a4319 100644 --- a/ui/tests/providers/providers-page.ts +++ b/ui/tests/providers/providers-page.ts @@ -25,6 +25,12 @@ export interface KubernetesProviderData { alias?: string; } +// GCP provider data +export interface GCPProviderData { + projectId: string; + alias?: string; +} + // AWS credential options export const AWS_CREDENTIAL_OPTIONS = { AWS_ROLE_ARN: "role", @@ -91,6 +97,20 @@ export interface KubernetesProviderCredential { kubeconfigContent:string; } +// GCP credential options +export const GCP_CREDENTIAL_OPTIONS = { + GCP_SERVICE_ACCOUNT: "service_account" +} as const; + +// GCP credential type +type GCPCredentialType = (typeof GCP_CREDENTIAL_OPTIONS)[keyof typeof GCP_CREDENTIAL_OPTIONS]; + +// GCP provider credential +export interface GCPProviderCredential { + type: GCPCredentialType; + serviceAccountKey:string; +} + // Providers page export class ProvidersPage extends BasePage { @@ -147,6 +167,11 @@ export class ProvidersPage extends BasePage { readonly kubernetesContextInput: Locator; readonly kubernetesKubeconfigContentInput: Locator; + // GCP provider form elements + readonly gcpProjectIdInput: Locator; + readonly gcpServiceAccountKeyInput: Locator; + readonly gcpServiceAccountRadio: Locator; + // Delete button readonly deleteProviderConfirmationButton: Locator; @@ -197,6 +222,10 @@ export class ProvidersPage extends BasePage { this.kubernetesContextInput = page.getByRole("textbox", { name: "Context" }); this.kubernetesKubeconfigContentInput = page.getByRole("textbox", { name: "Kubeconfig Content" }); + // GCP provider form inputs + this.gcpProjectIdInput = page.getByRole("textbox", { name: "Project ID" }); + this.gcpServiceAccountKeyInput = page.getByRole("textbox", { name: "Service Account Key" }); + // Alias input this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)" }); @@ -231,6 +260,11 @@ export class ProvidersPage extends BasePage { name: /App Certificate Credentials/i, }); + // Radios for selecting GCP credentials method + this.gcpServiceAccountRadio = page.getByRole("radio", { + name: /Service Account Key/i, + }); + // Inputs for IAM Role credentials this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" }); this.externalIdInput = page.getByRole("textbox", { name: "External ID" }); @@ -287,6 +321,13 @@ export class ProvidersPage extends BasePage { await this.waitForPageLoad(); } + async selectGCPProvider(): Promise { + // Select the GCP provider + + await this.gcpProviderRadio.click({ force: true }); + await this.waitForPageLoad(); + } + async fillAWSProviderDetails(data: AWSProviderData): Promise { // Fill the AWS provider details @@ -327,6 +368,15 @@ export class ProvidersPage extends BasePage { } } + async fillGCPProviderDetails(data: GCPProviderData): Promise { + // Fill the GCP provider details + + await this.gcpProjectIdInput.fill(data.projectId); + 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. @@ -464,6 +514,19 @@ export class ProvidersPage extends BasePage { await this.waitForPageLoad(); } + async selectGCPCredentialsType(type: GCPCredentialType): Promise { + // Ensure we are on the add-credentials page where the selector exists + + await expect(this.page).toHaveURL(/\/providers\/add-credentials/); + if (type === GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT) { + await this.gcpServiceAccountRadio.click({ force: true }); + } else { + throw new Error(`Invalid GCP credential type: ${type}`); + } + // Wait for the page to load + await this.waitForPageLoad(); + } + async fillRoleCredentials(credentials: AWSProviderCredential): Promise { // Fill the role credentials form @@ -545,6 +608,13 @@ export class ProvidersPage extends BasePage { } } + async fillGCPServiceAccountKeyCredentials(credentials: GCPProviderCredential): Promise { + // Fill the GCP credentials form + + if (credentials.serviceAccountKey) { + await this.gcpServiceAccountKeyInput.fill(credentials.serviceAccountKey); + } + } async verifyPageLoaded(): Promise { // Verify the providers page is loaded @@ -593,6 +663,13 @@ export class ProvidersPage extends BasePage { await expect(this.kubernetesContextInput).toBeVisible(); } + async verifyGCPServiceAccountPageLoaded(): Promise { + // Verify the GCP service account page is loaded + + await expect(this.page).toHaveTitle(/Prowler/); + await expect(this.gcpServiceAccountKeyInput).toBeVisible(); + } + async verifyLaunchScanPageLoaded(): Promise { // Verify the launch scan page is loaded diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md index 0b7229d659..8e2cfc598b 100644 --- a/ui/tests/providers/providers.md +++ b/ui/tests/providers/providers.md @@ -323,3 +323,60 @@ - Requires valid Kubernetes cluster with accessible kubeconfig - Kubeconfig must have sufficient permissions for security scanning - Test validates that kubeconfig content goes to the correct field (not the context field) + +--- + +## Test Case: `PROVIDER-E2E-007` - Add GCP Provider with Service Account Key + +**Priority:** `critical` + +**Tags:** + +- type → @e2e, @serial +- feature → @providers +- provider → @gcp + +**Description/Objective:** Validates the complete flow of adding a new GCP provider using service account key authentication + +**Preconditions:** + +- Admin user authentication required (admin.auth.setup setup) +- Environment variables configured: E2E_GCP_PROJECT_ID, E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY +- Remove any existing provider with the same Project ID before starting the test +- This test must be run serially and never in parallel with other tests, as it requires the Project ID not to be already registered beforehand. + +### Flow Steps: + +1. Navigate to providers page +2. Click "Add Provider" button +3. Select GCP provider type +4. Fill provider details (project ID and alias) +5. Select service account credentials type +6. Fill GCP service account key credentials +7. Launch initial scan +8. Verify redirect to provider management page + +### Expected Result: + +- GCP provider successfully added with service account key +- Initial scan launched successfully +- User redirected to provider details page + +### Key verification points: + +- Provider page loads correctly +- Connect account page displays GCP option +- Provider details form accepts project ID and alias +- Service account credentials page loads with service account key field +- Service account key is properly filled in the correct field +- Launch scan page appears +- Successful redirect to provider page after scan launch + +### Notes: + +- Test uses environment variables for GCP project ID and service account key +- Service account key is provided as base64 encoded JSON content +- Provider cleanup performed before each test to ensure clean state +- Requires valid GCP project with service account having appropriate permissions +- Service account must have sufficient permissions for security scanning +- Test validates that service account key goes to the correct field diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts index 976d14f0e2..ca363f7514 100644 --- a/ui/tests/providers/providers.spec.ts +++ b/ui/tests/providers/providers.spec.ts @@ -14,6 +14,9 @@ import { KubernetesProviderData, KubernetesProviderCredential, KUBERNETES_CREDENTIAL_OPTIONS, + GCPProviderData, + GCPProviderCredential, + GCP_CREDENTIAL_OPTIONS, } from "./providers-page"; import { ScansPage } from "../scans/scans-page"; import fs from "fs"; @@ -541,4 +544,108 @@ test.describe("Add Provider", () => { }, ); }); + + test.describe.serial("Add GCP Provider", () => { + // Providers page object + let providersPage: ProvidersPage; + let scansPage: ScansPage; + + // Test data from environment variables + const projectId = process.env.E2E_GCP_PROJECT_ID; + + // Validate required environment variables + if (!projectId ) { + throw new Error( + "E2E_GCP_PROJECT_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(projectId); + }); + + // Use admin authentication for provider management + test.use({ storageState: "playwright/.auth/admin_user.json" }); + + test( + "should add a new GCP provider with service account key", + { + tag: [ + "@critical", + "@e2e", + "@providers", + "@gcp", + "@serial", + "@PROVIDER-E2E-007", + ], + }, + async ({ page }) => { + // Validate required environment variables + const serviceAccountKeyB64 = process.env.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY; + + // Verify service account key is base64 encoded + if (!serviceAccountKeyB64) { + throw new Error("E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY environment variable is not set"); + } + + // Decode service account key from base64 + const serviceAccountKey = Buffer.from(serviceAccountKeyB64, 'base64').toString('utf8'); + + // Verify service account key is valid JSON + if (!JSON.parse(serviceAccountKey)) { + throw new Error("Invalid service account key format"); + } + + // Prepare test data for GCP provider + const gcpProviderData: GCPProviderData = { + projectId: projectId, + alias: "Test E2E GCP Account - Service Account Key", + }; + + // Prepare static credentials + const gcpCredentials: GCPProviderCredential = { + type: GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT, + serviceAccountKey: serviceAccountKey + }; + + // 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.selectGCPProvider(); + + // Fill provider details + await providersPage.fillGCPProviderDetails(gcpProviderData); + await providersPage.clickNext(); + + // Select static credentials type + await providersPage.selectGCPCredentialsType( + GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT, + ); + + // Verify GCP service account page is loaded + await providersPage.verifyGCPServiceAccountPageLoaded(); + + // Fill static service account key details + await providersPage.fillGCPServiceAccountKeyCredentials(gcpCredentials); + await providersPage.clickNext(); + + // Launch scan + await providersPage.verifyLaunchScanPageLoaded(); + await providersPage.clickNext(); + + // Wait for redirect to scan page + scansPage = new ScansPage(page); + await scansPage.verifyPageLoaded(); + }, + ); + }); });