name: test-frontend-e2e description: > Generates Playwright E2E specs for PwnDoc. Tests run against the full stack (frontend + backend + MongoDB) with pre-authenticated browser sessions. Run with ./pwndoc-cli test --frontend-e2e. context: fork agent: general-purpose allowed-tools: - Bash - Glob - Grep - Read - Write - Edit argument-hint: "[feature] e.g. audits, vulnerabilities, data-clients, settings"
Frontend E2E Test Generator
You generate Playwright E2E tests for PwnDoc. Tests run in Chromium, Firefox, and WebKit against the full application stack.
Arguments
$ARGUMENTS - The feature/flow to test (e.g., "audits", "vulnerabilities", "settings", "data-clients").
Fix Mode
If $ARGUMENTS starts with --fix, the format is:
--fix {spec-file} "{failure summary}" [--no-run]
Example: --fix frontend/tests/e2e/vulnerabilities.spec.js "Timeout waiting for selector [data-testid='vuln-row']" --no-run
In fix mode:
- Read the failing spec file specified in the argument
- Read the page source file(s) being tested (derive from the spec's routes/navigation)
- Read the existing E2E reference:
frontend/tests/e2e/audits-list.spec.js - Analyze the failure message — determine if the issue is a wrong selector, timing, test logic, or data dependency
- Fix the spec file using the Edit tool
- If
--no-runis present, STOP — do not run tests. Otherwise proceed to Step 3 (Run and Fix)
Step 1: Read Source and Reference Files
Before writing any tests, ALWAYS read:
- The page(s) being tested: find Vue files in
frontend/src/pages/related to the feature - Existing E2E reference:
frontend/tests/e2e/audits-list.spec.js - Playwright config:
frontend/tests/playwright.config.js - Auth setup:
frontend/tests/e2e/auth.setup.jsandfrontend/tests/e2e/init-user.setup.js
Understand the page's UI elements, forms, tables, and navigation before writing selectors.
Step 2: Generate the Spec File
Write the spec to frontend/tests/e2e/{feature}.spec.js.
Authentication
Tests are pre-authenticated via setup projects. The Playwright config defines:
init-userproject: registers the first admin userauth-{browser}projects: logs in and savesstorageState.{browser}.json{browser}projects: run*.spec.jsfiles with saved auth state
All *.spec.js files run with authentication already handled. The test user is:
- Username:
admin, Password:Admin123, Role: admin
Test Structure
import { test, expect } from './base.js';
Important: Always import from ./base.js, NOT from @playwright/test. The base fixture auto-saves browser storage state after each test, which is required because PwnDoc rotates refresh tokens on every use. Without this, subsequent tests load stale tokens and get redirected to /login.
See template-page-spec.md for the full test scaffold.
Locator Priority (strict order)
Follow this priority strictly. Use the first one that works — do NOT skip ahead to CSS selectors.
1. getByRole() — Always try first
page.getByRole('button', { name: 'Save' })
page.getByRole('textbox', { name: 'Title' })
page.getByRole('link', { name: 'Settings' })
page.getByRole('tab', { name: 'Languages' })
page.getByRole('row').filter({ hasText: 'row content' })
page.getByRole('cell', { name: 'value' })
page.getByRole('heading', { name: 'Audits' })
page.getByRole('dialog') // scope modals
page.getByRole('toolbar').getByRole('listitem') // scope toolbar
2. getByText() — Non-interactive elements
page.getByText('Vulnerability created successfully')
page.getByText(/already exists/i)
3. getByLabel() — Form controls with labels
page.getByLabel('Description')
page.getByLabel(/Name/)
4. getByPlaceholder() — Inputs with placeholder but no label
page.getByPlaceholder('Search...')
5. getByTestId() — When semantic locators don't work
page.getByTestId('delete-language-btn')
When you need getByTestId, add data-testid to the Vue source file yourself. This is preferred over CSS selectors.
6. CSS selectors — Absolute last resort
Only when the element is truly outside your control and none of the above work. NEVER use Quasar CSS class selectors (.q-dialog, .q-btn, .q-card, .q-table, .q-field, or any .q-* class). These are framework implementation details that break across versions.
Scoping within dialogs/modals
// Scope to a dialog using role (NOT .q-dialog)
const dialog = page.getByRole('dialog');
await dialog.getByLabel('Title').fill('New Item');
await dialog.getByRole('button', { name: 'Create' }).click();
Chaining and filtering
// Filter rows by text content
page.getByRole('row').filter({ hasText: 'Item Name' })
// First matching element
page.getByLabel(/Name/).first()
// Narrow with .and()
page.locator('input').and(page.getByLabel('Language'))
Waiting
- Playwright auto-waits for elements. Use
expect(...).toBeVisible()rather than explicit waits. - For navigation:
await expect(page).toHaveURL('/expected-route') - Never use
page.waitForResponse()— assert visual outcomes instead:- After
page.goto()orpage.reload(): wait for a key UI element that only appears once data is fetched:await page.goto(`/audits/${auditId}/general`); await expect(page.getByLabel(/Name/).first()).not.toHaveValue(''); - After a save action (button click or Ctrl+S): wait for the success toast:
await page.keyboard.press('Control+s'); await expect(page.getByText('Audit updated successfully')).toBeVisible(); - After upload-triggered side-effects (images, files): assert the resulting UI element — the visual assertion implicitly waits for the async operation:
await expect(editor.locator('img')).toBeVisible({ timeout: 5000 });
- After
Multi-browser
- Tests run in 3 browsers (Chromium, Firefox, WebKit)
- Do NOT use browser-specific APIs
- Use standard Playwright locators
Application Routes
| Route | Page | Description |
|---|---|---|
/audits |
Audit List | Main dashboard |
/audits/{id} |
Audit Edit | Multi-tab audit editor |
/vulnerabilities |
Vulnerability List | Vulnerability database |
/data |
Data Management | Overview page |
/data/custom |
Custom Data | Languages, Audit Types, Vuln Types, Sections |
/data/clients |
Clients | Client management |
/data/companies |
Companies | Company management |
/data/templates |
Templates | Report templates |
/data/collaborators |
Collaborators | Collaborator management |
/data/spellcheck |
Spellcheck | Spellcheck dictionary |
/data/languagetool-rules |
LanguageTool Rules | Grammar rules |
/settings |
Settings | Application settings, backups |
/profile |
User Profile | Profile management |
Step 3: Run and Fix
If $ARGUMENTS contains --no-run, STOP HERE. Do not run tests — the calling orchestrator will handle test execution. Strip --no-run from arguments before processing the feature name in earlier steps.
Otherwise, run tests with:
./pwndoc-cli test --frontend-e2e
Do not combine E2E runs with --coverage; coverage mode only supports backend and frontend unit suites.
If tests fail, read the error output, fix the spec, and re-run. Iterate until all tests pass across all browsers.
Reference
See template-page-spec.md for a complete scaffold.