Loading...
Loading...
E2E testing patterns for Prowler UI (Playwright). Trigger: When writing Playwright E2E tests under ui/tests in the Prowler UI (Prowler-specific base page/helpers, tags, flows).
npx skill4agent add prowler-cloud/prowler prowler-test-uiGeneric Patterns: For base Playwright patterns (Page Object Model, selectors, helpers), see theskill. This skill covers Prowler-specific conventions only.playwright
ui/tests/
├── base-page.ts # Prowler-specific base page
├── helpers.ts # Prowler test utilities
└── {page-name}/
├── {page-name}-page.ts # Page Object Model
├── {page-name}.spec.ts # ALL tests (single file per feature)
└── {page-name}.md # Test documentation (MANDATORY - sync with spec.ts){page-name}-page.ts{page-name}.spec.ts{page-name}.md.md.spec.ts{page-name}.md.md.spec.ts# Verify .md exists for each test folder
ls ui/tests/{feature}/{feature}.md
# Verify test IDs match
grep -o "@[A-Z]*-E2E-[0-9]*" ui/tests/{feature}/{feature}.spec.ts | sort -u
grep -o "\`[A-Z]*-E2E-[0-9]*\`" ui/tests/{feature}/{feature}.md | sort -unetworkidle| Strategy | Use Case |
|---|---|
❌ | NEVER - flaky with polling/WebSockets |
⚠️ | Only when absolutely necessary |
✅ | PREFERRED - wait for specific UI state |
✅ | Wait for navigation |
✅ | BEST - encapsulated verification |
await homePage.verifyPageLoaded();
await expect(page).toHaveURL("/dashboard");
await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible();await page.waitForLoadState("networkidle"); // ❌ FLAKY
await page.waitForTimeout(2000); // ❌ ARBITRARY WAITimport { Page, Locator, expect } from "@playwright/test";
export class BasePage {
constructor(protected page: Page) {}
async goto(path: string): Promise<void> {
await this.page.goto(path);
// Child classes should override verifyPageLoaded() to wait for specific elements
}
// Override in child classes to wait for page-specific elements
async verifyPageLoaded(): Promise<void> {
await expect(this.page.locator("main")).toBeVisible();
}
// Prowler-specific: notification handling
async waitForNotification(): Promise<Locator> {
const notification = this.page.locator('[role="status"]');
await notification.waitFor({ state: "visible" });
return notification;
}
async verifyNotificationMessage(message: string): Promise<void> {
const notification = await this.waitForNotification();
await expect(notification).toContainText(message);
}
}// ✅ GOOD - In SignInPage
async verifyOnSignInPage(): Promise<void> {
await expect(this.page).toHaveURL(/\/sign-in/);
await expect(this.pageTitle).toBeVisible();
}
// ✅ GOOD - In test
await homePage.goto(); // Try to access protected route
await signInPage.verifyOnSignInPage(); // Verify redirect
// ❌ BAD - Direct assertions in test
await homePage.goto();
await expect(page).toHaveURL(/\/sign-in/); // Should be in Page Object
await expect(page.getByText("Sign in")).toBeVisible();verifyOn{PageName}Page()import { BasePage } from "../base-page";
export class ProvidersPage extends BasePage {
readonly addButton = this.page.getByRole("button", { name: "Add Provider" });
readonly providerTable = this.page.getByRole("table");
async goto(): Promise<void> {
await super.goto("/providers");
}
async addProvider(type: string, alias: string): Promise<void> {
await this.addButton.click();
await this.page.getByLabel("Provider Type").selectOption(type);
await this.page.getByLabel("Alias").fill(alias);
await this.page.getByRole("button", { name: "Create" }).click();
}
}export class ScansPage extends BasePage {
readonly newScanButton = this.page.getByRole("button", { name: "New Scan" });
readonly scanTable = this.page.getByRole("table");
async goto(): Promise<void> {
await super.goto("/scans");
}
async startScan(providerAlias: string): Promise<void> {
await this.newScanButton.click();
await this.page.getByRole("combobox", { name: "Provider" }).click();
await this.page.getByRole("option", { name: providerAlias }).click();
await this.page.getByRole("button", { name: "Start Scan" }).click();
}
}test("Provider CRUD operations",
{ tag: ["@critical", "@e2e", "@providers", "@PROV-E2E-001"] },
async ({ page }) => {
// ...
}
);| Category | Tags |
|---|---|
| Priority | |
| Type | |
| Feature | |
| Test ID | |
### E2E Tests: {Feature Name}
**Suite ID:** `{SUITE-ID}`
**Feature:** {Feature description}
---
## Test Case: `{TEST-ID}` - {Test case title}
**Priority:** `{critical|high|medium|low}`
**Tags:** @e2e, @{feature-name}
**Preconditions:**
- {Prerequisites}
### Flow Steps:
1. {Step}
2. {Step}
### Expected Result:
- {Outcome}
### Key Verification Points:
- {Assertion}cd ui && pnpm run test:e2e # All tests
cd ui && pnpm run test:e2e tests/providers/ # Specific folder
cd ui && pnpm run test:e2e --grep "provider" # By pattern
cd ui && pnpm run test:e2e:ui # With UI
cd ui && pnpm run test:e2e:debug # Debug mode
cd ui && pnpm run test:e2e:headed # See browser
cd ui && pnpm run test:e2e:report # Generate report