Compare commits

...

60 Commits

Author SHA1 Message Date
StylusFrost
57d2fef6be test(ui): add missing spaces in ProvidersPage tests
- Added missing spaces in the ProvidersPage tests to improve readability and maintain consistency in the code style.
- This change enhances the overall clarity of the test code without affecting functionality.
2025-10-29 15:57:41 +01:00
StylusFrost
77c121c6a1 Merge branch 'PROWLER-181-gcp-add-and-connect-the-provider' into PROWLER-197-out-of-scope-github-add-an-connect-the-provider 2025-10-29 15:55:26 +01:00
StylusFrost
3cc49beb45 test(ui): needs space 2025-10-29 15:50:48 +01:00
StylusFrost
faec92cbbc test(ui): add GitHub provider support in ProvidersPage tests
- Introduced GitHub provider data and credential interfaces to the ProvidersPage.
- Implemented methods for filling GitHub provider details and credentials for both personal access token and GitHub App.
- Added end-to-end tests for adding a GitHub provider using personal access token and GitHub App authentication.
- Updated the UI E2E tests workflow to include necessary environment variables for GitHub integration.
- Enhanced documentation with new test cases for GitHub provider integration.
2025-10-27 20:38:21 +01:00
StylusFrost
2409049c88 test(ui): add GCP provider support in ProvidersPage tests
- Introduced GCP provider data and credential interfaces to the ProvidersPage.
- Implemented methods for filling GCP provider details and service account key credentials.
- Added end-to-end tests for adding a GCP provider using service account key authentication.
- Updated documentation to include new test case for GCP provider integration.
2025-10-27 09:34:13 +01:00
StylusFrost
477de791a9 test(ui): update environment variable name for user password in sign-up tests
- Changed the environment variable name from 'E2E_NEW_PASSWORD' to 'E2E_NEW_USER_PASSWORD' in the UI E2E tests workflow and related documentation.
- This update ensures consistency across the test setup and improves clarity in the sign-up flow tests.
2025-10-24 13:15:18 +02:00
StylusFrost
53607597b6 test(ci): remove push trigger from UI E2E tests workflow
- Eliminated the push trigger for the UI E2E tests workflow to streamline the process and focus on pull request events.
- This change enhances the workflow by ensuring tests are only run during pull requests, reducing unnecessary executions.
2025-10-24 12:08:40 +02:00
StylusFrost
4250d4ee07 test(ci): correct kubeconfig cluster name in UI E2E tests workflow
- Updated the kubeconfig cluster name from 'kind' to 'kind-kind' in the UI E2E tests workflow for improved accuracy and consistency.
- This change ensures proper configuration when interacting with the Kind cluster during end-to-end tests.
2025-10-24 11:14:19 +02:00
StylusFrost
f26083772e test(ci): correct parameter name for Kind cluster in UI E2E tests workflow
- Updated the parameter name for the Kind cluster creation from 'cluster-name' to 'cluster_name' in the UI E2E tests workflow for consistency with Kubernetes naming conventions.
- This change enhances clarity and aligns with best practices in the workflow configuration.
2025-10-24 11:11:06 +02:00
StylusFrost
a2e409d10e test(ci): update cluster name parameter in UI E2E tests workflow
- Changed the parameter name for the Kind cluster creation from 'name' to 'cluster-name' in the UI E2E tests workflow for improved clarity and consistency.
- This update aligns with best practices for Kubernetes configurations and enhances the overall readability of the workflow.
2025-10-24 11:09:59 +02:00
StylusFrost
027ae6cd73 test(ci): update kubeconfig context name in UI E2E tests workflow
- Changed the Kubernetes context name from 'kind-kind' to 'kind' in the UI E2E tests workflow for better clarity and consistency.
- Added a parameter to specify the Kind cluster name during its creation, ensuring proper integration with the Kubernetes setup.
2025-10-24 11:08:49 +02:00
StylusFrost
bc76a8cf25 test(ci): add kubectl config view step in UI E2E tests workflow
- Included a step to view the current kubeconfig in the UI E2E tests workflow, aiding in debugging and verification of the Kubernetes context setup.
- This addition enhances the workflow by providing visibility into the kubeconfig configuration during the test execution.
2025-10-24 11:05:52 +02:00
StylusFrost
e0bedb06d7 test(ci): update UI E2E tests workflow for Kubernetes integration
- Modified the Kubernetes context in the UI E2E tests workflow to use a fixed context 'kind-kind' instead of a secret.
- Added steps to create a Kind cluster and modify the kubeconfig for proper integration with the Kind cluster.
- Updated the docker-compose configuration to include the Kind network, ensuring seamless connectivity with the Kubernetes cluster.
2025-10-24 10:55:17 +02:00
StylusFrost
0b372477be Merge branch 'PROWLER-184-m365-add-and-connect-the-provider' into PROWLER-182-kubernetes-add-and-connect-the-provider 2025-10-23 15:12:00 +02:00
StylusFrost
670d9a8f87 test(ui): refactor ProvidersPage and enhance M365 provider tests
- Refactored the ProvidersPage class to improve the structure and clarity of provider data and credential handling for AWS, AZURE, and M365 providers.
- Introduced new methods for filling provider details and credentials, ensuring better organization and maintainability of the test code.
- Updated M365 provider tests to utilize the new structure, including verification of credential pages and improved error handling during provider management.
- Enhanced the test setup to ensure a clean state by deleting existing providers before each test.
2025-10-23 15:10:13 +02:00
StylusFrost
f6f25d1191 Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' into PROWLER-184-m365-add-and-connect-the-provider 2025-10-23 15:01:44 +02:00
StylusFrost
bd1aac2527 test(ui): rename Azure provider input locators for clarity
- Updated Azure provider input locators in the ProvidersPage class to include the 'azure' prefix, enhancing clarity and consistency.
- Adjusted the corresponding fill methods to reference the new locator names, ensuring proper functionality in the tests.
2025-10-23 15:00:45 +02:00
StylusFrost
cb1284e6e7 Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWLER-180-azure-add-and-connect-the-provider-new 2025-10-23 14:56:43 +02:00
StylusFrost
74580291a7 test(ui): refactor provider deletion logic and improve error handling
- Updated the ProvidersPage class to include a new method for deleting a provider if it exists, enhancing the test setup by ensuring a clean state.
- Improved error handling during the test connection process to provide clearer feedback on failures.
- Refactored existing tests to utilize the new deletion method, streamlining the test code and improving maintainability.
2025-10-23 14:52:24 +02:00
StylusFrost
83540472e4 test(ci): correct kubeconfig environment variable usage in UI E2E tests
- Changed the kubeconfig file creation step in the UI E2E tests workflow to use the environment variable directly instead of accessing it through secrets.
- This adjustment ensures proper handling of the kubeconfig setup for Kubernetes provider tests.
2025-10-23 13:26:32 +02:00
StylusFrost
1cec011d8d test(ci): create kubeconfig directory for UI E2E tests
- Added a step to create the kubeconfig directory in the UI E2E tests workflow to ensure proper setup for Kubernetes provider tests.
- This change enhances the configuration process by preventing potential errors related to missing directories.
2025-10-23 13:06:39 +02:00
StylusFrost
5af7c950ac test(ci): update UI E2E tests configuration and improve Kubernetes context setup
- Updated the Kubernetes context and kubeconfig path in the E2E tests workflow to use secrets for better security.
- Added a step to create the kubeconfig file from the secret, ensuring proper configuration for Kubernetes provider tests.
- Changed the "Add Kubernetes Provider" test to run serially for improved execution flow.
2025-10-23 13:03:53 +02:00
StylusFrost
642efecd37 test(ui): refactoring tests
Correct serial execution
Best process for deleting a provider
New scans page to manage properly when creating a provider
2025-10-22 20:51:35 +02:00
StylusFrost
ccd8908128 Merge branch 'PROWLER-184-m365-add-and-connect-the-provider' into PROWLER-182-kubernetes-add-and-connect-the-provider 2025-10-21 12:34:31 +02:00
StylusFrost
c0a82910ad test(ui): add M365 provider data and credentials to provider tests
- Introduced M365ProviderData, M365ProviderCredential, and M365_CREDENTIAL_OPTIONS to enhance the provider tests.
- This addition supports the integration of M365 provider functionalities in the UI tests.
2025-10-21 12:33:41 +02:00
StylusFrost
a004a73891 Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' into PROWLER-184-m365-add-and-connect-the-provider 2025-10-21 12:31:29 +02:00
StylusFrost
fccd2c3b9c test(ui): enhance AWS and Azure provider tests with role-based credentials
- Added tests for adding AWS providers with both static and role-based credentials, ensuring environment variables are validated.
- Refactored Azure provider tests to include role-based credential handling and improved setup for test state management.
- Updated test descriptions and tags for better clarity and organization.
2025-10-21 12:29:03 +02:00
StylusFrost
e8712359c7 test(ui): refactor ProvidersPage input locators to use role-based queries
- Updated additional input locators in ProvidersPage tests to utilize role-based queries for improved accessibility and consistency across the UI tests.
2025-10-21 12:02:36 +02:00
StylusFrost
1ea859f606 Merge branch 'PROWLER-180-azure-add-and-connect-the-provider-new' into PROWLER-184-m365-add-and-connect-the-provider 2025-10-21 11:58:48 +02:00
StylusFrost
d13182288e test(ui): refactor ProvidersPage input locators to use role-based queries
- Updated input locators in ProvidersPage tests to utilize role-based queries for improved accessibility and consistency across the UI tests.
2025-10-21 11:44:50 +02:00
StylusFrost
85d9411283 Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWLER-180-azure-add-and-connect-the-provider-new 2025-10-21 11:37:35 +02:00
StylusFrost
a500339138 test(ui): update ProvidersPage locators to use role-based queries
- Refactored input locators in ProvidersPage tests to utilize role-based queries for improved accessibility and consistency across the UI tests.
2025-10-21 11:35:29 +02:00
StylusFrost
4a0f0ba5bb Merge branch 'PROWLER-179-aws-add-an-connect-the-provider' into PROWLER-180-azure-add-and-connect-the-provider-new 2025-10-21 11:28:51 +02:00
StylusFrost
5384d30fd5 Merge branch 'PROWLER-187-create-new-user' into PROWLER-179-aws-add-an-connect-the-provider 2025-10-21 11:19:50 +02:00
StylusFrost
a6121396ca test(ui): remove unnecessary blank line in admin authentication setup test
- Cleaned up the admin authentication setup test by removing an unnecessary blank line for improved readability.
2025-10-21 11:13:52 +02:00
StylusFrost
d17d519a3e test(ui): update locators in tests to use role-based queries
- Refactored locators in BasePage, HomePage, SignInPage, and SignUpPage to utilize role-based queries for improved accessibility and consistency across the UI tests.
2025-10-21 11:12:09 +02:00
StylusFrost
71f5ac5165 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.
2025-10-21 10:56:37 +02:00
StylusFrost
f15fdfc642 test(test): update button selector in ProvidersPage tests
- Changed the selector for the "Add Cloud Provider" button from text-based to role-based for improved reliability and maintainability.
2025-10-21 09:31:11 +02:00
StylusFrost
94d5322f16 test(ui): update locators in tests to use role-based queries
- Replaced text-based locators with role-based queries in various test files for improved accessibility and consistency.
- Removed deprecated HomePage and SignInPage files to streamline the test structure.
2025-10-20 18:11:29 +02:00
StylusFrost
4920f84d75 test(ui): update sign-up tests to use E2E_NEW_PASSWORD environment variable
- Modified the sign-up test to retrieve the password from the E2E_NEW_PASSWORD environment variable.
- Updated documentation to specify the requirement for the E2E_NEW_PASSWORD variable before running tests.
2025-10-20 17:35:54 +02:00
StylusFrost
7b321c8cd1 test(ui): enhance M365 provider management E2E tests
- Added support for certificate-based authentication in M365 provider management.
- Updated the ProvidersPage interface to include fields for certificate credentials.
- Enhanced E2E tests to validate the complete flow of adding a new M365 provider with certificate credentials.
- Included necessary environment variable checks for certificate content and updated test steps accordingly.
2025-10-20 17:00:42 +02:00
StylusFrost
1212356db3 Merge branch 'master' into PROWLER-184-m365-add-and-connect-the-provider 2025-10-20 14:57:57 +02:00
StylusFrost
13436613d6 test(ui): add M365 provider management E2E tests
- Introduced new E2E tests for adding Microsoft 365 providers with static credentials.
- Updated the ProvidersPage interface to include M365-specific fields and methods.
- Enhanced the test suite to validate the complete flow of adding a new M365 provider, ensuring proper handling of credentials and provider details.
- Added necessary environment variable checks and cleanup steps to maintain test integrity.
2025-10-20 14:53:43 +02:00
StylusFrost
b23b083092 Merge branch 'master' into PROWLER-180-azure-add-and-connect-the-provider-new 2025-10-20 14:13:31 +02:00
StylusFrost
d63ae0e40f Merge branch 'master' into PROWLER-179-aws-add-an-connect-the-provider 2025-10-20 14:11:46 +02:00
StylusFrost
5d86aacb2a Merge branch 'master' into PROWLER-187-create-new-user 2025-10-20 13:51:07 +02:00
StylusFrost
0d088eca13 test(ui): add Azure environment variables for E2E tests
- Added necessary Azure environment variables to the UI E2E test workflow.
- This enhancement supports the integration of Azure provider management in the testing suite.
2025-10-20 11:56:16 +02:00
StylusFrost
055964aff3 test(ui): add Azure provider management E2E tests
- Introduced new E2E tests for adding Azure providers with static credentials.
- Updated the ProvidersPage interface to include Azure-specific fields and methods.
- Enhanced the test suite to validate the complete flow of adding a new Azure provider, ensuring proper handling of credentials and provider details.
- Added necessary environment variable checks and cleanup steps to maintain test integrity.
2025-10-20 11:51:21 +02:00
StylusFrost
447d754b49 test(ui): add page load wait to sign-up page tests
- Introduced a call to `waitForPageLoad` in the sign-up page test to ensure the page is fully loaded before proceeding with visibility checks for elements.
2025-10-20 10:36:40 +02:00
StylusFrost
761563472b test(ui): improve provider page load handling in tests
- Added a wait for network idle state in the ProvidersPage test to ensure all network requests are completed before proceeding.
- This enhancement improves the reliability of the test by ensuring the page is fully loaded before assertions are made.
2025-10-20 10:30:51 +02:00
StylusFrost
5e3db29de7 test(ui): update AWS provider credential handling in tests
- Refactored AWS provider credential interfaces to improve type safety and clarity.
- Replaced `ProviderCredentials` with `AWSProviderCredential` and introduced `AWS_CREDENTIAL_OPTIONS` for credential types.
- Updated tests to utilize the new credential structure, ensuring consistency across AWS provider management actions.
2025-10-17 19:35:25 +02:00
StylusFrost
d8ca60a4ab test(ui): add AWS provider management E2E tests
- Introduced new E2E tests for adding AWS providers with both static and role-based credentials.
- Updated Playwright configuration to include a new test suite for providers.
- Enhanced the UI workflow by adding necessary environment variables for E2E testing.
- Created helper functions for provider management actions and validations.
2025-10-17 13:41:40 +02:00
StylusFrost
cef7fcc24b Merge branch 'master' into PROWLER-187-create-new-user 2025-10-14 11:03:23 +02:00
StylusFrost
fcf42937aa test(ui): enhance session management tests with new helper functions
- Updated the `verifyLogoutSuccess` function to use a regex for URL verification.
- Added new helper functions `getSession` and `verifySessionValid` to streamline session validation in tests, ensuring that session data is correctly retrieved and validated.
2025-10-14 10:26:00 +02:00
StylusFrost
2c9d8ad8ea test(ui): simplify sign-up page tests by removing redundant loading state verification
- Removed the `verifyLoadingState` method from the `SignUpPage` class as it was redundant.
- Updated comments in the sign-up test to enhance clarity and focus on key actions.
- Added a call to `verifyNoErrors` to ensure no errors occur during the sign-up process.
2025-10-10 18:21:43 +02:00
StylusFrost
f424342e7e test(ui): add E2E tests document for user sign-up flow
- Documented test case details including flow steps, expected results, and key verification points.
- Enhanced existing sign-up test specifications to include relevant tags for better categorization and tracking.
2025-10-10 11:53:18 +02:00
StylusFrost
9b7e4f59e1 test(ui): implement base page object and enhance sign-up flow tests
- Introduced a BasePage class to encapsulate common functionality for page objects, improving code reusability and maintainability.
- Created new page objects for HomePage, SignInPage, and SignUpPage to streamline the sign-up and login processes in tests.
- Added comprehensive sign-up flow tests to validate user registration and login functionality, ensuring a smooth user experience.
- Updated Playwright configuration to support new test structures and improve overall test organization.
2025-10-09 16:19:27 +02:00
StylusFrost
d21222aa3a test(ui): format Playwright configuration for consistency
- Added missing commas and improved formatting in the Playwright configuration file to enhance readability and maintain consistency across the codebase.
2025-10-09 11:05:09 +02:00
StylusFrost
bdbb2fad78 test(ui): update Playwright test commands to specify project. Compatibility with current e2e test
- Modified Playwright test commands in package.json to explicitly use the 'chromium' project.
2025-10-09 10:54:34 +02:00
StylusFrost
cf7b66101c test(ui): enhance Playwright test setups for user authentication
- Added multiple authentication setup files for different user roles (admin, manage scans, manage integrations, etc.) to streamline end-to-end testing.
- Introduced a helper function to authenticate users and save their state for reuse in tests.
- Updated Playwright configuration to include new authentication projects.

