agskills.dev
MARKETPLACE

prowler-test-ui

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).

prowler-cloud12.9k1.9k

Aperçu

SKILL.md
Metadata
name
prowler-test-ui
description
>
Trigger
When writing Playwright E2E tests under ui/tests in the Prowler UI (Prowler-specific base page/helpers, tags, flows).
license
Apache-2.0
author
prowler-cloud
version
"1.0"
scope
[root, ui]
allowed-tools
Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task

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.

  1. Navigate to target page
  2. Take snapshot to see actual DOM structure
  3. Interact with forms/elements to verify real flow
  4. Document actual selectors from snapshots
  5. 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!

StrategyUse Case
networkidleNEVER - flaky with polling/WebSockets
⚠️ loadOnly when absolutely necessary
expect(element).toBeVisible()PREFERRED - wait for specific UI state
page.waitForURL()Wait for navigation
pageObject.verifyPageLoaded()BEST - encapsulated verification

GOOD:

await homePage.verifyPageLoaded(); await expect(page).toHaveURL("/dashboard"); await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible();

BAD:

await page.waitForLoadState("networkidle"); // ❌ FLAKY await page.waitForTimeout(2000); // ❌ ARBITRARY WAIT

Prowler Base Page

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.


Prowler-Specific Pages

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

Scans Page

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 Tags for Prowler

test("Provider CRUD operations", { tag: ["@critical", "@e2e", "@providers", "@PROV-E2E-001"] }, async ({ page }) => { // ... } );
CategoryTags
Priority@critical, @high, @medium, @low
Type@e2e, @smoke, @regression
Feature@providers, @scans, @findings, @compliance, @signin, @signup
Test ID@PROV-E2E-001, @SCAN-E2E-002

Prowler Test Documentation Template

Keep under 60 lines. Focus on flow, preconditions, expected results only.

### 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}

Commands

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