Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
cf7b661
test(ui): enhance Playwright test setups for user authentication
StylusFrost Oct 9, 2025
bdbb2fa
test(ui): update Playwright test commands to specify project. Compati…
StylusFrost Oct 9, 2025
d21222a
test(ui): format Playwright configuration for consistency
StylusFrost Oct 9, 2025
9b7e4f5
test(ui): implement base page object and enhance sign-up flow tests
StylusFrost Oct 9, 2025
f424342
test(ui): add E2E tests document for user sign-up flow
StylusFrost Oct 10, 2025
2c9d8ad
test(ui): simplify sign-up page tests by removing redundant loading s…
StylusFrost Oct 10, 2025
fcf4293
test(ui): enhance session management tests with new helper functions
StylusFrost Oct 14, 2025
cef7fcc
Merge branch 'master' into PROWLER-187-create-new-user
StylusFrost Oct 14, 2025
d8ca60a
test(ui): add AWS provider management E2E tests
StylusFrost Oct 17, 2025
5e3db29
test(ui): update AWS provider credential handling in tests
StylusFrost Oct 17, 2025
7615634
test(ui): improve provider page load handling in tests
StylusFrost Oct 20, 2025
447d754
test(ui): add page load wait to sign-up page tests
StylusFrost Oct 20, 2025
055964a
test(ui): add Azure provider management E2E tests
StylusFrost Oct 20, 2025
0d088ec
test(ui): add Azure environment variables for E2E tests
StylusFrost Oct 20, 2025
5d86aac
Merge branch 'master' into PROWLER-187-create-new-user
StylusFrost Oct 20, 2025
d63ae0e
Merge branch 'master' into PROWLER-179-aws-add-an-connect-the-provider
StylusFrost Oct 20, 2025
b23b083
Merge branch 'master' into PROWLER-180-azure-add-and-connect-the-prov…
StylusFrost Oct 20, 2025
1343661
test(ui): add M365 provider management E2E tests
StylusFrost Oct 20, 2025
1212356
Merge branch 'master' into PROWLER-184-m365-add-and-connect-the-provider
StylusFrost Oct 20, 2025
7b321c8
test(ui): enhance M365 provider management E2E tests
StylusFrost Oct 20, 2025
4920f84
test(ui): update sign-up tests to use E2E_NEW_PASSWORD environment va…
StylusFrost Oct 20, 2025
94d5322
test(ui): update locators in tests to use role-based queries
StylusFrost Oct 20, 2025
f15fdfc
test(test): update button selector in ProvidersPage tests
StylusFrost Oct 21, 2025
71f5ac5
test(ui): add Kubernetes provider management E2E tests
StylusFrost Oct 21, 2025
d17d519
test(ui): update locators in tests to use role-based queries
StylusFrost Oct 21, 2025
a612139
test(ui): remove unnecessary blank line in admin authentication setup…
StylusFrost Oct 21, 2025
5384d30
Merge branch 'PROWLER-187-create-new-user' into PROWLER-179-aws-add-a…
StylusFrost Oct 21, 2025
4a0f0ba
Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWL…
StylusFrost Oct 21, 2025
a500339
test(ui): update ProvidersPage locators to use role-based queries
StylusFrost Oct 21, 2025
85d9411
Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWL…
StylusFrost Oct 21, 2025
d131822
test(ui): refactor ProvidersPage input locators to use role-based que…
StylusFrost Oct 21, 2025
1ea859f
Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' int…
StylusFrost Oct 21, 2025
e871235
test(ui): refactor ProvidersPage input locators to use role-based que…
StylusFrost Oct 21, 2025
fccd2c3
test(ui): enhance AWS and Azure provider tests with role-based creden…
StylusFrost Oct 21, 2025
a004a73
Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' int…
StylusFrost Oct 21, 2025
c0a8291
test(ui): add M365 provider data and credentials to provider tests
StylusFrost Oct 21, 2025
ccd8908
Merge branch 'PROWLER-184-m365-add-and-connect-the-provider' into PRO…
StylusFrost Oct 21, 2025
642efec
test(ui): refactoring tests
StylusFrost Oct 21, 2025
5af7c95
test(ci): update UI E2E tests configuration and improve Kubernetes co…
StylusFrost Oct 23, 2025
1cec011
test(ci): create kubeconfig directory for UI E2E tests
StylusFrost Oct 23, 2025
8354047
test(ci): correct kubeconfig environment variable usage in UI E2E tests
StylusFrost Oct 23, 2025
7458029
test(ui): refactor provider deletion logic and improve error handling
StylusFrost Oct 23, 2025
cb1284e
Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWL…
StylusFrost Oct 23, 2025
bd1aac2
test(ui): rename Azure provider input locators for clarity
StylusFrost Oct 23, 2025
f6f25d1
Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' int…
StylusFrost Oct 23, 2025
670d9a8
test(ui): refactor ProvidersPage and enhance M365 provider tests
StylusFrost Oct 23, 2025
0b37247
Merge branch 'PROWLER-184-m365-add-and-connect-the-provider' into PRO…
StylusFrost Oct 23, 2025
e0bedb0
test(ci): update UI E2E tests workflow for Kubernetes integration
StylusFrost Oct 24, 2025
bc76a8c
test(ci): add kubectl config view step in UI E2E tests workflow
StylusFrost Oct 24, 2025
027ae6c
test(ci): update kubeconfig context name in UI E2E tests workflow
StylusFrost Oct 24, 2025
a2e409d
test(ci): update cluster name parameter in UI E2E tests workflow
StylusFrost Oct 24, 2025
f260837
test(ci): correct parameter name for Kind cluster in UI E2E tests wor…
StylusFrost Oct 24, 2025
4250d4e
test(ci): correct kubeconfig cluster name in UI E2E tests workflow
StylusFrost Oct 24, 2025
5360759
test(ci): remove push trigger from UI E2E tests workflow
StylusFrost Oct 24, 2025
477de79
test(ui): update environment variable name for user password in sign-…
StylusFrost Oct 24, 2025
2409049
test(ui): add GCP provider support in ProvidersPage tests
StylusFrost Oct 27, 2025
faec92c
test(ui): add GitHub provider support in ProvidersPage tests
StylusFrost Oct 27, 2025
9d44070
test(ui): enhance invitations management in E2E tests
StylusFrost Oct 28, 2025
3cc49be
test(ui): needs space
StylusFrost Oct 29, 2025
77c121c
Merge branch 'PROWLER-181-gcp-add-and-connect-the-provider' into PROW…
StylusFrost Oct 29, 2025
57d2fef
test(ui): add missing spaces in ProvidersPage tests
StylusFrost Oct 29, 2025
f457653
Merge remote-tracking branch 'origin/PROWLER-197-out-of-scope-github-…
StylusFrost Oct 29, 2025
fb59998
fix(ui): add missing spaces in InvitationsPage tests
StylusFrost Oct 29, 2025
f58030f
test(ui): add scans E2E test suite and enhance scans functionality
StylusFrost Nov 3, 2025
722a69e
test(ui): enhance ScansPage with success toast verification
StylusFrost Nov 3, 2025
9b899b9
test(ui): update E2E test commands to include scans project
StylusFrost Nov 3, 2025
4b30ca3
test(ui): update provider tests to verify scheduled scan status
StylusFrost Nov 4, 2025
a101359
test(ui): add AWS provider E2E test for assume role using AWS SDK def…
StylusFrost Nov 13, 2025
4dcf361
test(ui): add OCI provider E2E test for API key credentials
StylusFrost Nov 28, 2025
439c1ca
test(ui): enhance ProvidersPage tests with error message handling and…
StylusFrost Nov 28, 2025
0203d7d
Merge branch 'master' into PROWLER-430-out-of-scope-oci-add-an-connec…
StylusFrost Nov 28, 2025
35a1052
test(ui): remove unnecessary page load wait in SignUpPage test
StylusFrost Nov 28, 2025
1de6272
test(ui): update InvitationsPage to streamline invitation process
StylusFrost Nov 28, 2025
669c6c6
test(ui): update AWS provider tests to utilize AWS SDK default creden…
StylusFrost Dec 1, 2025
011e8f2
test(ui): add GCP service account credentials to E2E test environment
StylusFrost Dec 1, 2025
e52ec0c
test(ui): update E2E test environment to include AWS credentials in .…
StylusFrost Dec 1, 2025
589835a
test(ui): correct syntax for appending AWS credentials to .env file
StylusFrost Dec 1, 2025
30a168b
test(ui): refine environment variable validation in Add Provider tests
StylusFrost Dec 1, 2025
acdf465
fix(ui): remove any type and redundant validation in e2e tests
Alan-TheGentleman Dec 1, 2025
fbfb890
refactor(ui): apply DRY/KISS improvements to providers e2e tests
Alan-TheGentleman Dec 1, 2025
02fc15f
feat(ui): implement AWS provider management functions in helpers
StylusFrost Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion .github/workflows/ui-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- 'ui/**'

