test(ui): add Kubernetes provider management E2E tests

- Introduced support for adding Kubernetes providers with kubeconfig content in the UI.
- Updated the ProvidersPage interface to include fields and methods for Kubernetes provider details and credentials.
- Enhanced E2E tests to validate the complete flow of adding a new Kubernetes provider, ensuring proper handling of context and kubeconfig content.
- Included necessary environment variable checks for Kubernetes context and kubeconfig path to maintain test integrity.
This commit is contained in:
StylusFrost
2025-10-21 10:56:37 +02:00
parent 7b321c8cd1
commit 71f5ac5165
4 changed files with 229 additions and 18 deletions
+2
View File
@@ -33,6 +33,8 @@ jobs:
E2E_M365_SECRET_ID: ${{ secrets.E2E_M365_SECRET_ID }}
E2E_M365_TENANT_ID: ${{ secrets.E2E_M365_TENANT_ID }}
E2E_M365_CERTIFICATE_CONTENT: ${{ secrets.E2E_M365_CERTIFICATE_CONTENT }}
E2E_KUBERNETES_CONTEXT: ${{ secrets.E2E_KUBERNETES_CONTEXT }}
E2E_KUBERNETES_KUBECONFIG_PATH: ${{ secrets.E2E_KUBERNETES_KUBECONFIG_PATH }}
steps:
- name: Checkout repository
+74 -18
View File
@@ -19,6 +19,12 @@ export interface M365ProviderData {
alias?: string;
}
// Kubernetes provider data
export interface KubernetesProviderData {
context: string;
alias?: string;
}
// AWS credential options
export const AWS_CREDENTIAL_OPTIONS = {
AWS_ROLE_ARN: "role",
@@ -71,6 +77,19 @@ export interface M365ProviderCredential {
certificateContent?:string;
}
// Kubernetes credential options
export const KUBERNETES_CREDENTIAL_OPTIONS = {
KUBECONFIG_CONTENT: "kubeconfig"
} as const;
// Kubernetes credential type
type KubernetesCredentialType = (typeof KUBERNETES_CREDENTIAL_OPTIONS)[keyof typeof KUBERNETES_CREDENTIAL_OPTIONS];
// Kubernetes provider credential
export interface KubernetesProviderCredential {
type: KubernetesCredentialType;
kubeconfigContent:string;
}
// Providers page
export class ProvidersPage extends BasePage {
@@ -124,6 +143,10 @@ export class ProvidersPage extends BasePage {
readonly m365TenantIdInput: Locator;
readonly m365CertificateContentInput: Locator;
// Kubernetes provider form elements
readonly kubernetesContextInput: Locator;
readonly kubernetesKubeconfigContentInput: Locator;
// Delete button
readonly deleteProviderConfirmationButton: Locator;
@@ -131,9 +154,7 @@ export class ProvidersPage extends BasePage {
super(page);
// Button to add a new cloud provider
this.addProviderButton = page.getByText("Add Cloud Provider", {
exact: true,
});
this.addProviderButton = page.getByRole("button", { name: "Add Cloud Provider", exact: true });
// Table displaying existing providers
this.providersTable = page.getByRole("table");
@@ -157,23 +178,27 @@ export class ProvidersPage extends BasePage {
this.githubProviderRadio = page.getByRole("radio", { name: /GitHub/i });
// AWS provider form inputs
this.accountIdInput = page.getByLabel("Account ID");
this.accountIdInput = page.getByRole("textbox", { name: "Account ID" });
// AZURE provider form inputs
this.azureSubscriptionIdInput = page.getByLabel("Subscription ID");
this.azureClientIdInput = page.getByLabel("Client ID");
this.azureClientSecretInput = page.getByLabel("Client Secret");
this.azureTenantIdInput = page.getByLabel("Tenant ID");
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.getByLabel("Domain ID");
this.m365ClientIdInput = page.getByLabel("Client ID");
this.m365ClientSecretInput = page.getByLabel("Client Secret");
this.m365TenantIdInput = page.getByLabel("Tenant ID");
this.m365CertificateContentInput = page.getByLabel("Certificate Content");
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" });
// Kubernetes provider form inputs
this.kubernetesContextInput = page.getByRole("textbox", { name: "Context" });
this.kubernetesKubeconfigContentInput = page.getByRole("textbox", { name: "Kubeconfig Content" });
// Alias input
this.aliasInput = page.getByLabel("Provider alias (optional)");
this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)" });
// Navigation buttons in the form (next and back)
this.nextButton = page
@@ -208,12 +233,12 @@ export class ProvidersPage extends BasePage {
});
// Inputs for IAM Role credentials
this.roleArnInput = page.getByLabel("Role ARN");
this.externalIdInput = page.getByLabel("External ID");
this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" });
this.externalIdInput = page.getByRole("textbox", { name: "External ID" });
// Inputs for static credentials
this.accessKeyIdInput = page.getByLabel("Access Key ID");
this.secretAccessKeyInput = page.getByLabel("Secret Access Key");
this.accessKeyIdInput = page.getByRole("textbox", { name: "Access Key ID" });
this.secretAccessKeyInput = page.getByRole("textbox", { name: "Secret Access Key" });
// Delete button
this.deleteProviderConfirmationButton = page.locator(
@@ -255,6 +280,14 @@ export class ProvidersPage extends BasePage {
await this.waitForPageLoad();
}
async selectKubernetesProvider(): Promise<void> {
// Select the Kubernetes provider
await this.kubernetesProviderRadio.click({ force: true });
await this.waitForPageLoad();
}
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
// Fill the AWS provider details
@@ -285,6 +318,15 @@ export class ProvidersPage extends BasePage {
}
}
async fillKubernetesProviderDetails(data: KubernetesProviderData): Promise<void> {
// Fill the Kubernetes provider details
await this.kubernetesContextInput.fill(data.context);
if (data.alias) {
await this.aliasInput.fill(data.alias);
}
}
async clickNext(): Promise<void> {
// 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.
@@ -458,6 +500,14 @@ export class ProvidersPage extends BasePage {
}
}
async fillKubernetesCredentials(credentials: KubernetesProviderCredential): Promise<void> {
// Fill the Kubernetes credentials form
if (credentials.kubeconfigContent) {
await this.kubernetesKubeconfigContentInput.fill(credentials.kubeconfigContent);
}
}
async verifyPageLoaded(): Promise<void> {
// Verify the providers page is loaded
@@ -489,6 +539,12 @@ export class ProvidersPage extends BasePage {
await expect(this.m365CertificateCredentialsRadio).toBeVisible();
}
async verifyKubernetesCredentialsPageLoaded(): Promise<void> {
// Verify the Kubernetes credentials page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.kubernetesContextInput).toBeVisible();
}
async verifyLaunchScanPageLoaded(): Promise<void> {
+58
View File
@@ -265,3 +265,61 @@
- Provider cleanup performed before each test to ensure clean state
- Requires valid Microsoft 365 tenant with certificate-based authentication
- Certificate must be properly configured and have sufficient permissions for security scanning
---
## Test Case: `PROVIDER-E2E-006` - Add Kubernetes Provider with Kubeconfig Content
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @kubernetes
**Description/Objective:** Validates the complete flow of adding a new Kubernetes provider using kubeconfig content authentication
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_KUBERNETES_CONTEXT, E2E_KUBERNETES_KUBECONFIG_PATH
- Kubeconfig file must exist at the specified path
- Remove any existing provider with the same Context before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Context not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select Kubernetes provider type
4. Fill provider details (context and alias)
5. Verify credentials page is loaded
6. Fill Kubernetes credentials (kubeconfig content)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- Kubernetes provider successfully added with kubeconfig content
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays Kubernetes option
- Provider details form accepts context and alias
- Credentials page loads with kubeconfig content field
- Kubeconfig content 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 Kubernetes context and kubeconfig file path
- Kubeconfig content is read from file and used for authentication
- Provider cleanup performed before each test to ensure clean state
- 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)
+95
View File
@@ -11,7 +11,11 @@ import {
M365ProviderData,
M365ProviderCredential,
M365_CREDENTIAL_OPTIONS,
KubernetesProviderData,
KubernetesProviderCredential,
KUBERNETES_CREDENTIAL_OPTIONS,
} from "./providers-page";
import fs from "fs";
test.describe.serial("AddProvider", () => {
test.describe("Add AWS Provider", () => {
@@ -423,4 +427,95 @@ test.describe.serial("AddProvider", () => {
},
);
});
test.describe("Add Kubernetes Provider", () => {
// Providers page object
let providersPage: ProvidersPage;
// Test data from environment variables
const context = process.env.E2E_KUBERNETES_CONTEXT;
const kubeconfigPath = process.env.E2E_KUBERNETES_KUBECONFIG_PATH;
// Validate required environment variables
if (!context || !kubeconfigPath) {
throw new Error(
"E2E_KUBERNETES_CONTEXT and E2E_KUBERNETES_KUBECONFIG_PATH environment variables are not set",
);
}
// Read kubeconfig content from file
const kubeconfigContent = fs.readFileSync(kubeconfigPath, 'utf8');
// Verify kubeconfig file exists
if (!fs.existsSync(kubeconfigPath)) {
throw new Error(`Kubeconfig file not found at ${kubeconfigPath}`);
}
// Setup before each test
test.beforeEach(async ({ page }) => {
providersPage = new ProvidersPage(page);
// Clean up existing provider to ensure clean test state
await helpers.deleteProviderIfExists(page, context);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new Kubernetes provider with kubeconfig context",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@kubernetes",
"@serial",
"@PROVIDER-E2E-006",
],
},
async ({ page }) => {
// Prepare test data for Kubernetes provider
const kubernetesProviderData: KubernetesProviderData = {
context: context,
alias: "Test E2E Kubernetes Account - Kubeconfig Context",
};
// Prepare static credentials
const kubernetesCredentials: KubernetesProviderCredential = {
type: KUBERNETES_CREDENTIAL_OPTIONS.KUBECONFIG_CONTENT,
kubeconfigContent: kubeconfigContent,
};
// Navigate to providers page
await providersPage.goto();
await providersPage.verifyPageLoaded();
// Start adding new provider
await providersPage.clickAddProvider();
await providersPage.verifyConnectAccountPageLoaded();
// Select Kubernetes provider
await providersPage.selectKubernetesProvider();
// Fill provider details
await providersPage.fillKubernetesProviderDetails(kubernetesProviderData);
await providersPage.clickNext();
// Verify credentials page is loaded
await providersPage.verifyKubernetesCredentialsPageLoaded();
// Fill static credentials details
await providersPage.fillKubernetesCredentials(kubernetesCredentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to provider page
await providersPage.verifyLoadProviderPageAfterNewProvider();
},
);
});
});