mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-25 04:44:20 +00:00
test(ui): update providers page object for modal wizard
This commit is contained in:
1 file changed
+166
-111
@@ -194,6 +194,9 @@ export interface AlibabaCloudProviderCredential {
|
||||
|
||||
// Providers page
|
||||
export class ProvidersPage extends BasePage {
|
||||
readonly wizardModal: Locator;
|
||||
readonly wizardTitle: Locator;
|
||||
|
||||
// Alias input
|
||||
readonly aliasInput: Locator;
|
||||
|
||||
@@ -288,6 +291,14 @@ export class ProvidersPage extends BasePage {
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
||||
this.wizardModal = page.getByRole("dialog", {
|
||||
name: /Adding A Cloud Provider/i,
|
||||
});
|
||||
this.wizardTitle = page.getByRole("heading", {
|
||||
name: "Adding A Cloud Provider",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
// Button to add a new cloud provider
|
||||
this.addProviderButton = page.getByRole("link", {
|
||||
name: "Add Cloud Provider",
|
||||
@@ -507,6 +518,25 @@ export class ProvidersPage extends BasePage {
|
||||
await this.addProviderButton.click();
|
||||
}
|
||||
|
||||
async openProviderWizardModal(): Promise<void> {
|
||||
await this.clickAddProvider();
|
||||
await this.verifyWizardModalOpen();
|
||||
}
|
||||
|
||||
async closeProviderWizardModal(): Promise<void> {
|
||||
await this.page.keyboard.press("Escape");
|
||||
await expect(this.wizardModal).not.toBeVisible();
|
||||
}
|
||||
|
||||
async verifyWizardModalOpen(): Promise<void> {
|
||||
await expect(this.wizardModal).toBeVisible();
|
||||
await expect(this.wizardTitle).toBeVisible();
|
||||
}
|
||||
|
||||
async advanceWizardStep(): Promise<void> {
|
||||
await this.clickNext();
|
||||
}
|
||||
|
||||
private async selectProviderRadio(radio: Locator): Promise<void> {
|
||||
// Force click to handle overlay intercepts
|
||||
await radio.click({ force: true });
|
||||
@@ -536,6 +566,59 @@ export class ProvidersPage extends BasePage {
|
||||
await this.selectProviderRadio(this.githubProviderRadio);
|
||||
}
|
||||
|
||||
async selectAWSSingleAccountMethod(): Promise<void> {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
name: "Add A Single AWS Cloud Account",
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async selectAWSOrganizationsMethod(): Promise<void> {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
name: "Add Multiple Accounts With AWS Organizations",
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async verifyOrganizationsAuthenticationStepLoaded(): Promise<void> {
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(
|
||||
this.page.getByRole("heading", {
|
||||
name: /Authentication Details/i,
|
||||
}),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyOrganizationsAccountSelectionStepLoaded(): Promise<void> {
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(
|
||||
this.page.getByText(
|
||||
/Confirm all accounts under this Organization you want to add to Prowler\./i,
|
||||
),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyOrganizationsLaunchStepLoaded(): Promise<void> {
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.page.getByText(/Accounts Connected!/i)).toBeVisible();
|
||||
}
|
||||
|
||||
async chooseOrganizationsScanSchedule(option: "daily" | "single"): Promise<void> {
|
||||
const trigger = this.page.getByRole("combobox");
|
||||
await trigger.click();
|
||||
|
||||
const optionName =
|
||||
option === "single"
|
||||
? "Run a single scan (no recurring schedule)"
|
||||
: "Scan Daily (every 24 hours)";
|
||||
|
||||
await this.page.getByRole("option", { name: optionName }).click();
|
||||
}
|
||||
|
||||
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
|
||||
// Fill the AWS provider details
|
||||
|
||||
@@ -599,116 +682,83 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
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.
|
||||
await this.verifyWizardModalOpen();
|
||||
|
||||
// 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();
|
||||
const launchScanButton = this.page.getByRole("button", {
|
||||
name: "Launch scan",
|
||||
exact: true,
|
||||
});
|
||||
if (await launchScanButton.isVisible().catch(() => false)) {
|
||||
await launchScanButton.click();
|
||||
await this.handleLaunchScanCompletion();
|
||||
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 actionNames = [
|
||||
"Go to scans",
|
||||
"Authenticate",
|
||||
"Next",
|
||||
"Save",
|
||||
"Check connection",
|
||||
] as const;
|
||||
|
||||
if (await this.saveButton.count()) {
|
||||
await this.saveButton.click();
|
||||
return;
|
||||
}
|
||||
// If "Save" is not present, try clicking the "Next" button
|
||||
if (await this.nextButton.count()) {
|
||||
await this.nextButton.click();
|
||||
for (const actionName of actionNames) {
|
||||
const button = this.page.getByRole("button", {
|
||||
name: actionName,
|
||||
exact: true,
|
||||
});
|
||||
if (await button.isVisible().catch(() => false)) {
|
||||
await button.click();
|
||||
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();
|
||||
|
||||
// Wait for either success (redirect to scans) or error message to appear
|
||||
const errorMessage = this.page
|
||||
.locator(
|
||||
"div.border-border-error, div.bg-red-100, p.text-text-error-primary, p.text-danger",
|
||||
)
|
||||
.first();
|
||||
|
||||
// Helper to check and throw error if visible
|
||||
const checkAndThrowError = async (): Promise<void> => {
|
||||
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"}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Wait up to 15 seconds for either the error message or redirect
|
||||
await Promise.race([
|
||||
errorMessage.waitFor({ state: "visible", timeout: 15000 }),
|
||||
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())) {
|
||||
await checkAndThrowError();
|
||||
}
|
||||
} catch (error) {
|
||||
await checkAndThrowError();
|
||||
throw error;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback logic: try finding any common primary action buttons in expected order
|
||||
const candidates: Array<{ name: string | RegExp; exact?: boolean }> = [
|
||||
{ name: "Next", exact: true }, // Try the "Next" button (exact match to avoid Next.js dev tools)
|
||||
{ name: "Save", exact: true }, // Try the "Save" button
|
||||
{ name: "Launch scan" }, // Try the "Launch scan" button
|
||||
{ name: /Continue|Proceed/i }, // Try "Continue" or "Proceed" (case-insensitive)
|
||||
];
|
||||
|
||||
// Try each candidate name and click it if found
|
||||
for (const candidate of candidates) {
|
||||
// Exclude Next.js dev tools button by filtering out buttons with aria-haspopup attribute
|
||||
const btn = this.page
|
||||
.getByRole("button", {
|
||||
name: candidate.name,
|
||||
exact: candidate.exact,
|
||||
})
|
||||
.and(this.page.locator(":not([aria-haspopup])"));
|
||||
|
||||
if (await btn.count()) {
|
||||
await btn.click();
|
||||
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",
|
||||
"Could not find an actionable primary button in the provider wizard modal.",
|
||||
);
|
||||
}
|
||||
|
||||
async selectCredentialsType(type: AWSCredentialType): Promise<void> {
|
||||
// Ensure we are on the add-credentials page where the selector exists
|
||||
private async handleLaunchScanCompletion(): Promise<void> {
|
||||
const errorMessage = this.page
|
||||
.locator(
|
||||
"div.border-border-error, div.bg-red-100, p.text-text-error-primary, p.text-danger",
|
||||
)
|
||||
.first();
|
||||
const goToScansButton = this.page.getByRole("button", {
|
||||
name: "Go to scans",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
|
||||
try {
|
||||
await Promise.race([
|
||||
this.page.waitForURL(/\/scans/, { timeout: 30000 }),
|
||||
goToScansButton.waitFor({ state: "visible", timeout: 30000 }),
|
||||
errorMessage.waitFor({ state: "visible", timeout: 30000 }),
|
||||
]);
|
||||
} catch {
|
||||
// Continue and inspect visible state below.
|
||||
}
|
||||
|
||||
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"}`,
|
||||
);
|
||||
}
|
||||
|
||||
const isGoToScansVisible = await goToScansButton
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
if (isGoToScansVisible) {
|
||||
await goToScansButton.click();
|
||||
await this.page.waitForURL(/\/scans/, { timeout: 30000 });
|
||||
}
|
||||
}
|
||||
|
||||
async selectCredentialsType(type: AWSCredentialType): Promise<void> {
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.roleCredentialsRadio).toBeVisible();
|
||||
|
||||
if (type === AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN) {
|
||||
await this.roleCredentialsRadio.click({ force: true });
|
||||
@@ -720,9 +770,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
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/);
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.m365StaticCredentialsRadio).toBeVisible();
|
||||
|
||||
if (type === M365_CREDENTIAL_OPTIONS.M365_CREDENTIALS) {
|
||||
await this.m365StaticCredentialsRadio.click({ force: true });
|
||||
@@ -734,9 +783,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
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/);
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.gcpServiceAccountRadio).toBeVisible();
|
||||
if (type === GCP_CREDENTIAL_OPTIONS.GCP_SERVICE_ACCOUNT) {
|
||||
await this.gcpServiceAccountRadio.click({ force: true });
|
||||
} else {
|
||||
@@ -745,9 +793,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
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/);
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.githubPersonalAccessTokenRadio).toBeVisible();
|
||||
|
||||
if (type === GITHUB_CREDENTIAL_OPTIONS.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
||||
await this.githubPersonalAccessTokenRadio.click({ force: true });
|
||||
@@ -960,9 +1007,8 @@ export class ProvidersPage extends BasePage {
|
||||
async selectAlibabaCloudCredentialsType(
|
||||
type: AlibabaCloudCredentialType,
|
||||
): Promise<void> {
|
||||
// Ensure we are on the add-credentials page where the selector exists
|
||||
|
||||
await expect(this.page).toHaveURL(/\/providers\/add-credentials/);
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.alibabacloudStaticCredentialsRadio).toBeVisible();
|
||||
|
||||
if (type === ALIBABACLOUD_CREDENTIAL_OPTIONS.ALIBABACLOUD_CREDENTIALS) {
|
||||
await this.alibabacloudStaticCredentialsRadio.click({ force: true });
|
||||
@@ -1047,6 +1093,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Verify the connect account page is loaded
|
||||
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.awsProviderRadio).toBeVisible();
|
||||
await expect(this.ociProviderRadio).toBeVisible();
|
||||
await expect(this.gcpProviderRadio).toBeVisible();
|
||||
@@ -1061,6 +1108,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Verify the credentials page is loaded
|
||||
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.roleCredentialsRadio).toBeVisible();
|
||||
}
|
||||
|
||||
@@ -1115,7 +1163,7 @@ export class ProvidersPage extends BasePage {
|
||||
// Verify the launch scan page is loaded
|
||||
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await expect(this.page).toHaveURL(/\/providers\/test-connection/);
|
||||
await this.verifyWizardModalOpen();
|
||||
|
||||
// Verify the Launch scan button is visible
|
||||
const launchScanButton = this.page
|
||||
@@ -1202,12 +1250,19 @@ export class ProvidersPage extends BasePage {
|
||||
async verifyUpdateCredentialsPageLoaded(): Promise<void> {
|
||||
// Verify the update credentials page is loaded
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await expect(this.page).toHaveURL(/\/providers\/update-credentials/);
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(
|
||||
this.page.getByRole("button", { name: "Authenticate", exact: true }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyTestConnectionPageLoaded(): Promise<void> {
|
||||
// Verify the test connection page is loaded
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await expect(this.page).toHaveURL(/\/providers\/test-connection/);
|
||||
await this.verifyWizardModalOpen();
|
||||
const testConnectionAction = this.page
|
||||
.getByRole("button", { name: "Launch scan", exact: true })
|
||||
.or(this.page.getByRole("button", { name: "Check connection", exact: true }));
|
||||
await expect(testConnectionAction).toBeVisible();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user