jobs:

e2e-tests:
if: github.repository == 'prowler-cloud/prowler'
runs-on: ubuntu-latest
Expand All @@ -33,12 +34,50 @@ 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_GCP_BASE64_SERVICE_ACCOUNT_KEY: ${{ secrets.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY }}
E2E_GCP_PROJECT_ID: ${{ secrets.E2E_GCP_PROJECT_ID }}
E2E_GITHUB_APP_ID: ${{ secrets.E2E_GITHUB_APP_ID }}
E2E_GITHUB_BASE64_APP_PRIVATE_KEY: ${{ secrets.E2E_GITHUB_BASE64_APP_PRIVATE_KEY }}
E2E_GITHUB_USERNAME: ${{ secrets.E2E_GITHUB_USERNAME }}
E2E_GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_PERSONAL_ACCESS_TOKEN }}
E2E_GITHUB_ORGANIZATION: ${{ secrets.E2E_GITHUB_ORGANIZATION }}
E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN: ${{ secrets.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN }}
E2E_ORGANIZATION_ID: ${{ secrets.E2E_ORGANIZATION_ID }}
E2E_OCI_TENANCY_ID: ${{ secrets.E2E_OCI_TENANCY_ID }}
E2E_OCI_USER_ID: ${{ secrets.E2E_OCI_USER_ID }}
E2E_OCI_FINGERPRINT: ${{ secrets.E2E_OCI_FINGERPRINT }}
E2E_OCI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
E2E_OCI_REGION: ${{ secrets.E2E_OCI_REGION }}
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: Add AWS credentials for testing AWS SDK Default Adding Provider
run: |
echo "Adding AWS credentials for testing AWS SDK Default Adding Provider..."
echo "AWS_ACCESS_KEY_ID=${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}" >> .env
echo "AWS_SECRET_ACCESS_KEY=${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}" >> .env
- name: Start API services
run: |
# Override docker-compose image tag to use latest instead of stable
Expand Down
8 changes: 4 additions & 4 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"format:check": "./node_modules/.bin/prettier --check ./app",
"format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app",
"prepare": "husky",
"test:e2e": "playwright test --project=chromium --project=sign-up --project=providers",
"test:e2e:ui": "playwright test --project=chromium --project=sign-up --project=providers --ui",
"test:e2e:debug": "playwright test --project=chromium --project=sign-up --project=providers --debug",
"test:e2e:headed": "playwright test --project=chromium --project=sign-up --project=providers --headed",
"test:e2e": "playwright test --project=chromium --project=sign-up --project=providers --project=invitations --project=scans",
"test:e2e:ui": "playwright test --project=chromium --project=sign-up --project=providers --project=invitations --project=scans --ui",
"test:e2e:debug": "playwright test --project=chromium --project=sign-up --project=providers --project=invitations --project=scans --debug",
"test:e2e:headed": "playwright test --project=chromium --project=sign-up --project=providers --project=invitations --project=scans --headed",
"test:e2e:report": "playwright show-report",
"test:e2e:install": "playwright install"
},
Expand Down
12 changes: 12 additions & 0 deletions ui/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,24 @@ export default defineConfig({
name: "sign-up",
testMatch: "sign-up.spec.ts",
},
// This project runs the scans test suite
{
name: "scans",
testMatch: "scans.spec.ts",
dependencies: ["admin.auth.setup"],
},
// This project runs the providers test suite
{
name: "providers",
testMatch: "providers.spec.ts",
dependencies: ["admin.auth.setup"],
},
// This project runs the invitations test suite
{
name: "invitations",
testMatch: "invitations.spec.ts",
dependencies: ["admin.auth.setup"],
},
],