This change improves the testing framework by allowing for more comprehensive and role-specific user authentication scenarios.
2025-10-09 10:27:37 +02:00
17 changed files with 2947 additions and 93 deletions

View File

@@ -10,6 +10,7 @@ on:
- 'ui/**'
jobs:
e2e-tests:
if: github.repository == 'prowler-cloud/prowler'
runs-on: ubuntu-latest
@@ -18,9 +19,50 @@ jobs:
AUTH_TRUST_HOST: true
NEXTAUTH_URL: 'http://localhost:3000'
NEXT_PUBLIC_API_BASE_URL: 'http://localhost:8080/api/v1'
E2E_ADMIN_USER: ${{ secrets.E2E_ADMIN_USER }}
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
E2E_AWS_PROVIDER_ACCOUNT_ID: ${{ secrets.E2E_AWS_PROVIDER_ACCOUNT_ID }}
E2E_AWS_PROVIDER_ACCESS_KEY: ${{ secrets.E2E_AWS_PROVIDER_ACCESS_KEY }}
E2E_AWS_PROVIDER_SECRET_KEY: ${{ secrets.E2E_AWS_PROVIDER_SECRET_KEY }}
E2E_AWS_PROVIDER_ROLE_ARN: ${{ secrets.E2E_AWS_PROVIDER_ROLE_ARN }}
E2E_AZURE_SUBSCRIPTION_ID: ${{ secrets.E2E_AZURE_SUBSCRIPTION_ID }}
E2E_AZURE_CLIENT_ID: ${{ secrets.E2E_AZURE_CLIENT_ID }}
E2E_AZURE_SECRET_ID: ${{ secrets.E2E_AZURE_SECRET_ID }}
E2E_AZURE_TENANT_ID: ${{ secrets.E2E_AZURE_TENANT_ID }}
E2E_M365_DOMAIN_ID: ${{ secrets.E2E_M365_DOMAIN_ID }}
E2E_M365_CLIENT_ID: ${{ secrets.E2E_M365_CLIENT_ID }}
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: 'kind-kind'
E2E_KUBERNETES_KUBECONFIG_PATH: /home/runner/.kube/config
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_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
@@ -97,4 +139,4 @@ jobs:
run: |
echo "Shutting down services..."
docker compose down -v || true
echo "Cleanup completed"
echo "Cleanup completed"

