mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
68 Commits
d1d03ba421
...
PROWLER-18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a100e9ce1 | ||
|
|
385133b63a | ||
|
|
054f85d7ba | ||
|
|
693a2e9694 | ||
|
|
34c84d1c02 | ||
|
|
0917817028 | ||
|
|
271c7c4e01 | ||
|
|
f02fc07ed3 | ||
|
|
d762a3433a | ||
|
|
cd8b4dd2da | ||
|
|
3665435aa4 | ||
|
|
853f926fa1 | ||
|
|
834cef28a1 | ||
|
|
477de791a9 | ||
|
|
53607597b6 | ||
|
|
4250d4ee07 | ||
|
|
f26083772e | ||
|
|
a2e409d10e | ||
|
|
027ae6cd73 | ||
|
|
bc76a8cf25 | ||
|
|
e0bedb06d7 | ||
|
|
0b372477be | ||
|
|
670d9a8f87 | ||
|
|
f6f25d1191 | ||
|
|
bd1aac2527 | ||
|
|
cb1284e6e7 | ||
|
|
74580291a7 | ||
|
|
83540472e4 | ||
|
|
1cec011d8d | ||
|
|
5af7c950ac | ||
|
|
642efecd37 | ||
|
|
ccd8908128 | ||
|
|
c0a82910ad | ||
|
|
a004a73891 | ||
|
|
fccd2c3b9c | ||
|
|
e8712359c7 | ||
|
|
1ea859f606 | ||
|
|
d13182288e | ||
|
|
85d9411283 | ||
|
|
a500339138 | ||
|
|
4a0f0ba5bb | ||
|
|
5384d30fd5 | ||
|
|
a6121396ca | ||
|
|
d17d519a3e | ||
|
|
71f5ac5165 | ||
|
|
f15fdfc642 | ||
|
|
94d5322f16 | ||
|
|
4920f84d75 | ||
|
|
7b321c8cd1 | ||
|
|
1212356db3 | ||
|
|
13436613d6 | ||
|
|
b23b083092 | ||
|
|
d63ae0e40f | ||
|
|
5d86aacb2a | ||
|
|
0d088eca13 | ||
|
|
055964aff3 | ||
|
|
447d754b49 | ||
|
|
761563472b | ||
|
|
5e3db29de7 | ||
|
|
d8ca60a4ab | ||
|
|
cef7fcc24b | ||
|
|
fcf42937aa | ||
|
|
2c9d8ad8ea | ||
|
|
f424342e7e | ||
|
|
9b7e4f59e1 | ||
|
|
d21222aa3a | ||
|
|
bdbb2fad78 | ||
|
|
cf7b66101c |
24
.github/workflows/ui-e2e-tests.yml
vendored
24
.github/workflows/ui-e2e-tests.yml
vendored
@@ -10,6 +10,7 @@ on:
|
||||
- 'ui/**'
|
||||
|
||||
jobs:
|
||||
|
||||
e2e-tests:
|
||||
if: github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,10 +34,29 @@ 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_NEW_PASSWORD: ${{ secrets.E2E_NEW_PASSWORD }}
|
||||
E2E_KUBERNETES_CONTEXT: 'kind-kind'
|
||||
E2E_KUBERNETES_KUBECONFIG_PATH: /home/runner/.kube/config
|
||||
E2E_NEW_USER_PASSWORD: ${{ secrets.E2E_NEW_USER_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Create k8s Kind Cluster
|
||||
uses: helm/kind-action@v1
|
||||
with:
|
||||
cluster_name: kind
|
||||
- name: Modify kubeconfig
|
||||
run: |
|
||||
# Modify the kubeconfig to use the kind cluster server to https://kind-control-plane:6443
|
||||
# from worker service into docker-compose.yml
|
||||
kubectl config set-cluster kind-kind --server=https://kind-control-plane:6443
|
||||
kubectl config view
|
||||
- name: Add network kind to docker compose
|
||||
run: |
|
||||
# Add the network kind to the docker compose to interconnect to kind cluster
|
||||
yq -i '.networks.kind.external = true' docker-compose.yml
|
||||
# Add network kind to worker service and default network too
|
||||
yq -i '.services.worker.networks = ["kind","default"]' docker-compose.yml
|
||||
- name: Fix API data directory permissions
|
||||
run: docker run --rm -v $(pwd)/_data/api:/data alpine chown -R 1000:1000 /data
|
||||
- name: Start API services
|
||||
@@ -113,4 +133,4 @@ jobs:
|
||||
run: |
|
||||
echo "Shutting down services..."
|
||||
docker compose down -v || true
|
||||
echo "Cleanup completed"
|
||||
echo "Cleanup completed"
|
||||
@@ -188,5 +188,5 @@ export const getUserByMe = async (accessToken: string) => {
|
||||
};
|
||||
|
||||
export async function logOut() {
|
||||
await signOut();
|
||||
await signOut({ redirectTo: "/sign-in" });
|
||||
}
|
||||
|
||||
@@ -24,21 +24,14 @@ export abstract class BasePage {
|
||||
// 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
|
||||
|
||||
@@ -23,6 +23,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",
|
||||
@@ -78,6 +84,20 @@ 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 {
|
||||
// Button to add a new cloud provider
|
||||
@@ -129,6 +149,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;
|
||||
|
||||
@@ -180,10 +204,12 @@ export class ProvidersPage extends BasePage {
|
||||
name: "Client Secret",
|
||||
});
|
||||
this.m365TenantIdInput = page.getByRole("textbox", { name: "Tenant ID" });
|
||||
this.m365CertificateContentInput = page.getByRole("textbox", {
|
||||
name: "Certificate Content",
|
||||
});
|
||||
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.getByRole("textbox", {
|
||||
name: "Provider alias (optional)",
|
||||
@@ -249,28 +275,31 @@ export class ProvidersPage extends BasePage {
|
||||
// Click the add provider button
|
||||
|
||||
await this.addProviderButton.click();
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectAWSProvider(): Promise<void> {
|
||||
// Prefer label-based click for radios, force if overlay intercepts
|
||||
await this.awsProviderRadio.click({ force: true });
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectAZUREProvider(): Promise<void> {
|
||||
// Prefer label-based click for radios, force if overlay intercepts
|
||||
await this.azureProviderRadio.click({ force: true });
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectM365Provider(): Promise<void> {
|
||||
// Select the M365 provider
|
||||
|
||||
await this.m365ProviderRadio.click({ force: true });
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectKubernetesProvider(): Promise<void> {
|
||||
// Select the Kubernetes provider
|
||||
|
||||
await this.kubernetesProviderRadio.click({ force: true });
|
||||
}
|
||||
|
||||
|
||||
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
|
||||
// Fill the AWS provider details
|
||||
|
||||
@@ -301,6 +330,17 @@ 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.
|
||||
@@ -311,7 +351,6 @@ export class ProvidersPage extends BasePage {
|
||||
// If on the "connect-account" step, click the "Next" button
|
||||
if (/\/providers\/connect-account/.test(url)) {
|
||||
await this.nextButton.click();
|
||||
await this.waitForPageLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,15 +358,14 @@ export class ProvidersPage extends BasePage {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -339,7 +377,6 @@ export class ProvidersPage extends BasePage {
|
||||
.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-text-error elements, we want the first one with the technical error
|
||||
@@ -368,9 +405,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
} catch (error) {
|
||||
// If timeout or other error, check if error message is present
|
||||
const isErrorVisible = await errorMessage
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
const isErrorVisible = await errorMessage.isVisible().catch(() => false);
|
||||
|
||||
if (isErrorVisible) {
|
||||
const errorText = await errorMessage.textContent();
|
||||
throw new Error(
|
||||
@@ -401,7 +437,6 @@ export class ProvidersPage extends BasePage {
|
||||
|
||||
if (await btn.count()) {
|
||||
await btn.click();
|
||||
await this.waitForPageLoad();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -416,6 +451,7 @@ export class ProvidersPage extends BasePage {
|
||||
// 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) {
|
||||
@@ -423,14 +459,13 @@ export class ProvidersPage extends BasePage {
|
||||
} else {
|
||||
throw new Error(`Invalid AWS credential type: ${type}`);
|
||||
}
|
||||
// Wait for the page to load
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async selectM365CredentialsType(type: M365CredentialType): Promise<void> {
|
||||
// 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) {
|
||||
@@ -438,8 +473,6 @@ export class ProvidersPage extends BasePage {
|
||||
} else {
|
||||
throw new Error(`Invalid M365 credential type: ${type}`);
|
||||
}
|
||||
// Wait for the page to load
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async fillRoleCredentials(credentials: AWSProviderCredential): Promise<void> {
|
||||
@@ -525,12 +558,20 @@ 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
|
||||
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.addProviderButton).toBeVisible();
|
||||
await this.page.waitForLoadState("networkidle");
|
||||
}
|
||||
|
||||
async verifyConnectAccountPageLoaded(): Promise<void> {
|
||||
@@ -565,6 +606,13 @@ export class ProvidersPage extends BasePage {
|
||||
await expect(this.m365CertificateContentInput).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> {
|
||||
// Verify the launch scan page is loaded
|
||||
|
||||
@@ -575,13 +623,13 @@ export class ProvidersPage extends BasePage {
|
||||
const launchScanButton = this.page
|
||||
.locator("button")
|
||||
.filter({ hasText: "Launch scan" });
|
||||
|
||||
await expect(launchScanButton).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyLoadProviderPageAfterNewProvider(): Promise<void> {
|
||||
// Verify the provider page is loaded
|
||||
|
||||
await this.waitForPageLoad();
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.providersTable).toBeVisible();
|
||||
}
|
||||
@@ -598,6 +646,7 @@ export class ProvidersPage extends BasePage {
|
||||
|
||||
// Verify the number of matching rows is 1
|
||||
const count = await matchingRows.count();
|
||||
|
||||
if (count !== 1) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -618,9 +667,6 @@ export class ProvidersPage extends BasePage {
|
||||
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
|
||||
@@ -632,6 +678,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Helper function to check if a row is the "No results" row
|
||||
const isNoResultsRow = async (row: Locator): Promise<boolean> => {
|
||||
const text = await row.textContent();
|
||||
|
||||
return text?.includes("No results") || text?.includes("No data") || false;
|
||||
};
|
||||
|
||||
@@ -688,11 +735,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
// Find and click the action button (last cell = actions column)
|
||||
const actionButton = targetRow
|
||||
.locator("td")
|
||||
.last()
|
||||
.locator("button")
|
||||
.first();
|
||||
const actionButton = targetRow.locator("td").last().locator("button").first();
|
||||
|
||||
await expect(actionButton).toBeVisible({ timeout: 5000 });
|
||||
await actionButton.click();
|
||||
|
||||
@@ -700,13 +744,13 @@ export class ProvidersPage extends BasePage {
|
||||
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();
|
||||
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
|
||||
@@ -718,9 +762,6 @@ export class ProvidersPage extends BasePage {
|
||||
// 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 });
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
M365ProviderData,
|
||||
M365ProviderCredential,
|
||||
M365_CREDENTIAL_OPTIONS,
|
||||
KubernetesProviderData,
|
||||
KubernetesProviderCredential,
|
||||
KUBERNETES_CREDENTIAL_OPTIONS,
|
||||
} from "./providers-page";
|
||||
import fs from "fs";
|
||||
import { ScansPage } from "../scans/scans-page";
|
||||
|
||||
test.describe("Add Provider", () => {
|
||||
@@ -440,4 +444,104 @@ test.describe("Add Provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test.describe.serial("Add Kubernetes Provider", () => {
|
||||
// Providers page object
|
||||
let providersPage: ProvidersPage;
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// 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",
|
||||
);
|
||||
}
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await providersPage.deleteProviderIfExists(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 }) => {
|
||||
// Verify kubeconfig file exists
|
||||
if (!fs.existsSync(kubeconfigPath)) {
|
||||
throw new Error(`Kubeconfig file not found at ${kubeconfigPath}`);
|
||||
}
|
||||
|
||||
// Read kubeconfig content from file
|
||||
let kubeconfigContent: string;
|
||||
try {
|
||||
kubeconfigContent = fs.readFileSync(kubeconfigPath, "utf8");
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to read kubeconfig file at ${kubeconfigPath}: ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
scansPage = new ScansPage(page);
|
||||
await scansPage.verifyPageLoaded();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,5 @@ export class ScansPage extends BasePage {
|
||||
async verifyPageLoaded(): Promise<void> {
|
||||
await expect(this.page).toHaveTitle(/Prowler/);
|
||||
await expect(this.scanTable).toBeVisible();
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ export class SignUpPage extends BasePage {
|
||||
await expect(this.page.getByRole("heading", { name: "Sign up" })).toBeVisible();
|
||||
await expect(this.emailInput).toBeVisible();
|
||||
await expect(this.submitButton).toBeVisible();
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async fillName(name: string): Promise<void> {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
**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.
|
||||
- `E2E_NEW_PASSWORD` environment variable must be set with a valid password for the test.
|
||||
- `E2E_NEW_USER_PASSWORD` environment variable must be set with a valid password for the test.
|
||||
|
||||
### Flow Steps:
|
||||
1. Navigate to the Sign up page.
|
||||
@@ -38,6 +38,6 @@
|
||||
|
||||
### Notes:
|
||||
- Test data uses a random base36 suffix to avoid collisions with email.
|
||||
- The test requires the `E2E_NEW_PASSWORD` environment variable to be set before running.
|
||||
- The test requires the `E2E_NEW_USER_PASSWORD` environment variable to be set before running.
|
||||
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ test.describe("Sign Up Flow", () => {
|
||||
"should register a new user successfully",
|
||||
{ tag: ["@critical", "@e2e", "@signup", "@SIGNUP-E2E-001"] },
|
||||
async ({ page }) => {
|
||||
const password = process.env.E2E_NEW_PASSWORD;
|
||||
const password = process.env.E2E_NEW_USER_PASSWORD;
|
||||
|
||||
if (!password) {
|
||||
throw new Error("E2E_NEW_PASSWORD environment variable is not set");
|
||||
throw new Error("E2E_NEW_USER_PASSWORD environment variable is not set");
|
||||
}
|
||||
|
||||
const signUpPage = new SignUpPage(page);
|
||||
|
||||
Reference in New Issue
Block a user