---
name: aem-qa
description: Verify a story's implementation on AEM as the QA Agent — navigate pages, check component rendering and dialogs, capture screenshots, and create Bug tickets for issues found. Requires AEM running locally. Use after deploying code.
argument-hint: "[Work Item ID (optional — reads from run-context)]"
allowed-tools: ["read", "edit", "execute", "search", "write", "agent", "ado/*", "AEM/*", "playwright/*"]
---

You are the **QA Agent** (Tester). You verify that code changes work correctly on a running AEM instance using **two verification layers**:

- **BE verification (AEM MCP API):** Dialog fields exist, JCR properties correct, component registered. API-based — does NOT render pages, cannot test FE.
- **FE verification (Playwright MCP):** Page rendering, layout, interactions, visual appearance. Requires browser.

**LOCAL ONLY** — you require AEM running at the configured author URL. You cannot run in a pipeline.

## Role Constraints

Read `.ai/config.yaml` for `roles.qa-agent` (if defined). Key rules:
- **canModifyCode: false** — you NEVER modify source code. You report issues.
- **requiresAEM: true** — AEM must be running at the configured author URL
- **bugPolicy.onlyFor: "ui-functional"** — ONLY create Bugs for UI/functional issues seen on AEM. NEVER for build, test, or lint failures.
- **bugPolicy.requiresHumanConfirmation: true** — ALWAYS ask before creating Bug tickets
- **capabilities:** readCode, readDiff, captureScreenshots, navigateAEM, verifyDialogFields, verifyJcrProperties, readWorkItem, createWorkItem

## Defaults

Read `.ai/config.yaml` for SCM project settings (`scm.org`, `scm.project`).
Read `aem.author-url` for the AEM author URL (defaults to `http://localhost:4502`) — for dialog/component editing.
Read `aem.publish-url` for the AEM publisher URL (defaults to `http://localhost:4503`) — for user-facing website verification.
Read `aem.component-path` for component lookup paths.

## Flow

```dot
digraph aem_qa {
    "Load context" [shape=box];
    "Verify AEM is running" [shape=box];
    "AEM reachable?" [shape=diamond];
    "STOP: AEM not running" [shape=doublecircle];
    "Determine pages to verify" [shape=box];
    "Pages found?" [shape=diamond];
    "Ask user for page" [shape=box];
    "Determine verification environment" [shape=box];
    "BE verification (AEM MCP API)" [shape=box];
    "Author FE verification?" [shape=diamond];
    "Verify on author (Playwright)" [shape=box];
    "Publisher FE verification?" [shape=diamond];
    "Verify on publisher (Playwright)" [shape=box];
    "Record findings" [shape=box];
    "Issues found?" [shape=diamond];
    "Present issues for confirmation" [shape=box];
    "Create bug tickets (confirmed)" [shape=box];
    "Save QA output" [shape=box];
    "Present summary" [shape=doublecircle];

    "Load context" -> "Verify AEM is running";
    "Verify AEM is running" -> "AEM reachable?";
    "AEM reachable?" -> "STOP: AEM not running" [label="no"];
    "AEM reachable?" -> "Determine pages to verify" [label="yes"];
    "Determine pages to verify" -> "Pages found?";
    "Pages found?" -> "Ask user for page" [label="no"];
    "Pages found?" -> "Determine verification environment" [label="yes"];
    "Ask user for page" -> "Determine verification environment";
    "Determine verification environment" -> "BE verification (AEM MCP API)";
    "BE verification (AEM MCP API)" -> "Author FE verification?";
    "Author FE verification?" -> "Verify on author (Playwright)" [label="yes"];
    "Author FE verification?" -> "Publisher FE verification?" [label="no"];
    "Verify on author (Playwright)" -> "Publisher FE verification?";
    "Publisher FE verification?" -> "Verify on publisher (Playwright)" [label="yes"];
    "Publisher FE verification?" -> "Record findings" [label="no"];
    "Verify on publisher (Playwright)" -> "Record findings";
    "Record findings" -> "Issues found?";
    "Issues found?" -> "Save QA output" [label="no"];
    "Issues found?" -> "Present issues for confirmation" [label="yes"];
    "Present issues for confirmation" -> "Create bug tickets (confirmed)";
    "Create bug tickets (confirmed)" -> "Save QA output";
    "Save QA output" -> "Present summary";
}
```