View File

@@ -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",
"test:e2e:ui": "playwright test --project=chromium --ui",
"test:e2e:debug": "playwright test --project=chromium --debug",
"test:e2e:headed": "playwright test --project=chromium --headed",
"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:report": "playwright show-report",
"test:e2e:install": "playwright install"
},

View File

@@ -89,6 +89,18 @@ export default defineConfig({
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
testMatch: "auth-login.spec.ts",
},
// This project runs the sign-up test suite
{
name: "sign-up",
testMatch: "sign-up.spec.ts",
},
// This project runs the providers test suite
{
name: "providers",
testMatch: "providers.spec.ts",
dependencies: ["admin.auth.setup"],
},
],

159
ui/tests/base-page.ts Normal file
View File

@@ -0,0 +1,159 @@
import { Page, Locator, expect } from "@playwright/test";
/**
* Base page object class containing common functionality
* that can be shared across all page objects
*/
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" });
this.themeToggle = page.getByRole("button", { name: "Toggle theme" });
}
// 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
async verifyPageTitle(expectedTitle: string | RegExp): Promise<void> {
await expect(this.page).toHaveTitle(expectedTitle);
}
async verifyLoadingState(): Promise<void> {
await expect(this.loadingIndicator).toBeVisible();
}
async verifyNoLoadingState(): Promise<void> {
await expect(this.loadingIndicator).not.toBeVisible();
}
// Common form interaction methods
async clearInput(input: Locator): Promise<void> {
await input.clear();
}
async fillInput(input: Locator, value: string): Promise<void> {
await input.fill(value);
}
async clickButton(button: Locator): Promise<void> {
await button.click();
}
// Common validation methods
async verifyElementVisible(element: Locator): Promise<void> {
await expect(element).toBeVisible();
}
async verifyElementNotVisible(element: Locator): Promise<void> {
await expect(element).not.toBeVisible();
}
async verifyElementText(element: Locator, expectedText: string): Promise<void> {
await expect(element).toHaveText(expectedText);
}
async verifyElementContainsText(element: Locator, expectedText: string): Promise<void> {
await expect(element).toContainText(expectedText);
}
// Common accessibility methods
async verifyKeyboardNavigation(elements: Locator[]): Promise<void> {
for (const element of elements) {
await this.page.keyboard.press("Tab");
await expect(element).toBeFocused();
}
}
async verifyAriaLabels(elements: { locator: Locator; expectedLabel: string }[]): Promise<void> {
for (const { locator, expectedLabel } of elements) {
await expect(locator).toHaveAttribute("aria-label", expectedLabel);
}
}
// Common utility methods
async getElementText(element: Locator): Promise<string> {
return await element.textContent() || "";
}
async getElementValue(element: Locator): Promise<string> {
return await element.inputValue();
}
async isElementVisible(element: Locator): Promise<boolean> {
return await element.isVisible();
}
async isElementEnabled(element: Locator): Promise<boolean> {
return await element.isEnabled();
}
// Common error handling methods
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;
}
async verifyNoErrors(): Promise<void> {
const errors = await this.getFormErrors();
expect(errors).toHaveLength(0);
}
// 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` });
}
}

View File

