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).
Generic Patterns: For base Playwright patterns (Page Object Model, selectors, helpers), see the playwright skill.
This skill covers Prowler-specific conventions only.
Prowler UI Test Structure
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)
MANDATORY Checklist (Create or Modify Tests)
β οΈ ALWAYS verify BEFORE completing any E2E task:
When CREATING new tests:
{page-name}-page.ts - Page Object created/updated
{page-name}.spec.ts - Tests added with correct tags (@TEST-ID)
{page-name}.md - Documentation created with ALL test cases
Test IDs in .md match tags in .spec.ts
When MODIFYING existing tests:
{page-name}.md MUST be updated if:
Test cases were added/removed
Test flow changed (steps)
Preconditions or expected results changed
Tags or priorities changed
Test IDs synchronized between .md and .spec.ts
Quick validation:
# 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 -u
β An E2E change is NOT considered complete without updating the corresponding .md file
MCP Workflow - CRITICAL
β οΈ MANDATORY: If Playwright MCP tools are available, ALWAYS use them BEFORE creating tests.
Navigate to target page
Take snapshot to see actual DOM structure
Interact with forms/elements to verify real flow
Document actual selectors from snapshots
Only then write test code
Why: Prevents tests based on assumptions. Real exploration = stable tests.
Wait Strategies (CRITICAL)
β οΈ NEVER use networkidle - it causes flaky tests!
import { 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);
}
}
Page Navigation Verification Pattern
β οΈ URL assertions belong in Page Objects, NOT in tests!
When verifying redirects or page navigation, create dedicated methods in the target Page Object:
// β 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();
Naming convention:verifyOn{PageName}Page() for redirect verification methods.
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
Resources
Documentation: See references/ for links to local developer guide