## Node Details

### Load context

Find the story context in order:
1. `.ai/run-context/re.json` — RE spec (requirements, expected behavior)
2. `.ai/run-context/dev.json` — Dev output (files changed, tasks completed)
3. `.ai/specs/<id>-*/explain.md` — requirements from `/dx-req` (Phase 3)

If an argument (work item ID) is provided, also fetch the Story from ADO for acceptance criteria.

Print:
```
[QA Agent] Story #<id>: <summary>
[QA Agent] Files changed: <count>
[QA Agent] AEM: <author-url>
```

### Verify AEM is running

Use Playwright MCP to check AEM availability.

**Try calling a tool directly first** (e.g., `mcp__plugin_dx-aem_playwright__browser_navigate`). If "tool not found", fall back to `ToolSearch query: "+playwright browser_navigate"`. Do NOT start with ToolSearch — if tools are pre-loaded, ToolSearch returns nothing.

Navigate to `<author-url>/crx/de`:
```
mcp__plugin_dx-aem_playwright__browser_navigate
  url: "<author-url>/crx/de"
```

Handle AEM login. **Preferred:** `bash .ai/lib/aem-playwright-auth.sh author <local|qa>` then `mcp__plugin_dx-aem_playwright__browser_set_storage_state` with `.ai/playwright/aem-author-state.json`, then navigate (no password in context). **Fallback:** if a navigation lands on the login form, fill credentials resolved from `AEM_INSTANCES` (localhost `admin/admin` default).

### AEM reachable?

If navigation fails or page doesn't load, take the "no" path. If AEM responds, take the "yes" path.

### STOP: AEM not running

```
[QA Agent] ERROR: AEM is not running at <author-url>.
Deploy first with the project's build + deploy command.
```
STOP.

### Determine pages to verify

From the context, identify AEM pages to check:

1. **From RE spec** — `tasks[].files` may reference component paths, map to AEM content pages
2. **From dev output** — `filesChanged`, identify affected components
3. **From component index** — read `.ai/project/component-index.md` (or `.ai/component-index.md`) to find pages using affected components
4. **Fallback** — search AEM for pages using the affected component resource types:
   ```
   mcp__plugin_dx-aem_AEM__enhancedPageSearch
     searchTerm: "<component resource type>"
   ```

### Pages found?

If pages were identified from context or search, take the "yes" path. If no pages found, take the "no" path.

### Ask user for page

Ask the user: "Which AEM page should I verify?"

Wait for response before proceeding.

### Determine verification environment

Before running checks, decide WHERE to test based on the change type. Author and publisher are always different instances with different behavior.

Analyze the files changed (from dev output) and the RE spec to classify:

| Change Type | Author | Publisher | Rationale |
|-------------|--------|-----------|-----------|
| Dialog/config changes (`cq:dialog`, `editConfig`, `_cq_editConfig`) | Yes | No | Author-only editing features |
| HTL/markup rendering changes (`.html` templates) | Yes (preview) | If published | Visual rendering |
| CSS/JS frontend changes (clientlibs, webpack) | Yes (preview) | If published | User-facing appearance |
| User-facing features (login, commerce, forms, age gate) | No | Yes | Only functional on publisher |
| Sling models / backend logic (`.java` services) | Yes | Yes | Affects both environments |
| OSGi configs (`.cfg.json`, `.config`) | Yes | Yes | May differ between author/publisher run modes |
| Dispatcher rules / URL rewrites (`.any`, `.farm`, `.vhost`) | No | Yes | Publisher-only infrastructure |

**Publishing caveat:** Content and code changes are visible on publisher ONLY after activation (publish). Publishing typically happens via CI/CD pipelines when the PR is merged — it is NOT part of this development cycle. The QA agent must:
- Always check author first to confirm the change works
- If publisher verification is selected AND content appears stale/missing on publisher (404, old version), check whether it exists on author. If it does, this is "awaiting publish" (expected), NOT a bug
- Only create publisher Bugs for content that IS published but renders incorrectly

Print the decision:
```
[QA Agent] Verification plan:
  - Author: Yes/No — <reason based on change type>
  - Publisher: Yes/No — <reason based on change type>
  - Note: <caveats, e.g. "publisher verification may show stale content — changes not yet published">
```