webServer: {
Expand Down
20 changes: 9 additions & 11 deletions ui/tests/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { Page, Locator, expect } from "@playwright/test";
*/
export abstract class BasePage {
readonly page: Page;

// Common UI elements that appear on most pages
readonly title: Locator;
readonly loadingIndicator: Locator;
readonly themeToggle: Locator;

constructor(page: Page) {
this.page = page;

// Common locators that most pages share
this.title = page.locator("h1, h2, [role='heading']").first();
this.loadingIndicator = page.getByRole("status", { name: "Loading" });
Expand All @@ -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
Expand Down Expand Up @@ -119,14 +112,14 @@ export abstract class BasePage {
async getFormErrors(): Promise<string[]> {
const errorElements = await this.page.locator('[role="alert"], .error-message, [data-testid="error"]').all();
const errors: string[] = [];

for (const element of errorElements) {
const text = await element.textContent();
if (text) {
errors.push(text.trim());
}
}

return errors;
}

Expand All @@ -137,23 +130,28 @@ export abstract class BasePage {

// Common wait methods
async waitForElement(element: Locator, timeout: number = 5000): Promise<void> {

await element.waitFor({ timeout });
}

async waitForElementToDisappear(element: Locator, timeout: number = 5000): Promise<void> {

await element.waitFor({ state: "hidden", timeout });
}

async waitForUrl(expectedUrl: string | RegExp, timeout: number = 5000): Promise<void> {

await this.page.waitForURL(expectedUrl, { timeout });
}

// Common screenshot methods
async takeScreenshot(name: string): Promise<void> {

await this.page.screenshot({ path: `screenshots/${name}.png` });
}

async takeElementScreenshot(element: Locator, name: string): Promise<void> {

await element.screenshot({ path: `screenshots/${name}.png` });
}
}
188 changes: 187 additions & 1 deletion ui/tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Page, expect } from "@playwright/test";
import { Locator, Page, expect } from "@playwright/test";
import { SignInPage, SignInCredentials } from "./sign-in/sign-in-page";
import { AWSProviderCredential, AWSProviderData, AWS_CREDENTIAL_OPTIONS, ProvidersPage } from "./providers/providers-page";
import { ScansPage } from "./scans/scans-page";

export const ERROR_MESSAGES = {
INVALID_CREDENTIALS: "Invalid email or password",
Expand Down Expand Up @@ -191,3 +193,187 @@ export async function verifySessionValid(page: Page) {
expect(session.refreshToken).toBeTruthy();
return session;
}


export async function addAWSProvider(
page: Page,
accountId: string,
accessKey: string,
secretKey: string,
): Promise<void> {
// 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,
};

// Create providers page object
const providersPage = new ProvidersPage(page);

// 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();

// Verify credentials page is loaded
await providersPage.verifyCredentialsPageLoaded();

// Select static credentials type
await providersPage.selectCredentialsType(
AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS,
);
// Fill static credentials
await providersPage.fillStaticCredentials(staticCredentials);
await providersPage.clickNext();

// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();

// Wait for redirect to provider page
const scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
}

export async function deleteProviderIfExists(page: ProvidersPage, providerUID: string): Promise<void> {
// Delete the provider if it exists

// Navigate to providers page
await page.goto();
await expect(page.providersTable).toBeVisible({ timeout: 10000 });

// Find and use the search input to filter the table
const searchInput = page.page.getByPlaceholder(/search|filter/i);

await expect(searchInput).toBeVisible({ timeout: 5000 });

// Clear and search for the specific provider
await searchInput.clear();
await searchInput.fill(providerUID);
await searchInput.press("Enter");

// 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 page.page.waitForTimeout(1500);

// Get all rows from the table
const allRows = page.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;
};

// 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;
};

// Wait for filtering to complete (max 0 or 1 data rows)
await expect(async () => {

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 provider row
const targetRow = await findProviderRow();

if (!targetRow) {
// Provider not found, nothing to delete
// Navigate back to providers page to ensure clean state
await page.goto();
await expect(page.providersTable).toBeVisible({ timeout: 10000 });
return;
}

// Find and click the action button (last cell = actions column)
const actionButton = targetRow
.locator("td")
.last()
.locator("button")
.first();

// Ensure the button is in view before clicking (handles horizontal scroll)
await actionButton.scrollIntoViewIfNeeded();
// Verify the button is visible
await expect(actionButton).toBeVisible({ timeout: 5000 });
await actionButton.click();

// Wait for dropdown menu to appear and find delete option
const deleteMenuItem = page.page.getByRole("menuitem", {
name: /delete.*provider/i,
});

await expect(deleteMenuItem).toBeVisible({ timeout: 5000 });
await deleteMenuItem.click();

// Wait for confirmation modal to appear
const modal = page.page
.locator('[role="dialog"], .modal, [data-testid*="modal"]')
.first();

await expect(modal).toBeVisible({ timeout: 10000 });

// Find and click the delete confirmation button
await expect(page.deleteProviderConfirmationButton).toBeVisible({
timeout: 5000,
});
await page.deleteProviderConfirmationButton.click();

// Wait for modal to close (this indicates deletion was initiated)
await expect(modal).not.toBeVisible({ timeout: 10000 });

// Navigate back to providers page to ensure clean state
await page.goto();
await expect(page.providersTable).toBeVisible({ timeout: 10000 });
}
Loading
Loading