@@ -1,5 +1,6 @@
import { Page, expect } from "@playwright/test";
import { SignInPage, SignInCredentials } from "./page-objects/sign-in-page";
import { SignInPage, SignInCredentials } from "./sign-in/sign-in-page";
import { ProvidersPage } from "./providers/providers-page";
export const ERROR_MESSAGES = {
INVALID_CREDENTIALS: "Invalid email or password",
@@ -146,7 +147,9 @@ export async function authenticateAndSaveState(
storagePath: string,
) {
if (!email || !password) {
throw new Error('Email and password are required for authentication and save state');
throw new Error(
"Email and password are required for authentication and save state",
);
}
// Create SignInPage instance
@@ -162,6 +165,18 @@ export async function authenticateAndSaveState(
await page.context().storageState({ path: storagePath });
}
/**
* Generate a random base36 suffix of specified length
* Used for creating unique test data to avoid conflicts
*/
export function makeSuffix(len: number): string {
let s = "";
while (s.length < len) {
s += Math.random().toString(36).slice(2);
}
return s.slice(0, len);
}
export async function getSession(page: Page) {
const response = await page.request.get("/api/auth/session");
return response.json();

View File

@@ -1,7 +1,7 @@
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "../base-page";
export class HomePage {
readonly page: Page;
export class HomePage extends BasePage {
// Main content elements
readonly mainContent: Locator;
@@ -18,15 +18,14 @@ export class HomePage {
readonly overviewSection: Locator;
// UI elements
readonly themeToggle: Locator;
readonly logo: Locator;
constructor(page: Page) {
this.page = page;
super(page);
// Main content elements
this.mainContent = page.locator("main");
this.breadcrumbs = page.getByLabel("Breadcrumbs");
this.breadcrumbs = page.getByRole("navigation", { name: "Breadcrumbs" });
this.overviewHeading = page.getByRole("heading", { name: "Overview", exact: true });
// Navigation elements
@@ -39,18 +38,12 @@ export class HomePage {
this.overviewSection = page.locator('[data-testid="overview-section"]');
// UI elements
this.themeToggle = page.getByLabel("Toggle theme");
this.logo = page.locator('svg[width="300"]');
}
// Navigation methods
async goto(): Promise<void> {
await this.page.goto("/");
await this.waitForPageLoad();
}
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
await super.goto("/");
}
// Verification methods
@@ -58,7 +51,6 @@ export class HomePage {
await expect(this.page).toHaveURL("/");
await expect(this.mainContent).toBeVisible();
await expect(this.overviewHeading).toBeVisible();
await this.waitForPageLoad();
}
async verifyBreadcrumbs(): Promise<void> {
@@ -94,15 +86,6 @@ export class HomePage {
}
// Utility methods
async refresh(): Promise<void> {
await this.page.reload();
await this.waitForPageLoad();
}
async goBack(): Promise<void> {
await this.page.goBack();
await this.waitForPageLoad();
}
// Accessibility methods
async verifyKeyboardNavigation(): Promise<void> {
@@ -111,11 +94,6 @@ export class HomePage {
await expect(this.themeToggle).toBeFocused();
}
// Wait methods
async waitForRedirect(expectedUrl: string): Promise<void> {
await this.page.waitForURL(expectedUrl);
}
async waitForContentLoad(): Promise<void> {
await this.page.waitForFunction(() => {
const main = document.querySelector("main");

View File

@@ -0,0 +1,957 @@
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "../base-page";
// AWS provider data
export interface AWSProviderData {
accountId: string;
alias?: string;
}
// AZURE provider data
export interface AZUREProviderData {
subscriptionId: string;
alias?: string;
}
// M365 provider data
export interface M365ProviderData {
domainId: string;
alias?: string;
}
// Kubernetes provider data
export interface KubernetesProviderData {
context: string;
alias?: string;
}
// GCP provider data
export interface GCPProviderData {
projectId: string;
alias?: string;
}
// GitHub provider data
export interface GitHubProviderData {
username: string;
alias?: string;
}
// AWS credential options
export const AWS_CREDENTIAL_OPTIONS = {
AWS_ROLE_ARN: "role",
AWS_CREDENTIALS: "credentials"
} as const;
// AWS credential type
type AWSCredentialType = (typeof AWS_CREDENTIAL_OPTIONS)[keyof typeof AWS_CREDENTIAL_OPTIONS];
// AWS provider credential
export interface AWSProviderCredential {
type: AWSCredentialType;
roleArn?: string;
externalId?: string;
accessKeyId?: string;
secretAccessKey?: string;
}
// AZURE credential options
export const AZURE_CREDENTIAL_OPTIONS = {
AZURE_CREDENTIALS: "credentials"
} as const;
// AZURE credential type
type AZURECredentialType = (typeof AZURE_CREDENTIAL_OPTIONS)[keyof typeof AZURE_CREDENTIAL_OPTIONS];
// AZURE provider credential
export interface AZUREProviderCredential {
type: AZURECredentialType;
clientId:string;
clientSecret:string;
tenantId:string;
}
// M365 credential options
export const M365_CREDENTIAL_OPTIONS = {
M365_CREDENTIALS: "credentials",
M365_CERTIFICATE_CREDENTIALS: "certificate"
} as const;
// M365 credential type
type M365CredentialType = (typeof M365_CREDENTIAL_OPTIONS)[keyof typeof M365_CREDENTIAL_OPTIONS];
// M365 provider credential
export interface M365ProviderCredential {
type: M365CredentialType;
clientId:string;
clientSecret?:string;
tenantId:string;
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;
}
// 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;
}
// GitHub credential options
export const GITHUB_CREDENTIAL_OPTIONS = {
GITHUB_PERSONAL_ACCESS_TOKEN: "personal_access_token",
GITHUB_ORGANIZATION_ACCESS_TOKEN: "organization_access_token",
GITHUB_APP: "github_app"
} as const;
// GitHub credential type
type GitHubCredentialType = (typeof GITHUB_CREDENTIAL_OPTIONS)[keyof typeof GITHUB_CREDENTIAL_OPTIONS];
// GitHub provider personal access token credential
export interface GitHubProviderCredential {
type: GitHubCredentialType;
personalAccessToken?:string;
githubAppId?:string;
githubAppPrivateKey?:string;
}
// Providers page
export class ProvidersPage extends BasePage {
// Button to add a new cloud provider
readonly addProviderButton: Locator;
readonly providersTable: Locator;
// Provider selection elements
readonly awsProviderRadio: Locator;
readonly gcpProviderRadio: Locator;
readonly azureProviderRadio: Locator;
readonly m365ProviderRadio: Locator;
readonly kubernetesProviderRadio: Locator;
readonly githubProviderRadio: Locator;
// AWS provider form elements
readonly accountIdInput: Locator;
readonly aliasInput: Locator;
readonly nextButton: Locator;
readonly backButton: Locator;
readonly saveButton: Locator;
readonly launchScanButton: Locator;
// AWS credentials type selection
readonly roleCredentialsRadio: Locator;
readonly staticCredentialsRadio: Locator;
// M365 credentials type selection
readonly m365StaticCredentialsRadio: Locator;
readonly m365CertificateCredentialsRadio: Locator;
// GitHub credentials type selection
readonly githubPersonalAccessTokenRadio: Locator;
readonly githubAppCredentialsRadio: Locator;
// AWS role credentials form
readonly roleArnInput: Locator;
readonly externalIdInput: Locator;
// AWS static credentials form
readonly accessKeyIdInput: Locator;
readonly secretAccessKeyInput: Locator;
// AZURE provider form elements
readonly azureSubscriptionIdInput: Locator;
readonly azureClientIdInput: Locator;
readonly azureClientSecretInput: Locator;
readonly azureTenantIdInput: Locator;
// M365 provider form elements
readonly m365domainIdInput: Locator;
readonly m365ClientIdInput: Locator;
readonly m365ClientSecretInput: Locator;
readonly m365TenantIdInput: Locator;
readonly m365CertificateContentInput: Locator;
// Kubernetes provider form elements
readonly kubernetesContextInput: Locator;
readonly kubernetesKubeconfigContentInput: Locator;
// GCP provider form elements
readonly gcpProjectIdInput: Locator;
readonly gcpServiceAccountKeyInput: Locator;
readonly gcpServiceAccountRadio: Locator;
// GitHub provider form elements
readonly githubUsernameInput: Locator;
readonly githubAppIdInput: Locator;
readonly githubAppPrivateKeyInput: Locator;
readonly githubPersonalAccessTokenInput: Locator;
// Delete button
readonly deleteProviderConfirmationButton: Locator;
constructor(page: Page) {
super(page);
// Button to add a new cloud provider
this.addProviderButton = page.getByRole("button", { name: "Add Cloud Provider", exact: true });
// Table displaying existing providers
this.providersTable = page.getByRole("table");
// Radio buttons to select the type of cloud provider
this.awsProviderRadio = page.getByRole("radio", {
name: /Amazon Web Services/i,
});
this.gcpProviderRadio = page.getByRole("radio", {
name: /Google Cloud Platform/i,
});
this.azureProviderRadio = page.getByRole("radio", {
name: /Microsoft Azure/i,
});
this.m365ProviderRadio = page.getByRole("radio", {
name: /Microsoft 365/i,
});
this.kubernetesProviderRadio = page.getByRole("radio", {
name: /Kubernetes/i,
});
this.githubProviderRadio = page.getByRole("radio", { name: /GitHub/i });
// AWS provider form inputs
this.accountIdInput = page.getByRole("textbox", { name: "Account ID" });
// AZURE provider form inputs
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.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" });
// GCP provider form inputs
this.gcpProjectIdInput = page.getByRole("textbox", { name: "Project ID" });
this.gcpServiceAccountKeyInput = page.getByRole("textbox", { name: "Service Account Key" });
// GitHub provider form inputs
this.githubUsernameInput = page.getByRole("textbox", { name: "Username" });
this.githubPersonalAccessTokenInput = page.getByRole("textbox", { name: "Personal Access Token" });
this.githubAppIdInput = page.getByRole("textbox", { name: "GitHub App ID" });
this.githubAppPrivateKeyInput = page.getByRole("textbox", { name: "GitHub App Private Key" });
// Alias input
this.aliasInput = page.getByRole("textbox", { name: "Provider alias (optional)" });
// Navigation buttons in the form (next and back)
this.nextButton = page
.locator("form")
.getByRole("button", { name: "Next", exact: true });
this.backButton = page.getByRole("button", { name: "Back" });
// Button to save the form
this.saveButton = page.getByRole("button", { name: "Save", exact: true });
// Button to launch a scan
this.launchScanButton = page.getByRole("button", {
name: "Launch scan",
exact: true,
});
// Radios for selecting AWS credentials method
this.roleCredentialsRadio = page.getByRole("radio", {
name: /Connect assuming IAM Role/i,
});
this.staticCredentialsRadio = page.getByRole("radio", {
name: /Connect via Credentials/i,
});
// Radios for selecting M365 credentials method
this.m365StaticCredentialsRadio = page.getByRole("radio", {
name: /App Client Secret Credentials/i,
});
this.m365CertificateCredentialsRadio = page.getByRole("radio", {
name: /App Certificate Credentials/i,
});
// Radios for selecting GCP credentials method
this.gcpServiceAccountRadio = page.getByRole("radio", {
name: /Service Account Key/i,
});
// Radios for selecting GitHub credentials method
this.githubPersonalAccessTokenRadio = page.getByRole("radio", {
name: /Personal Access Token/i,
});
this.githubAppCredentialsRadio = page.getByRole("radio", {
name: /GitHub App/i,
});
// Inputs for IAM Role credentials
this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" });
this.externalIdInput = page.getByRole("textbox", { name: "External ID" });
// Inputs for static credentials
this.accessKeyIdInput = page.getByRole("textbox", { name: "Access Key ID" });
this.secretAccessKeyInput = page.getByRole("textbox", { name: "Secret Access Key" });
// Delete button in confirmation modal
this.deleteProviderConfirmationButton = page.getByRole("button", {
name: "Delete",
exact: true,
});
}
async goto(): Promise<void> {
// Go to the providers page
await super.goto("/providers");
}
async clickAddProvider(): Promise<void> {
// 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 });
await this.waitForPageLoad();
}
async selectGCPProvider(): Promise<void> {
// Select the GCP provider
await this.gcpProviderRadio.click({ force: true });
await this.waitForPageLoad();
}
async selectGitHubProvider(): Promise<void> {
// Select the GitHub provider
await this.githubProviderRadio.click({ force: true });
await this.waitForPageLoad();
}
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
// Fill the AWS provider details
await this.accountIdInput.fill(data.accountId);
if (data.alias) {
await this.aliasInput.fill(data.alias);
}
}
async fillAZUREProviderDetails(data: AZUREProviderData): Promise<void> {
// Fill the AWS provider details
await this.azureSubscriptionIdInput.fill(data.subscriptionId);
if (data.alias) {
await this.aliasInput.fill(data.alias);
}
}
async fillM365ProviderDetails(data: M365ProviderData): Promise<void> {
// Fill the M365 provider details
await this.m365domainIdInput.fill(data.domainId);
if (data.alias) {
await this.aliasInput.fill(data.alias);
}
}
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 fillGCPProviderDetails(data: GCPProviderData): Promise<void> {
// Fill the GCP provider details
await this.gcpProjectIdInput.fill(data.projectId);
if (data.alias) {
await this.aliasInput.fill(data.alias);
}
}
async fillGitHubProviderDetails(data: GitHubProviderData): Promise<void> {
// Fill the GitHub provider details
await this.githubUsernameInput.fill(data.username);
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.
// Get the current page URL
const url = this.page.url();
// If on the "connect-account" step, click the "Next" button
if (/\/providers\/connect-account/.test(url)) {
await this.nextButton.click();
await this.waitForPageLoad();
return;
}
// If on the "add-credentials" step, check for "Save" and "Next" buttons
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;
}
}
// If on the "test-connection" step, click the "Launch scan" button
if (/\/providers\/test-connection/.test(url)) {
const buttonByText = this.page
.locator("button")
.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-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;
}
// Fallback logic: try finding any common primary action buttons in expected order
const candidates = [
{ name: "Next" }, // Try the "Next" button
{ name: "Save" }, // Try the "Save" button
{ name: "Launch scan" }, // Try the "Launch scan" button
{ name: /Continue|Proceed/i }, // Try "Continue" or "Proceed" (case-insensitive)
] as const;
// Try each candidate name and click it if found
for (const candidate of candidates) {
// Try each candidate name and click it if found
const btn = this.page.getByRole("button", {
name: candidate.name as any,
});
if (await btn.count()) {
await btn.click();
await this.waitForPageLoad();
return;
}
}
// If none of the expected action buttons are present, throw an error
throw new Error(
"Could not find an actionable Next/Save/Launch scan button on this step",
);
}
async selectCredentialsType(type: AWSCredentialType): Promise<void> {
// 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) {
await this.staticCredentialsRadio.click({ force: true });
} 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) {
await this.m365CertificateCredentialsRadio.click({ force: true });
} else {
throw new Error(`Invalid M365 credential type: ${type}`);
}
// Wait for the page to load
await this.waitForPageLoad();
}
async selectGCPCredentialsType(type: GCPCredentialType): Promise<void> {
// 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 selectGitHubCredentialsType(type: GitHubCredentialType): Promise<void> {
// Ensure we are on the add-credentials page where the selector exists
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
if (type === GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN) {
await this.githubPersonalAccessTokenRadio.click({ force: true });
} else if (type === GITHUB_CREDENTIAL_OPTIONS.GITHUB_APP) {
await this.githubAppCredentialsRadio.click({ force: true });
} else {
throw new Error(`Invalid GitHub credential type: ${type}`);
}
// Wait for the page to load
await this.waitForPageLoad();
}
async fillRoleCredentials(credentials: AWSProviderCredential): Promise<void> {
// Fill the role credentials form
if (credentials.accessKeyId) {
await this.accessKeyIdInput.fill(credentials.accessKeyId);
}
if (credentials.secretAccessKey) {
await this.secretAccessKeyInput.fill(credentials.secretAccessKey);
}
if (credentials.roleArn) {
await this.roleArnInput.fill(credentials.roleArn);
}
if (credentials.externalId) {
// External ID may be prefilled and disabled; only fill if enabled
if (await this.externalIdInput.isEnabled()) {
await this.externalIdInput.fill(credentials.externalId);
}
}
}
async fillStaticCredentials(credentials: AWSProviderCredential): Promise<void> {
// Fill the static credentials form
if (credentials.accessKeyId) {
await this.accessKeyIdInput.fill(credentials.accessKeyId);
}
if (credentials.secretAccessKey) {
await this.secretAccessKeyInput.fill(credentials.secretAccessKey);
}
}
async fillAZURECredentials(credentials: AZUREProviderCredential): Promise<void> {
// Fill the azure credentials form
if (credentials.clientId) {
await this.azureClientIdInput.fill(credentials.clientId);
}
if (credentials.clientSecret) {
await this.azureClientSecretInput.fill(credentials.clientSecret);
}
if (credentials.tenantId) {
await this.azureTenantIdInput.fill(credentials.tenantId);
}
}
async fillM365Credentials(credentials: M365ProviderCredential): Promise<void> {
// Fill the m365 credentials form
if (credentials.clientId) {
await this.m365ClientIdInput.fill(credentials.clientId);
}
if (credentials.clientSecret) {
await this.m365ClientSecretInput.fill(credentials.clientSecret);
}
if (credentials.tenantId) {
await this.m365TenantIdInput.fill(credentials.tenantId);
}
}
async fillM365CertificateCredentials(credentials: M365ProviderCredential): Promise<void> {
// Fill the m365 certificate credentials form
if (credentials.clientId) {
await this.m365ClientIdInput.fill(credentials.clientId);
}
if (credentials.certificateContent) {
await this.m365CertificateContentInput.fill(credentials.certificateContent);
}
if (credentials.tenantId) {
await this.m365TenantIdInput.fill(credentials.tenantId);
}
}
async fillKubernetesCredentials(credentials: KubernetesProviderCredential): Promise<void> {
// Fill the Kubernetes credentials form
if (credentials.kubeconfigContent) {
await this.kubernetesKubeconfigContentInput.fill(credentials.kubeconfigContent);
}
}
async fillGCPServiceAccountKeyCredentials(credentials: GCPProviderCredential): Promise<void> {
// Fill the GCP credentials form
if (credentials.serviceAccountKey) {
await this.gcpServiceAccountKeyInput.fill(credentials.serviceAccountKey);
}
}
async fillGitHubPersonalAccessTokenCredentials(credentials: GitHubProviderCredential): Promise<void> {
// Fill the GitHub personal access token credentials form
if (credentials.personalAccessToken) {
await this.githubPersonalAccessTokenInput.fill(credentials.personalAccessToken);
}
}
async fillGitHubAppCredentials(credentials: GitHubProviderCredential): Promise<void> {
// Fill the GitHub app credentials form
if (credentials.githubAppId) {
await this.githubAppIdInput.fill(credentials.githubAppId);
}
if (credentials.githubAppPrivateKey) {
await this.githubAppPrivateKeyInput.fill(credentials.githubAppPrivateKey);
}
}
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> {
// Verify the connect account page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.awsProviderRadio).toBeVisible();
}
async verifyCredentialsPageLoaded(): Promise<void> {
// Verify the credentials page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.roleCredentialsRadio).toBeVisible();
}
async verifyM365CredentialsPageLoaded(): Promise<void> {
// Verify the M365 credentials page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.m365ClientIdInput).toBeVisible();
await expect(this.m365ClientSecretInput).toBeVisible();
await expect(this.m365TenantIdInput).toBeVisible();
}
async verifyM365CertificateCredentialsPageLoaded(): Promise<void> {
// Verify the M365 certificate credentials page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.m365ClientIdInput).toBeVisible();
await expect(this.m365TenantIdInput).toBeVisible();
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 verifyGCPServiceAccountPageLoaded(): Promise<void> {
// Verify the GCP service account page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.gcpServiceAccountKeyInput).toBeVisible();
}
async verifyGitHubPersonalAccessTokenPageLoaded(): Promise<void> {
// Verify the GitHub personal access token page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.githubPersonalAccessTokenInput).toBeVisible();
}
async verifyGitHubAppPageLoaded(): Promise<void> {
// Verify the GitHub app page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.githubAppIdInput).toBeVisible();
await expect(this.githubAppPrivateKeyInput).toBeVisible();
}
async verifyLaunchScanPageLoaded(): Promise<void> {
// Verify the launch scan page is loaded
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.page).toHaveURL(/\/providers\/test-connection/);
// Verify the Launch scan button is visible
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();
}
async verifySingleRowForProviderUID(providerUID: string): Promise<boolean> {
// Verify if table has 1 row and that row contains providerUID
await expect(this.providersTable).toBeVisible();
// Get the matching rows
const matchingRows = this.providersTable.locator("tbody tr", {
hasText: providerUID,
});
// Verify the number of matching rows is 1
const count = await matchingRows.count();
if (count !== 1) return false;
return true;
}
async deleteProviderIfExists(providerUID: string): Promise<void> {
// Delete the provider if it exists
// Navigate to providers page
await this.goto();
await expect(this.providersTable).toBeVisible({ timeout: 10000 });
// Find and use the search input to filter the table
const searchInput = this.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");
// 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);
// 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;
};
// 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 () => {
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 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;
}
// 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 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();
// Wait for confirmation modal to appear
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
await expect(this.deleteProviderConfirmationButton).toBeVisible({ timeout: 5000 });
await this.deleteProviderConfirmationButton.click();
// 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 });
}
}

View File

@@ -0,0 +1,552 @@
### E2E Tests: AWS Provider Management
**Suite ID:** `PROVIDER-E2E`
**Feature:** AWS Provider Management - Add and configure AWS cloud providers with different authentication methods
---
## Test Case: `PROVIDER-E2E-001` - Add AWS Provider with Static Credentials
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @aws
**Description/Objective:** Validates the complete flow of adding a new AWS provider using static access key credentials
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY and E2E_AWS_PROVIDER_SECRET_KEY
- Remove any existing provider with the same Account ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Account ID not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select AWS provider type
4. Fill provider details (account ID and alias)
5. Select "credentials" authentication type
6. Fill static credentials (access key and secret key)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- AWS provider successfully added with static credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays AWS option
- Credentials form accepts static credentials
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for AWS credentials
- Provider cleanup performed before each test to ensure clean state
- Requires valid AWS account with appropriate permissions
---
## Test Case: `PROVIDER-E2E-002` - Add AWS Provider with Assume Role Credentials Access Key and Secret Key
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @aws
**Description/Objective:** Validates the complete flow of adding a new AWS provider using role-based authentication with Access Key and Secret Key
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, E2E_AWS_PROVIDER_SECRET_KEY, E2E_AWS_PROVIDER_ROLE_ARN
- Remove any existing provider with the same Account ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Account ID not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select AWS provider type
4. Fill provider details (account ID and alias)
5. Select "role" authentication type
6. Fill role credentials (access key, secret key, and role ARN)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- AWS provider successfully added with role credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays AWS option
- Role credentials form accepts all required fields
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for AWS credentials and role ARN
- Provider cleanup performed before each test to ensure clean state
- Requires valid AWS account with role assumption permissions
- Role ARN must be properly configured
---
## Test Case: `PROVIDER-E2E-003` - Add Azure Provider with Static Credentials
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @azure
**Description/Objective:** Validates the complete flow of adding a new Azure provider using static client credentials (Client ID, Client Secret, Tenant ID)
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_AZURE_SUBSCRIPTION_ID, E2E_AZURE_CLIENT_ID, E2E_AZURE_SECRET_ID, E2E_AZURE_TENANT_ID
- Remove any existing provider with the same Subscription ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Subscription ID not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select Azure provider type
4. Fill provider details (subscription ID and alias)
5. Fill Azure credentials (client ID, client secret, tenant ID)
6. Launch initial scan
7. Verify redirect to provider management page
### Expected Result:
- Azure provider successfully added with static credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays Azure option
- Azure credentials form accepts all required fields
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for Azure credentials
- Provider cleanup performed before each test to ensure clean state
- Requires valid Azure subscription with appropriate permissions
- Client credentials must have sufficient permissions for security scanning
---
## Test Case: `PROVIDER-E2E-004` - Add M365 Provider with Static Credentials
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @m365
**Description/Objective:** Validates the complete flow of adding a new Microsoft 365 provider using static client credentials (Client ID, Client Secret, Tenant ID) tied to a Domain ID.
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, E2E_M365_SECRET_ID, E2E_M365_TENANT_ID
- Remove any existing provider with the same Domain ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Domain ID not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select M365 provider type
4. Fill provider details (domain ID and alias)
5. Select static credentials type
6. Fill M365 credentials (client ID, client secret, tenant ID)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- M365 provider successfully added with static credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays M365 option
- M365 credentials form accepts all required fields
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for M365 credentials
- Provider cleanup performed before each test to ensure clean state
- Requires valid Microsoft 365 tenant with appropriate permissions
- Client credentials must have sufficient permissions for security scanning
---
## Test Case: `PROVIDER-E2E-005` - Add M365 Provider with Certificate Credentials
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @m365
**Description/Objective:** Validates the complete flow of adding a new Microsoft 365 provider using certificate-based authentication (Client ID, Tenant ID, Certificate Content) tied to a Domain ID.
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, E2E_M365_TENANT_ID, E2E_M365_CERTIFICATE_CONTENT
- Remove any existing provider with the same Domain ID before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Domain ID not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select M365 provider type
4. Fill provider details (domain ID and alias)
5. Select certificate credentials type
6. Fill M365 certificate credentials (client ID, tenant ID, certificate content)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- M365 provider successfully added with certificate credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays M365 option
- Certificate credentials form accepts all required fields
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for M365 certificate credentials
- 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)
---
## 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
- Test uses base64 encoded environment variables for GCP service account key
---
## Test Case: `PROVIDER-E2E-008` - Add GitHub Provider with Personal Access Token
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @github
**Description/Objective:** Validates the complete flow of adding a new GitHub provider using personal access token authentication for a user account
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_GITHUB_USERNAME, E2E_GITHUB_PERSONAL_ACCESS_TOKEN
- Remove any existing provider with the same Username before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Username not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select GitHub provider type
4. Fill provider details (username and alias)
5. Select personal access token credentials type
6. Fill GitHub personal access token credentials
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- GitHub provider successfully added with personal access token
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays GitHub option
- Provider details form accepts username and alias
- Personal access token credentials page loads with token field
- Personal access token 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 GitHub username and personal access token
- Provider cleanup performed before each test to ensure clean state
- Requires valid GitHub account with personal access token
- Personal access token must have sufficient permissions for security scanning
- Test validates that personal access token goes to the correct field
---
## Test Case: `PROVIDER-E2E-009` - Add GitHub Provider with GitHub App
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @github
**Description/Objective:** Validates the complete flow of adding a new GitHub provider using GitHub App authentication for a user account
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_GITHUB_USERNAME, E2E_GITHUB_APP_ID, E2E_GITHUB_BASE64_APP_PRIVATE_KEY
- Remove any existing provider with the same Username before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Username not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select GitHub provider type
4. Fill provider details (username and alias)
5. Select GitHub App credentials type
6. Fill GitHub App credentials (App ID and private key)
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- GitHub provider successfully added with GitHub App credentials
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays GitHub option
- Provider details form accepts username and alias
- GitHub App credentials page loads with App ID and private key fields
- GitHub App credentials are properly filled in the correct fields
- Launch scan page appears
- Successful redirect to provider page after scan launch
### Notes:
- Test uses environment variables for GitHub username, App ID, and base64 encoded private key
- Private key is base64 encoded and must be decoded before use
- Provider cleanup performed before each test to ensure clean state
- Requires valid GitHub App with App ID and private key
- GitHub App must have sufficient permissions for security scanning
- Test validates that GitHub App credentials go to the correct fields
---
## Test Case: `PROVIDER-E2E-010` - Add GitHub Provider with Organization Personal Access Token
**Priority:** `critical`
**Tags:**
- type → @e2e, @serial
- feature → @providers
- provider → @github
**Description/Objective:** Validates the complete flow of adding a new GitHub provider using organization personal access token authentication
**Preconditions:**
- Admin user authentication required (admin.auth.setup setup)
- Environment variables configured: E2E_GITHUB_ORGANIZATION, E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN
- Remove any existing provider with the same Organization name before starting the test
- This test must be run serially and never in parallel with other tests, as it requires the Organization name not to be already registered beforehand.
### Flow Steps:
1. Navigate to providers page
2. Click "Add Provider" button
3. Select GitHub provider type
4. Fill provider details (organization name and alias)
5. Select personal access token credentials type
6. Fill GitHub organization personal access token credentials
7. Launch initial scan
8. Verify redirect to provider management page
### Expected Result:
- GitHub provider successfully added with organization personal access token
- Initial scan launched successfully
- User redirected to provider details page
### Key verification points:
- Provider page loads correctly
- Connect account page displays GitHub option
- Provider details form accepts organization name and alias
- Personal access token credentials page loads with token field
- Organization personal access token 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 GitHub organization name and organization access token
- Provider cleanup performed before each test to ensure clean state
- Requires valid GitHub organization with organization access token
- Organization access token must have sufficient permissions for security scanning
- Test validates that organization personal access token goes to the correct field

View File

@@ -0,0 +1,936 @@
import { test } from "@playwright/test";
import * as helpers from "../helpers";
import {
ProvidersPage,
AWSProviderData,
AWSProviderCredential,
AWS_CREDENTIAL_OPTIONS,
AZUREProviderData,
AZUREProviderCredential,
AZURE_CREDENTIAL_OPTIONS,
M365ProviderData,
M365ProviderCredential,
M365_CREDENTIAL_OPTIONS,
KubernetesProviderData,
KubernetesProviderCredential,
KUBERNETES_CREDENTIAL_OPTIONS,
GCPProviderData,
GCPProviderCredential,
GCP_CREDENTIAL_OPTIONS,
GitHubProviderData,
GitHubProviderCredential,
GITHUB_CREDENTIAL_OPTIONS,
} from "./providers-page";
import { ScansPage } from "../scans/scans-page";
import fs from "fs";
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;
const secretKey = process.env.E2E_AWS_PROVIDER_SECRET_KEY;
const roleArn = process.env.E2E_AWS_PROVIDER_ROLE_ARN;
// Validate required environment variables
if (!accountId) {
throw new Error(
"E2E_AWS_PROVIDER_ACCOUNT_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(accountId);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new AWS provider with static credentials",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@aws",
"@serial",
"@PROVIDER-E2E-001",
],
},
async ({ page }) => {
// Validate required environment variables
if (!accountId || !accessKey || !secretKey) {
throw new Error(
"E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, and E2E_AWS_PROVIDER_SECRET_KEY environment variables are not set",
);
}
// 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,
};
// 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();
// Select static credentials type
await providersPage.selectCredentialsType(
AWS_CREDENTIAL_OPTIONS.AWS_CREDENTIALS,
);
await providersPage.verifyCredentialsPageLoaded();
// Fill static credentials
await providersPage.fillStaticCredentials(staticCredentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to provider page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
test(
"should add a new AWS provider with assume role credentials with Access Key and Secret Key",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@aws",
"@serial",
"@PROVIDER-E2E-002",
],
},
async ({ page }) => {
// Validate required environment variables
if (!accountId || !accessKey || !secretKey || !roleArn) {
throw new Error(
"E2E_AWS_PROVIDER_ACCOUNT_ID, E2E_AWS_PROVIDER_ACCESS_KEY, E2E_AWS_PROVIDER_SECRET_KEY, and E2E_AWS_PROVIDER_ROLE_ARN environment variables are not set",
);
}
// Prepare test data for AWS provider
const awsProviderData: AWSProviderData = {
accountId: accountId,
alias: "Test E2E AWS Account - Credentials",
};
// Prepare role-based credentials
const roleCredentials: AWSProviderCredential = {
type: AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN,
accessKeyId: accessKey,
secretAccessKey: secretKey,
roleArn: roleArn,
};
// 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();
// Select role credentials type
await providersPage.selectCredentialsType(
AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN,
);
await providersPage.verifyCredentialsPageLoaded();
// Fill role credentials
await providersPage.fillRoleCredentials(roleCredentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to provider page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
});
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;
const clientId = process.env.E2E_AZURE_CLIENT_ID;
const clientSecret = process.env.E2E_AZURE_SECRET_ID;
const tenantId = process.env.E2E_AZURE_TENANT_ID;
// Validate required environment variables
if (!subscriptionId || !clientId || !clientSecret || !tenantId) {
throw new Error(
"E2E_AZURE_SUBSCRIPTION_ID, E2E_AZURE_CLIENT_ID, E2E_AZURE_SECRET_ID, and E2E_AZURE_TENANT_ID 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(subscriptionId);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new Azure provider with static credentials",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@azure",
"@serial",
"@PROVIDER-E2E-003",
],
},
async ({ page }) => {
// Prepare test data for AZURE provider
const azureProviderData: AZUREProviderData = {
subscriptionId: subscriptionId,
alias: "Test E2E AZURE Account - Credentials",
};
// Prepare static credentials
const azureCredentials: AZUREProviderCredential = {
type: AZURE_CREDENTIAL_OPTIONS.AZURE_CREDENTIALS,
clientId: clientId,
clientSecret: clientSecret,
tenantId: tenantId,
};
// Navigate to providers page
await providersPage.goto();
await providersPage.verifyPageLoaded();
// Start adding new provider
await providersPage.clickAddProvider();
await providersPage.verifyConnectAccountPageLoaded();
// Select AZURE provider
await providersPage.selectAZUREProvider();
// Fill provider details
await providersPage.fillAZUREProviderDetails(azureProviderData);
await providersPage.clickNext();
// Fill static credentials details
await providersPage.fillAZURECredentials(azureCredentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
});
test.describe.serial("Add M365 Provider", () => {
// Providers page object
let providersPage: ProvidersPage;
let scansPage: ScansPage;
// Test data from environment variables
const domainId = process.env.E2E_M365_DOMAIN_ID;
const clientId = process.env.E2E_M365_CLIENT_ID;
const tenantId = process.env.E2E_M365_TENANT_ID;
// Validate required environment variables
if (!domainId || !clientId || !tenantId) {
throw new Error(
"E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, and E2E_M365_TENANT_ID 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(domainId);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new M365 provider with static credentials",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@m365",
"@serial",
"@PROVIDER-E2E-004",
],
},
async ({ page }) => {
// Validate required environment variables
const clientSecret = process.env.E2E_M365_SECRET_ID;
if (!clientSecret) {
throw new Error("E2E_M365_SECRET_ID environment variable is not set");
}
// Prepare test data for M365 provider
const m365ProviderData: M365ProviderData = {
domainId: domainId,
alias: "Test E2E M365 Account - Credentials",
};
// Prepare static credentials
const m365Credentials: M365ProviderCredential = {
type: M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS,
clientId: clientId,
clientSecret: clientSecret,
tenantId: tenantId,
};
// 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.selectM365Provider();
// Fill provider details
await providersPage.fillM365ProviderDetails(m365ProviderData);
await providersPage.clickNext();
// Select static credentials type
await providersPage.selectM365CredentialsType(
M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS,
);
// Verify M365 credentials page is loaded
await providersPage.verifyM365CredentialsPageLoaded();
// Fill static credentials details
await providersPage.fillM365Credentials(m365Credentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
test(
"should add a new M365 provider with certificate",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@m365",
"@serial",
"@PROVIDER-E2E-005",
],
},
async ({ page }) => {
// Validate required environment variables
const certificateContent = process.env.E2E_M365_CERTIFICATE_CONTENT;
if (!certificateContent) {
throw new Error(
"E2E_M365_CERTIFICATE_CONTENT environment variable is not set",
);
}
// Prepare test data for M365 provider
const m365ProviderData: M365ProviderData = {
domainId: domainId,
alias: "Test E2E M365 Account - Certificate",
};
// Prepare static credentials
const m365Credentials: M365ProviderCredential = {
type: M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS,
clientId: clientId,
tenantId: tenantId,
certificateContent: certificateContent,
};
// 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.selectM365Provider();
// Fill provider details
await providersPage.fillM365ProviderDetails(m365ProviderData);
await providersPage.clickNext();
// Select static credentials type
await providersPage.selectM365CredentialsType(
M365_CREDENTIAL_OPTIONS.M365_CERTIFICATE_CREDENTIALS,
);
// Verify M365 certificate credentials page is loaded
await providersPage.verifyM365CertificateCredentialsPageLoaded();
// Fill static credentials details
await providersPage.fillM365CertificateCredentials(m365Credentials);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
});
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();
},
);
});
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();
},
);
});
test.describe.serial("Add GitHub Provider", () => {
// Providers page object
let providersPage: ProvidersPage;
let scansPage: ScansPage;
test.describe("Add GitHub provider with username", () => {
// Test data from environment variables
const username = process.env.E2E_GITHUB_USERNAME;
// Validate required environment variables
if (!username) {
throw new Error("E2E_GITHUB_USERNAME 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(username);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new GitHub provider with personal access token",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@github",
"@serial",
"@PROVIDER-E2E-008",
],
},
async ({ page }) => {
// Validate required environment variables
const personalAccessToken =
process.env.E2E_GITHUB_PERSONAL_ACCESS_TOKEN;
// Verify username and personal access token are set in environment variables
if (!personalAccessToken) {
throw new Error(
"E2E_GITHUB_PERSONAL_ACCESS_TOKEN environment variables are not set",
);
}
// Prepare test data for GitHub provider
const githubProviderData: GitHubProviderData = {
username: username,
alias: "Test E2E GitHub Account - Personal Access Token",
};
// Prepare personal access token credentials
const githubCredentials: GitHubProviderCredential = {
type: GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN,
personalAccessToken: personalAccessToken,
};
// Navigate to providers page
await providersPage.goto();
await providersPage.verifyPageLoaded();
// Start adding new provider
await providersPage.clickAddProvider();
await providersPage.verifyConnectAccountPageLoaded();
// Select GitHub provider
await providersPage.selectGitHubProvider();
// Fill provider details
await providersPage.fillGitHubProviderDetails(githubProviderData);
await providersPage.clickNext();
// Select GitHub personal access token credentials type
await providersPage.selectGitHubCredentialsType(
GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN,
);
// Verify GitHub personal access token page is loaded
await providersPage.verifyGitHubPersonalAccessTokenPageLoaded();
// Fill static personal access token details
await providersPage.fillGitHubPersonalAccessTokenCredentials(
githubCredentials,
);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
test(
"should add a new GitHub provider with github app",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@github",
"@serial",
"@PROVIDER-E2E-009",
],
},
async ({ page }) => {
// Validate required environment variables
const githubAppId =
process.env.E2E_GITHUB_APP_ID;
const githubAppPrivateKeyB64 =
process.env.E2E_GITHUB_BASE64_APP_PRIVATE_KEY;
// Verify github app id and private key are set in environment variables
if (!githubAppId || !githubAppPrivateKeyB64) {
throw new Error(
"E2E_GITHUB_APP_ID and E2E_GITHUB_APP_PRIVATE_KEY environment variables are not set",
);
}
// Decode github app private key from base64
const githubAppPrivateKey = Buffer.from(
githubAppPrivateKeyB64,
"base64",
).toString("utf8");
// Prepare test data for GitHub provider
const githubProviderData: GitHubProviderData = {
username: username,
alias: "Test E2E GitHub Account - GitHub App",
};
// Prepare github app credentials
const githubCredentials: GitHubProviderCredential = {
type: GITHUB_CREDENTIAL_OPTIONS.GITHUB_APP,
githubAppId: githubAppId,
githubAppPrivateKey: githubAppPrivateKey,
};
// Navigate to providers page
await providersPage.goto();
await providersPage.verifyPageLoaded();
// Start adding new provider
await providersPage.clickAddProvider();
await providersPage.verifyConnectAccountPageLoaded();
// Select GitHub provider
await providersPage.selectGitHubProvider();
// Fill provider details
await providersPage.fillGitHubProviderDetails(githubProviderData);
await providersPage.clickNext();
// Select static github app credentials type
await providersPage.selectGitHubCredentialsType(
GITHUB_CREDENTIAL_OPTIONS.GITHUB_APP,
);
// Verify GitHub github app page is loaded
await providersPage.verifyGitHubAppPageLoaded();
// Fill static github app credentials details
await providersPage.fillGitHubAppCredentials(
githubCredentials,
);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
});
test.describe("Add GitHub provider with organization", () => {
// Test data from environment variables
const organization = process.env.E2E_GITHUB_ORGANIZATION;
// Validate required environment variables
if (!organization) {
throw new Error(
"E2E_GITHUB_ORGANIZATION 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(organization);
});
// Use admin authentication for provider management
test.use({ storageState: "playwright/.auth/admin_user.json" });
test(
"should add a new GitHub provider with organization personal access token",
{
tag: [
"@critical",
"@e2e",
"@providers",
"@github",
"@serial",
"@PROVIDER-E2E-010",
],
},
async ({ page }) => {
// Validate required environment variables
const organizationAccessToken =
process.env.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN;
// Verify username and personal access token are set in environment variables
if (!organizationAccessToken) {
throw new Error(
"E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN environment variables are not set",
);
}
// Prepare test data for GitHub provider
const githubProviderData: GitHubProviderData = {
username: organization,
alias: "Test E2E GitHub Account - Organization Access Token",
};
// Prepare personal access token credentials
const githubCredentials: GitHubProviderCredential = {
type: GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN,
personalAccessToken: organizationAccessToken,
};
// Navigate to providers page
await providersPage.goto();
await providersPage.verifyPageLoaded();
// Start adding new provider
await providersPage.clickAddProvider();
await providersPage.verifyConnectAccountPageLoaded();
// Select GitHub provider
await providersPage.selectGitHubProvider();
// Fill provider details
await providersPage.fillGitHubProviderDetails(githubProviderData);
await providersPage.clickNext();
// Select GitHub organization personal access token credentials type
await providersPage.selectGitHubCredentialsType(
GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN,
);
// Verify GitHub personal access token page is loaded
await providersPage.verifyGitHubPersonalAccessTokenPageLoaded();
// Fill static personal access token details
await providersPage.fillGitHubPersonalAccessTokenCredentials(
githubCredentials,
);
await providersPage.clickNext();
// Launch scan
await providersPage.verifyLaunchScanPageLoaded();
await providersPage.clickNext();
// Wait for redirect to scan page
scansPage = new ScansPage(page);
await scansPage.verifyPageLoaded();
},
);
});
});
});

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