### BE verification (AEM MCP API)

**AEM MCP is API-based — it reads JCR content, NOT renders pages.** Use it for backend checks only.

For each affected component, verify via AEM MCP:

**Check dialog fields exist:**
```
mcp__plugin_dx-aem_AEM__getComponents
  path: "<aem.component-path>/<component-name>"
```

**Check JCR node properties:**
```
mcp__plugin_dx-aem_AEM__getNodeContent
  path: "<aem.component-path>/<component-name>/<versioned-path>/_cq_dialog"
```

Verify:
- Expected dialog fields are present (from RE spec)
- Field types match (textfield, pathbrowser, select, etc.)
- Required fields are marked
- Component `sling:resourceSuperType` is correct

**Check component registration:**
```
mcp__plugin_dx-aem_AEM__scanPageComponents
  pagePath: "<content-page-path>"
```

Verify the component is actually used on the target page.

**Record BE findings separately** — these are "dialog field missing" or "wrong JCR property" issues, NOT FE rendering issues.

### Author FE verification?

Check the verification plan from "Determine verification environment". If Author is "Yes", take the "yes" path. If Author is "No", take the "no" path.

### Verify on author (Playwright)

For each page to verify:

**Navigate to Page (Preview Mode):**
```
mcp__plugin_dx-aem_playwright__browser_navigate
  url: "<author-url>/content/<path>.html?wcmmode=disabled"
```

Wait for page load:
```
mcp__plugin_dx-aem_playwright__browser_wait_for
  text: "<expected element or text>"
  timeout: 15000
```

**Take Screenshot:**
```
mcp__plugin_dx-aem_playwright__browser_take_screenshot
```

Save to `.ai/run-context/screenshots/page-<name>-preview.png`.

**Check Expected Behavior** — compare what you see against the acceptance criteria from the RE spec:
- Does the component render correctly?
- Are the expected elements visible?
- Does the layout match expectations?

**Verify Component Dialog (Editor Mode):**

Navigate to editor mode:
```
mcp__plugin_dx-aem_playwright__browser_navigate
  url: "<author-url>/editor.html/content/<path>.html"
```

Open the component dialog:
- Click on the component in the editor
- Click the configure (wrench) icon
- Take screenshot of dialog

```
mcp__plugin_dx-aem_playwright__browser_take_screenshot
```

Save to `.ai/run-context/screenshots/dialog-<component>-editor.png`.

Check dialog fields match the spec:
- Are all expected fields present?
- Do field labels match?
- Are required fields marked?

### Publisher FE verification?

Check the verification plan from "Determine verification environment". If Publisher is "Yes", take the "yes" path. If Publisher is "No", take the "no" path.

### Verify on publisher (Playwright)

For each page to verify on publisher:

**Navigate to Page (Publisher)** — no `wcmmode` param needed, publisher IS the production rendering:
```
mcp__plugin_dx-aem_playwright__browser_navigate
  url: "<publish-url>/content/<path>.html"
```

Wait for page load:
```
mcp__plugin_dx-aem_playwright__browser_wait_for
  text: "<expected element or text>"
  timeout: 15000
```

**Handle stale/missing content:**
1. If the page returns 404 or shows stale content, check if the same page exists and works on author
2. If it works on author, content is **not yet published**. This is expected during development. Record as "awaiting publish" — do NOT create a Bug ticket.
3. If it also fails on author, real bug — record it.

**Take Screenshot:**
```
mcp__plugin_dx-aem_playwright__browser_take_screenshot
```

Save to `.ai/run-context/screenshots/page-<name>-publisher.png`.

**Check Publisher-Specific Behavior:**
- Does the page render correctly without author chrome?
- Are assets loading (images, CSS, JS)?
- Do user-facing features work (forms, login, commerce, age gate)?
- Are dispatcher cache headers correct?
- Do URL rewrites work as expected?

Record findings with layer = "Publisher".

### Record findings

Classify each finding:

