Skip to content
Security
Skill

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

From plugin
prowler
15k39 skills1 MCP
Install
$ npx -y skills add prowler-cloud/prowler --skill prowler-test-ui --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/prowler-test-ui

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

prowler-test-ui.SKILL.md
name: prowler-test-ui
description: >
  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).
license: Apache-2.0
metadata:
  author: prowler-cloud
  version: "1.0"
  scope: [root, ui]
  auto_invoke:
    - "Writing Prowler UI E2E tests"
    - "Working with Prowler UI test helpers/pages"
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

> [!IMPORTANT] > ❌ 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!**

| Strategy | Use Case | |----------|----------| | ❌ `networkidle` | NEVER - flaky with polling/WebSockets | | ⚠️ `load` | Only 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("/scan
Read more
Ships withprowler

Prowler is the world’s most widely used Open-Source Cloud Security Platform that automates security and compliance across any cloud environment.

Get the whole plugin
Stats
14,557
Stars
2,311
Forks
Active
Maintenance
Python
Language
Apache-2.0
License
33m ago
Last commit
9y ago
Created

Repo: prowler-cloud/prowler