View File

@@ -4,6 +4,7 @@ import { authenticateAndSaveState } from '@/tests/helpers';
const adminUserFile = 'playwright/.auth/admin_user.json';
authAdminSetup('authenticate as admin e2e user', async ({ page }) => {
const adminEmail = process.env.E2E_ADMIN_USER;
const adminPassword = process.env.E2E_ADMIN_PASSWORD;

View File

@@ -6,7 +6,7 @@ const manageCloudProvidersUserFile = 'playwright/.auth/manage_cloud_providers_us
authManageCloudProvidersSetup('authenticate as manage cloud providers e2e user', async ({ page }) => {
const cloudProvidersEmail = process.env.E2E_MANAGE_CLOUD_PROVIDERS_USER;
const cloudProvidersPassword = process.env.E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD;
if (!cloudProvidersEmail || !cloudProvidersPassword) {
throw new Error('E2E_MANAGE_CLOUD_PROVIDERS_USER and E2E_MANAGE_CLOUD_PROVIDERS_PASSWORD environment variables are required');
}

View File

@@ -20,7 +20,7 @@ import {
ERROR_MESSAGES,
URLS,
verifyLoadingState,
} from "./helpers";
} from "../helpers";
test.describe("Login Flow", () => {
test.beforeEach(async ({ page }) => {

View File

@@ -1,5 +1,6 @@
import { Page, Locator, expect } from "@playwright/test";
import { HomePage } from "./home-page";
import { BasePage } from "../base-page";
import { HomePage } from "../home/home-page";
export interface SignInCredentials {
email: string;
@@ -11,8 +12,7 @@ export interface SocialAuthConfig {
githubEnabled: boolean;
}
export class SignInPage {
readonly page: Page;
export class SignInPage extends BasePage {
readonly homePage: HomePage;
// Form elements
@@ -31,59 +31,48 @@ export class SignInPage {
readonly backButton: Locator;
// UI elements
readonly title: Locator;
readonly logo: Locator;
readonly themeToggle: Locator;
// Error messages
readonly errorMessages: Locator;
readonly loadingIndicator: Locator;
// SAML specific elements
readonly samlModeTitle: Locator;
readonly samlEmailInput: Locator;
constructor(page: Page) {
this.page = page;
super(page);
this.homePage = new HomePage(page);
// Form elements
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");
this.emailInput = page.getByRole("textbox", { name: "Email" });
this.passwordInput = page.getByRole("textbox", { name: "Password" });
this.loginButton = page.getByRole("button", { name: "Log in" });
this.form = page.locator("form");
// Social authentication buttons
this.googleButton = page.getByText("Continue with Google");
this.githubButton = page.getByText("Continue with Github");
this.samlButton = page.getByText("Continue with SAML SSO");
this.googleButton = page.getByRole("button", { name: "Continue with Google" });
this.githubButton = page.getByRole("button", { name: "Continue with Github" });
this.samlButton = page.getByRole("button", { name: "Continue with SAML SSO" });
// Navigation elements
this.signUpLink = page.getByRole("link", { name: "Sign up" });
this.backButton = page.getByText("Back");
this.backButton = page.getByRole("button", { name: "Back" });
// UI elements
this.title = page.getByText("Sign in", { exact: true });
this.logo = page.locator('svg[width="300"]');
this.themeToggle = page.getByLabel("Toggle theme");
// Error messages
this.errorMessages = page.locator('[role="alert"], .error-message, [data-testid="error"]');
this.loadingIndicator = page.getByText("Loading");
// SAML specific elements
this.samlModeTitle = page.getByText("Sign in with SAML SSO");
this.samlEmailInput = page.getByLabel("Email");
this.samlModeTitle = page.getByRole("heading", { name: "Sign in with SAML SSO" });
this.samlEmailInput = page.getByRole("textbox", { name: "Email" });
}
// Navigation methods
async goto(): Promise<void> {
await this.page.goto("/sign-in");
await this.waitForPageLoad();
}
async waitForPageLoad(): Promise<void> {
await this.page.waitForLoadState("networkidle");
await super.goto("/sign-in");
}
// Form interaction methods
@@ -148,8 +137,7 @@ export class SignInPage {
async verifyPageLoaded(): Promise<void> {
await expect(this.page).toHaveTitle(/Prowler/);
await expect(this.logo).toBeVisible();
await expect(this.title).toBeVisible();
await this.waitForPageLoad();
await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible();
}
async verifyFormElements(): Promise<void> {
@@ -169,7 +157,7 @@ export class SignInPage {
}
async verifyNavigationLinks(): Promise<void> {
await expect(this.page.getByText("Need to create an account?")).toBeVisible();
await expect(this.page.getByRole('link', { name: /Need to create an account\?/i })).toBeVisible();
await expect(this.signUpLink).toBeVisible();
}
@@ -178,7 +166,7 @@ export class SignInPage {
}
async verifyLoginError(errorMessage: string = "Invalid email or password"): Promise<void> {
await expect(this.page.getByText(errorMessage).first()).toBeVisible();
await expect(this.page.getByRole("alert", { name: errorMessage })).toBeVisible();
await expect(this.page).toHaveURL("/sign-in");
}
@@ -189,19 +177,19 @@ export class SignInPage {
}
async verifyNormalModeActive(): Promise<void> {
await expect(this.title).toBeVisible();
await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible();
await expect(this.passwordInput).toBeVisible();
}
async verifyLoadingState(): Promise<void> {
await expect(this.loginButton).toHaveAttribute("aria-disabled", "true");
await expect(this.loadingIndicator).toBeVisible();
await super.verifyLoadingState();
}
async verifyFormValidation(): Promise<void> {
// Check for common validation messages
const emailError = this.page.getByText("Please enter a valid email address.");
const passwordError = this.page.getByText("Password is required.");
const emailError = this.page.getByRole("alert", { name: "Please enter a valid email address." });
const passwordError = this.page.getByRole("alert", { name: "Password is required." });
// At least one validation error should be visible
await expect(emailError.or(passwordError)).toBeVisible();
@@ -240,30 +228,7 @@ export class SignInPage {
return emailValue.length > 0 && passwordValue.length > 0;
}
async getFormErrors(): Promise<string[]> {
const errorElements = await this.errorMessages.all();
const errors: string[] = [];
for (const element of errorElements) {
const text = await element.textContent();
if (text) {
errors.push(text.trim());
}
}
return errors;
}
// Browser interaction methods
async refresh(): Promise<void> {
await this.page.reload();
await this.waitForPageLoad();
}
async goBack(): Promise<void> {
await this.page.goBack();
await this.waitForPageLoad();
}
// Session management methods
async logout(): Promise<void> {
@@ -272,7 +237,7 @@ export class SignInPage {
async verifyLogoutSuccess(): Promise<void> {
await expect(this.page).toHaveURL("/sign-in");
await expect(this.title).toBeVisible();
await expect(this.page.getByRole("heading", { name: "Sign in", exact: true })).toBeVisible();
}
// Advanced interaction methods
@@ -295,7 +260,7 @@ export class SignInPage {
// Error handling methods
async handleSamlError(): Promise<void> {
const samlError = this.page.getByText("SAML Authentication Error");
const samlError = this.page.getByRole("alert", { name: "SAML Authentication Error" });
if (await samlError.isVisible()) {
// Handle SAML error if present
console.log("SAML authentication error detected");

View File

@@ -0,0 +1,117 @@
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "../base-page";
export interface SignUpData {
name: string;
email: string;
password: string;
confirmPassword: string;
company?: string;
invitationToken?: string | null;
acceptTerms?: boolean;
}
export class SignUpPage extends BasePage {
// Form inputs
readonly nameInput: Locator;
readonly companyInput: Locator;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly confirmPasswordInput: Locator;
readonly invitationTokenInput: Locator;
// UI elements
readonly submitButton: Locator;
readonly loginLink: Locator;
readonly termsCheckbox: Locator;
constructor(page: Page) {
super(page);
// Prefer stable name attributes to avoid label ambiguity in composed inputs
this.nameInput = page.locator('input[name="name"]');
this.companyInput = page.locator('input[name="company"]');
this.emailInput = page.getByRole("textbox", { name: "Email" });
this.passwordInput = page.locator('input[name="password"]');
this.confirmPasswordInput = page.locator('input[name="confirmPassword"]');
this.invitationTokenInput = page.locator('input[name="invitationToken"]');
this.submitButton = page.getByRole("button", { name: "Sign up" });
this.loginLink = page.getByRole("link", { name: "Log in" });
this.termsCheckbox = page.getByRole("checkbox", { name: /I agree with the/i });
}
async goto(): Promise<void> {
await super.goto("/sign-up");
}
async verifyPageLoaded(): Promise<void> {
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> {
await this.nameInput.fill(name);
}
async fillCompany(company?: string): Promise<void> {
if (company) {
await this.companyInput.fill(company);
}
}
async fillEmail(email: string): Promise<void> {
await this.emailInput.fill(email);
}
async fillPassword(password: string): Promise<void> {
await this.passwordInput.fill(password);
}
async fillConfirmPassword(confirmPassword: string): Promise<void> {
await this.confirmPasswordInput.fill(confirmPassword);
}
async fillInvitationToken(token?: string | null): Promise<void> {
if (token) {
await this.invitationTokenInput.fill(token);
}
}
async acceptTermsIfPresent(accept: boolean = true): Promise<void> {
// Only in cloud env; check presence before interacting
if (await this.termsCheckbox.isVisible()) {
if (accept) {
await this.termsCheckbox.click();
}
}
}
async submit(): Promise<void> {
await this.submitButton.click();
}
async signup(data: SignUpData): Promise<void> {
await this.fillName(data.name);
await this.fillCompany(data.company);
await this.fillEmail(data.email);
await this.fillPassword(data.password);
await this.fillConfirmPassword(data.confirmPassword);
await this.fillInvitationToken(data.invitationToken ?? undefined);
await this.acceptTermsIfPresent(data.acceptTerms ?? true);
await this.submit();
}
async verifyRedirectToLogin(): Promise<void> {
await expect(this.page).toHaveURL("/sign-in");
}
async verifyRedirectToEmailVerification(): Promise<void> {
await expect(this.page).toHaveURL("/email-verification");
}
}

View File

@@ -0,0 +1,43 @@
### E2E Tests: User Sign-Up
**Suite ID:** `SIGNUP-E2E`
**Feature:** New user registration flow.
---
## Test Case: `SIGNUP-E2E-001` - Successful new user registration and login
**Priority:** `critical`
**Tags:**
- type → @e2e
- feature → @signup
**Description/Objetive:** Registers a new user with valid data, verifies redirect to Login (OSS), and confirms the user can authenticate.
**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_USER_PASSWORD` environment variable must be set with a valid password for the test.
### Flow Steps:
1. Navigate to the Sign up page.
2. Fill the form with valid data (unique email, valid password, terms accepted).
3. Submit the form.
4. Verify redirect to the Login page.
5. Log in with the newly created credentials.
### Expected Result:
- Sign-up succeeds and redirects to Login.
- User can log in successfully using the created credentials and reach the home page.
### Key verification points:
- After submitting sign-up, the URL changes to `/sign-in`.
- The newly created credentials can be used to sign in successfully.
- After login, the user lands on the home (`/`) and main content is visible.
### Notes:
- Test data uses a random base36 suffix to avoid collisions with email.
- The test requires the `E2E_NEW_USER_PASSWORD` environment variable to be set before running.

View File

@@ -0,0 +1,49 @@
import { test } from "@playwright/test";
import { SignUpPage } from "./sign-up-page";
import { SignInPage } from "../sign-in/sign-in-page";
import { makeSuffix } from "../helpers";
test.describe("Sign Up Flow", () => {
test(
"should register a new user successfully",
{ tag: ["@critical", "@e2e", "@signup", "@SIGNUP-E2E-001"] },
async ({ page }) => {
const password = process.env.E2E_NEW_USER_PASSWORD;
if (!password) {
throw new Error("E2E_NEW_USER_PASSWORD environment variable is not set");
}
const signUpPage = new SignUpPage(page);
await signUpPage.goto();
// Generate unique test data
const suffix = makeSuffix(10);
const uniqueEmail = `e2e+${suffix}@prowler.com`;
// Fill and submit the sign-up form
await signUpPage.signup({
name: `E2E User ${suffix}`,
company: `Test E2E Co ${suffix}`,
email: uniqueEmail,
password: password,
confirmPassword: password,
acceptTerms: true,
});
// Verify no errors occurred during sign-up
await signUpPage.verifyNoErrors();
// Verify redirect to login page (OSS environment)
await signUpPage.verifyRedirectToLogin();
// Verify the newly created user can log in successfully
const signInPage = new SignInPage(page);
await signInPage.login({
email: uniqueEmail,
password: password,
});
await signInPage.verifySuccessfulLogin();
},
);
});