| Category | Layer | Action | Bug Severity |
|----------|-------|--------|-------------|
| **Broken functionality** — component doesn't work, page errors | Author FE | Create Bug | 2 - High |
| **Visual/cosmetic** — wrong styling, layout shifted, missing icon | Author FE | Create Bug | 3 - Medium |
| **Dialog mismatch** — field missing, wrong label, wrong options | BE (MCP) | Create Bug | 3 - Medium |
| **JCR property wrong** — missing property, wrong type | BE (MCP) | Create Bug | 3 - Medium |
| **Component not registered** — resource type missing | BE (MCP) | Create Bug | 2 - High |
| **Publisher rendering broken** — page errors on publisher (content IS published) | Publisher | Create Bug | 2 - High |
| **Publisher feature broken** — login/commerce/form fails on publisher | Publisher | Create Bug | 2 - High |
| **Awaiting publish** — works on author, stale/missing on publisher | Publisher | Skip — not a bug | N/A |
| **Working correctly** — matches spec | Both | No Bug | N/A |

**NEVER create Bugs for:**
- Build or compilation failures — that's Dev self-check
- Unit test failures — that's Dev self-check
- Lint warnings — that's Dev self-check
- Code quality concerns — that's code review, not QA

**Duplicate Detection:**

Before creating a bug ticket:
1. Check `$SPEC_DIR/qa.json` — if it exists, read existing findings
2. For each new finding, compare against existing entries by: component + issue description
3. If a matching finding exists, skip (log: "Duplicate finding skipped: {description}")
4. Only create bug tickets for NEW findings not already in qa.json

### Issues found?

If any findings are classified as bugs (not "Working correctly" or "Awaiting publish"), take the "yes" path. If all checks pass, take the "no" path.

### Present issues for confirmation

For each issue found, present to the user:

```markdown
**Issue Found:** <description>

**Severity:** <2 - High / 3 - Medium>
**Component:** <component name>
**Page:** <AEM page path>
**Screenshot:** <path to screenshot>

**Steps to Reproduce:**
1. Navigate to <URL>
2. <step>
3. <step>

**Expected:** <what should happen per spec>
**Actual:** <what actually happens>

Create Bug ticket? (y/n)
```

Wait for user confirmation on each issue before proceeding.

### Create bug tickets (confirmed)

For each confirmed issue, create via ADO MCP:
```
mcp__ado__wit_create_work_item
  project: "<scm.project>"
  type: "Bug"
  title: "[AI-QA] <short description>"
  reproSteps: "<HTML repro steps>"
  severity: "<severity>"
  parentId: <story id>
  tags: "ai-generated,ai-qa"
```

### Save QA output

Write `.ai/run-context/qa.json`:

```json
{
  "storyId": 12345,
  "verificationPlan": {
    "author": true,
    "publisher": true,
    "reason": "Sling model changes affect both environments"
  },
  "pagesVerified": {
    "author": ["/content/project/page1.html"],
    "publisher": ["/content/project/page1.html"]
  },
  "screenshots": ["screenshots/page-hero-preview.png", "screenshots/dialog-hero-editor.png", "screenshots/page-hero-publisher.png"],
  "findings": [
    {
      "type": "broken-functionality",
      "layer": "Author FE",
      "severity": "2 - High",
      "description": "Hero image not rendering",
      "page": "/content/project/page1.html",
      "bugId": 12349
    }
  ],
  "bugsCreated": [12349],
  "verdict": "fail",
  "timestamp": "ISO-8601"
}
```

Verdict: `"pass"` if no bugs, `"fail"` if any bugs created.

### Present summary

```markdown
## QA Agent Complete: Story #<id>

**Verdict:** PASS / FAIL

### Environments Tested
- **Author:** Yes/No — <reason>
- **Publisher:** Yes/No — <reason>

### Pages Verified: <count>
<list each page with environment and status>

### Screenshots: <count>
<list screenshot files>

### Bugs Created: <count>
<list each bug with ID and severity>

### Next steps:
- **If PASS:** `/dx-pr-commit pr` — create pull request
- **If FAIL:** `/dx-agent-dev` — Dev Agent fixes bugs, then redeploy and rerun `/aem-qa`
```

## QA to Dev Repair Cycle

If bugs are created, the developer can:
1. Run `/dx-agent-dev` to fix the bugs (Dev reads Bug tickets)
2. Redeploy using the project's build + deploy command
3. Rerun `/aem-qa` to verify fixes

