Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWLER-180-azure-add-and-connect-the-provider-new

This commit is contained in:
StylusFrost
2025-10-23 14:56:43 +02:00
committed by GitHub
4 changed files with 181 additions and 102 deletions
-7
View File
@@ -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);
}
}
+137 -85
View File
@@ -162,10 +162,11 @@ export class ProvidersPage extends BasePage {
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"]',
);
// Delete button in confirmation modal
this.deleteProviderConfirmationButton = page.getByRole("button", {
name: "Delete",
exact: true,
});
}
async goto(): Promise<void> {
@@ -255,6 +256,43 @@ export class ProvidersPage extends BasePage {
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;
}
@@ -382,20 +420,10 @@ export class ProvidersPage extends BasePage {
await expect(launchScanButton).toBeVisible();
}
async verifyProviderExists(providerUID: string): Promise<boolean> {
// 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<void> {
// Verify the provider page is loaded
await this.waitForPageLoad();
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.providersTable).toBeVisible();
}
@@ -416,95 +444,119 @@ export class ProvidersPage extends BasePage {
return true;
}
async filterProvidersByProviderUID(providerUID: string): Promise<void> {
// Filter providers by providerUID
async deleteProviderIfExists(providerUID: string): Promise<void> {
// Delete the provider if it exists
// Go to the providers page
// Navigate to providers page
await this.goto();
await expect(this.providersTable).toBeVisible({ timeout: 10000 });
// Verify the providers table is visible
await expect(this.providersTable).toBeVisible();
// Get the search input
// Find and use the search input to filter the table
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 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);
async actionDeleteProvider(providerUID: string): Promise<void> {
// Delete the provider
// 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<boolean> => {
const text = await row.textContent();
return text?.includes("No results") || text?.includes("No data") || false;
};
// Filter search by providerUID
await this.filterProvidersByProviderUID(providerUID);
// Helper function to find the row with the specific UID
const findProviderRow = async (): Promise<Locator | null> => {
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;
};
// Verify the providers table is visible
await expect(this.providersTable).toBeVisible();
// 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 first row in the filtered results
const firstRow = this.providersTable.locator("tbody tr").first();
// 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;
}
// 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
// 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 the dropdown menu to appear
await this.page.waitForSelector('[role="menu"], [role="menuitem"]', {
timeout: 5000,
});
// Find the delete menu item
// 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();
// Verify the delete menu item is visible
await expect(deleteMenuItem).toBeVisible();
// Wait for confirmation modal to appear
const modal = this.page.locator('[role="dialog"], .modal, [data-testid*="modal"]').first();
await expect(modal).toBeVisible({ timeout: 10000 });
// Click the delete menu item with force to handle unstable elements
await deleteMenuItem.click({ force: true });
// Find and click the delete confirmation button
await expect(this.deleteProviderConfirmationButton).toBeVisible({ timeout: 5000 });
await this.deleteProviderConfirmationButton.click();
// Wait for the delete confirmation modal to appear
await this.page.waitForSelector(
'[role="dialog"], .modal, [data-testid*="modal"]',
{ timeout: 10000 },
);
// Wait for modal to close (this indicates deletion was initiated)
await expect(modal).not.toBeVisible({ timeout: 10000 });
// Find the delete confirmation button with multiple approaches
let deleteButton = this.deleteProviderConfirmationButton;
await expect(deleteButton).toBeVisible();
// Click the delete button with force to handle unstable elements
await deleteButton.click({ force: true });
// Wait for the modal to disappear and the page to update
const modalToCheck = this.page
.locator('[role="dialog"], .modal, [data-testid*="modal"]')
.first();
// Verify the modal is not visible
await expect(modalToCheck).not.toBeVisible();
// 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 });
}
}
+16 -10
View File
@@ -9,12 +9,14 @@ import {
AZUREProviderCredential,
AZURE_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;
@@ -32,7 +34,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
@@ -101,7 +103,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();
},
);
@@ -169,14 +172,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;
@@ -195,7 +200,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
@@ -251,8 +256,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();
},
);
});
+28
View File
@@ -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<void> {
await super.goto("/scans");
}
// Verification methods
async verifyPageLoaded(): Promise<void> {
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.scanTable).toBeVisible();
await this.waitForPageLoad();
}
}