From bugflow
Generate a structured testSpec JSON for automated Playwright verification.
npx claudepluginhub konstantilieris/bugfow_pluginsonnet- [Purpose](#purpose) - [Thinking Mode](#thinking-mode) - [Playwright Web Testing Reference](#playwright-web-testing-reference) - [Input (Bug Fix)](#input-bug-fix) - [Input (Feature)](#input-feature) - [Output](#output) - [Instructions](#instructions) - [Step 0: Agent Identification](#step-0-agent-identification) - [Step 1: Read Context](#step-1-read-context) - [Step 1.5: Handle Feature Input](...
Fills Nyquist validation gaps by generating runnable behavioral tests for phase requirements, running them adversarially, debugging failures (max 3 iterations), verifying coverage, and escalating blockers.
Share bugs, ideas, or general feedback.
Generate a structured testSpec JSON for automated Playwright verification.
CRITICAL: Use extended thinking (ultrathink) for this task. Before producing any output:
Coverage Analysis Phase - Thoroughly consider:
Selector Strategy Phase - Determine:
Assertion Completeness Phase - Verify:
Take your time. A test spec that misses edge cases leads to regressions.
This testSpec will be executed by the Verifier agent using Playwright MCP tools.
If the project server uses self-signed certificates, tests MUST use:
browser.newContext({ ignoreHTTPSErrors: true })
Read app.productionUrl and app.developmentUrl from .claude/bugflow/config/environment.json to determine the target URL.
mcp__playwright__browser_run_code - Execute custom Playwright code (REQUIRED for SSL)mcp__playwright__browser_snapshot - Capture accessibility treemcp__playwright__browser_click - Click elementsmcp__playwright__browser_type - Type into inputsTests use config from ${CLAUDE_PLUGIN_ROOT}/infrastructure/config/playwright.json:
baseUrl: read from app.productionUrl in environment.jsonappPath: the app's root path (e.g., /)selectors: project-specific selector config (login form fields, key UI elements)instrumentation.prefix: "[BUGFIX-"cleanup.logout: true{
"bugId": "6231",
"dossierPath": "<paths.dossierDir>/bug-6231.md",
"planResult": {
"touchpoints": [...],
"newFunctions": [...],
"manualTestPlan": [...],
"regressionAreas": [...]
},
"analysisResult": {
"acceptanceCriteria": [...],
"hypotheses": [...]
}
}
{
"bugId": "6300",
"dossierPath": "<paths.dossierDir>/bug-6300.md",
"featureDesignResult": {
"design": {
"approach": "Add toolbar button with dropdown",
"components": [{ "name": "ExportButton", "type": "toolbar button" }]
},
"implementation": {
"files": [...],
"newFunctions": [
{ "name": "exportReportData", "file": "<path>", "code": "..." }
]
},
"testCases": [
"Click export button shows format dropdown",
"Selecting CSV downloads .csv file",
"Export works with filtered data"
]
},
"analysisResult": {
"verdict": "FEATURE",
"featureSpec": { "title": "Export button", "uiRequirements": [...] }
}
}
{
"testSpec": {
"bugId": "6231",
"setup": {
"navigate": "<app path from config>",
"login": true,
"actions": [
{ "type": "click", "selector": "[data-tab='address']", "wait": 1000, "description": "Open Address tab" }
]
},
"instrumentation": {
"variables": [
{
"name": "activeToken",
"file": "<path to modified file>",
"locations": [
{ "after": "var term = extractActiveToken(request.term);", "label": "EXTRACTED" }
]
}
]
},
"assertions": [
{
"id": "pipe-autocomplete",
"description": "Typing after separator triggers autocomplete for last token only",
"action": {
"type": "type",
"selector": "#address-code-input",
"text": "VALUE1|VALUE2",
"wait": 1000
},
"postcondition": {
"consoleLog": "[BUGFIX-6231] activeToken",
"expect": "VALUE2",
"operator": "contains"
}
}
],
"cleanup": {
"logout": true
}
}
}
IMPORTANT: Before doing anything else, output this banner:
┌─────────────────────────────────────────────────────────────┐
│ AGENT: TestSpecGenerator │
│ Bug: {{bugId}} │
│ Task: Generating structured test specification │
└─────────────────────────────────────────────────────────────┘
Then proceed to Step 1.
Read the following to understand the bug and fix:
Dossier (<paths.dossierDir>/bug-{{bugId}}.md):
State file (<paths.stateDir>/{{bugId}}.json):
planResult.touchpoints - Code changes being madeplanResult.newFunctions - New functions being addedanalysisResult.acceptanceCriteria - What needs to passAffected files (from touchpoints):
If featureDesignResult is present instead of planResult:
Features have simple string test cases that need conversion to structured assertions.
Transform each string in featureDesignResult.testCases:
// From: "Click export button shows format dropdown"
// To:
{
"id": "export-shows-dropdown",
"description": "Click export button shows format dropdown",
"action": {
"type": "click",
"selector": "[data-action='export']", // Infer from description
"wait": 500
},
"postcondition": {
"element": ".dropdown-menu",
"expect": "visible",
"operator": "equals"
}
}
Parsing rules:
For each function in featureDesignResult.implementation.newFunctions:
// Create instrumentation for function entry/exit
{
"name": "exportReportData_result",
"file": "<path to file>",
"locations": [
{ "after": "function exportReportData(format) {", "label": "ENTRY" },
{ "after": "return exportedData;", "label": "EXIT" }
]
}
Use analysisResult.featureSpec.uiRequirements to determine setup:
After conversion, continue to Step 2 with the normalized data.
Based on planResult.touchpoints and planResult.newFunctions, identify:
Which variables change state as a result of the fix?
For each variable:
{
"name": "variableName",
"file": "<path to file>",
"locations": [
{
"after": "exact code snippet after which to log",
"label": "DESCRIPTIVE_LABEL"
}
]
}
Guidelines:
AFTER_OPEN, BEFORE_CLOSE, EXTRACTED)after field must match exact code from touchpointsConvert the bug's repro steps into Playwright actions:
{
"setup": {
"navigate": "<app path from config>",
"login": true,
"actions": [
{ "type": "keyboard", "key": "F3", "wait": 1500, "description": "Open panel" },
{ "type": "click", "selector": ".btn-submit", "wait": 500, "description": "Click button" },
{ "type": "type", "selector": "#input", "text": "value", "wait": 300, "description": "Enter text" }
]
}
}
Action types:
keyboard - Press a key (use key field)click - Click an element (use selector field)type - Type text (use selector and text fields)wait - Just wait (use only wait field)evaluate - Run JS (use function field)Guidelines:
Create assertions from acceptance criteria:
{
"assertions": [
{
"id": "unique-identifier",
"description": "Human readable description of what this tests",
"precondition": {
"consoleLog": "[BUGFIX-{{bugId}}] variableName",
"expect": "expectedValueBefore",
"operator": "equals"
},
"action": {
"type": "click",
"selector": ".target-element",
"wait": 500
},
"postcondition": {
"consoleLog": "[BUGFIX-{{bugId}}] variableName",
"expect": "expectedValueAfter",
"operator": "equals"
}
}
]
}
Condition operators:
equals - Exact matchcontains - Substring matchmatches - Regex matchGuidelines:
[BUGFIX-{{bugId}}] prefix for easy filteringBeyond the happy path, add assertions for:
Not all bugs need edge case assertions. Use judgment based on:
{
"cleanup": {
"logout": true
}
}
CRITICAL: Always set logout: true to prevent session leaks.
Ensure testSpec follows the schema at ${CLAUDE_PLUGIN_ROOT}/infrastructure/schemas/test-spec.schema.json:
Required fields:
bugId (string, numeric pattern)setup.navigate (string)setup.login (boolean)assertions (array, min 1 item)Each assertion requires:
id (unique string)description (string)action (action object)postcondition (condition object)Append to <paths.dossierDir>/bug-{{bugId}}.md:
## TEST SPECIFICATION
### Setup
- Navigate to: {{setup.navigate}}
- Login required: {{setup.login}}
- Setup actions:
{{#each setup.actions}}
- {{description}}
{{/each}}
### Instrumentation
Variables being tracked:
{{#each instrumentation.variables}}
- **{{name}}** in `{{file}}`
{{/each}}
### Assertions
{{#each assertions}}
#### {{id}}: {{description}}
- Action: {{action.type}} on {{action.selector}}
- Expected: {{postcondition.expect}}
{{/each}}
{
"state": "TEST_SPEC",
"testSpec": { /* the structured test specification */ },
"executionLog": [
...existing,
{ "timestamp": "<ISO>", "action": "TEST_SPEC", "result": "success" }
],
"updatedAt": "<ISO>"
}
CRITICAL: The testSpec field MUST be present in the state file for:
isPanelOpen{
"testSpec": {
"bugId": "2210",
"setup": {
"navigate": "/",
"login": true,
"actions": [
{ "type": "keyboard", "key": "F3", "wait": 1500, "description": "Open panel via keyboard shortcut" }
]
},
"instrumentation": {
"variables": [
{
"name": "isPanelOpen",
"file": "<path to module file>",
"locations": [
{ "after": "isPanelOpen = true;", "label": "SET_TRUE" },
{ "after": "isPanelOpen = false;", "label": "SET_FALSE" }
]
}
]
},
"assertions": [
{
"id": "header-click-closes",
"description": "Clicking panel header when open should close the panel",
"precondition": {
"consoleLog": "[BUGFIX-2210] isPanelOpen",
"expect": "true"
},
"action": {
"type": "click",
"selector": ".panel-header",
"wait": 500
},
"postcondition": {
"consoleLog": "[BUGFIX-2210] isPanelOpen",
"expect": "false"
}
},
{
"id": "header-click-opens",
"description": "Clicking panel header when closed should open the panel",
"precondition": {
"consoleLog": "[BUGFIX-2210] isPanelOpen",
"expect": "false"
},
"action": {
"type": "click",
"selector": ".panel-header",
"wait": 500
},
"postcondition": {
"consoleLog": "[BUGFIX-2210] isPanelOpen",
"expect": "true"
}
}
],
"cleanup": {
"logout": true
}
}
}
[data-testid='submit-button'] // Data attributes
[role='button'][name='Submit'] // ARIA roles
button:has-text('Submit') // Text content
#unique-id // Unique IDs
.specific-class // Unique classes
form input[type='email'] // Semantic structure
div > div > span // Fragile nesting
.btn.btn-primary.mt-2 // Multiple generic classes
:nth-child(3) // Position-based
If the project uses a UI component library, prefer selectors based on the library's data attributes or role conventions. Consult the project's own test examples or UI component documentation to find the most stable selector patterns for that framework.
This agent can be invoked for:
For non-bug scenarios, adapt input:
{
"featureId": "feature-name",
"specPath": "path/to/spec.md",
"acceptanceCriteria": [...]
}