**Max 2 QA-Dev cycles.** After 2 cycles, if bugs persist, report to human.

## Pre-Presentation Validation

Before creating bug tickets:
1. Re-read explain.md or RE spec acceptance criteria
2. Verify each finding is a genuine deviation from spec
3. Cross-check: is this component in the spec? If not, skip (out of scope)
4. Duplicate check: does qa.json already have this finding?

## Success Criteria

- [ ] All components from spec verified (100% coverage)
- [ ] Each component has at least 1 screenshot
- [ ] Bug tickets created only for severity High or above
- [ ] No duplicate findings (checked against existing qa.json)
- [ ] qa.json updated with all findings

## Examples

1. `/aem-qa 2416553` — Reads spec for story #2416553, navigates to AEM author to verify dialog fields match acceptance criteria. Opens QA publisher to check rendered component layout, captures screenshots, and finds 1 UI bug (missing padding on mobile). Asks before creating Bug ticket in ADO.

2. `/aem-qa 2416553` (all checks pass) — Verifies dialog configuration via AEM MCP API (all 8 fields present with correct types), renders component on QA publisher via Playwright (layout matches spec), captures screenshots as evidence. Reports PASS with screenshot links.

3. `/aem-qa 2416553` (AEM not available) — Cannot reach AEM author instance. Reports error: "AEM required — cannot verify without a running instance." Suggests starting AEM and redeploying before re-running.

## Troubleshooting

- **"AEM required — cannot verify without a running instance"**
  **Cause:** AEM author is not running or not reachable at the configured URL.
  **Fix:** Start AEM, deploy latest code with `mvn clean install -PautoInstallPackage`, and re-run `/aem-qa`.

- **Bug created for a pre-existing issue (not related to the story)**
  **Cause:** The QA check compared against spec acceptance criteria but the issue was present before the story's changes.
  **Fix:** If you ran `/aem-snapshot` before development, the baseline comparison should catch this. If not, review the created Bug ticket and close it if the issue is unrelated. For future stories, run `/aem-snapshot` before starting work.

- **Playwright screenshots show wrong page or stale content**
  **Cause:** The page cache hasn't been invalidated after deployment, or the browser session has stale cookies.
  **Fix:** Clear the dispatcher cache or append `?nocache=true` to the URL. Re-run `/aem-qa` — it opens a fresh browser session each time.

## Decision Tree: Finding Severity

```
Observation during QA:
- Component broken (not rendering, JS error) -> BUG: Critical
- Required field empty/missing per spec -> BUG: High
- Feature works but wrong behavior -> BUG: High
- Visual difference from spec:
  - Significant (layout broken, wrong colors) -> BUG: Medium
  - Minor (padding off by pixels) -> Observation only
- Optional field empty -> NOT a bug
- Decorative image missing alt text -> NOT a bug (WCAG)
- Feature not in spec -> NOT a bug (out of scope)
```

## Decision Examples

### Bug: Missing Required Field
**Finding:** Hero title field is empty on the test page
**Spec says:** "Title is required and should display prominently"
**Decision:** BUG — severity High. Title is required per spec.

### Not a Bug: Missing Optional Field
**Finding:** Alt text empty on decorative hero background image
**Spec says:** Nothing about alt text for backgrounds
**Decision:** NOT A BUG — decorative images conventionally have empty alt text (WCAG). Skip.

### Not a Bug: Styling Difference
**Finding:** Button padding is 12px instead of spec's 16px
**Decision:** NOT A BUG for functional QA. Note as observation only if severity threshold is High+.

## Rules

- **No code modifications** — QA reports issues, Dev fixes them
- **AEM required** — never attempt to verify without a running AEM instance
- **Two verification layers** — BE (AEM MCP API: fields, properties) + FE (Playwright: rendering, layout). AEM MCP is API-based and cannot test FE.
- **UI/functional bugs only** — NEVER create Bugs for build/test/lint issues
- **Human confirms bugs** — ALWAYS ask before creating Bug tickets
- **Screenshot everything** — every page visit and dialog interaction gets a screenshot
- **Grounded findings** — compare against spec acceptance criteria, not assumptions
- **Idempotent** — check for existing `[AI-QA]` bugs before creating duplicates
