Generates Playwright E2E tests with visual verification. Creates tests for untested endpoints using live browser inspection to ensure accurate locators.
---Operational Ethos Compliance: This skill operates under the principles defined in CLAUDE.md Section 13:- Context Consolidation: Automatic, not optional (Section 13.0)- Professional Standards: Security, testing, documentation mandatory (Section 13.1)- Anticipatory Design: Think 3 steps ahead (Section 13.2)- Full-Scope Traversal: Address all layers (Section 13.3)- Recursive Intelligence: Enrich system with every run (Section 13.4)---
Automatically generates comprehensive Playwright E2E tests for untested endpoints and user flows by coordinating the Playwright Generator agent with Keimenon MCP servers. Enhanced with visual reconnaissance to verify elements exist before generating tests, ensuring tests target real UI components with accurate locators.
Visual Verification Integration: Following Anthropic's Agent SDK pattern, this skill uses live browser inspection to generate tests that match the actual UI state, not assumptions from code analysis alone.
Invoke this skill when you need to:
/api/v1/nodes/:idGET, POST, PUT, DELETEkeimenon-api-testing MCP to verify endpoint is accessiblekeimenon-database MCP for relevant node/edge typesid format: src_abc123)Purpose: Verify UI elements exist in the actual application before generating tests
Critical: This phase prevents generating tests with invalid selectors
Launch test application in browser:
// Start servers if not running
(await mcp__playwright) - e2e__app_start({ env: 'local' });
// Wait for servers to be ready
await waitForServerReady('http://localhost:4001/health');
await waitForServerReady('http://localhost:3000');
Authenticate and navigate to target page:
// Determine which page/view contains the endpoint's UI
const targetPage = mapEndpointToUIPath(endpoint);
// Examples:
// - POST /api/v1/nodes → "/keimenon" (create node UI)
// - GET /api/v1/groups → "/groups" (groups list)
// - PUT /api/v1/settings → "/settings" (settings panel)
// Use Task to launch browser and navigate
const visualInspection = await Task({
subagent_type: 'playwright-test-planner',
prompt: `Navigate to ${targetPage} and inspect UI for ${endpoint}
Steps:
1. Login with test account ([email protected] / 123456)
2. Navigate to ${targetPage}
3. Wait for page to load completely
4. Capture screenshot of entire page
5. List all interactive elements (buttons, forms, links, inputs)
6. Generate robust locators for each element
7. Return structured data about UI state
Return:
- Screenshot path
- List of elements with locators
- Page structure/layout
- Any modals, dialogs, or dynamic content
`,
});
📸 Capture screenshot of target page:
const screenshot = {
path: visualInspection.screenshot_path,
timestamp: Date.now(),
page_url: targetPage,
viewport: { width: 1920, height: 1080 },
};
📸 Extract interactive elements from live page:
// Use browser tools to find all elements related to this endpoint
const elements = await extractInteractiveElements(visualInspection);
// Example result:
const uiElements = [
{
type: 'button',
role: 'button',
text: 'Create Source Node',
locator: "page.getByRole('button', { name: /create source node/i })",
visible: true,
enabled: true,
position: { x: 850, y: 120 },
screenshot_region: { x: 800, y: 100, w: 180, h: 50 },
},
{
type: 'input',
role: 'textbox',
label: 'Node Title',
locator: "page.getByLabel('Node Title')",
visible: false, // Initially hidden (in dialog)
required: true,
},
{
type: 'input',
role: 'textbox',
label: 'Content',
locator: "page.getByLabel('Content')",
visible: false, // Initially hidden (in dialog)
required: true,
},
{
type: 'button',
role: 'button',
text: 'Submit',
locator: "page.getByRole('button', { name: /submit/i })",
visible: false, // In dialog
form_action: 'POST /api/v1/nodes',
},
];
📸 Map visual elements to API operations:
// Determine which UI elements trigger which API calls
const uiToApiMapping = {
endpoint: 'POST /api/v1/nodes',
trigger_element: uiElements.find((e) => e.text === 'Create Source Node'),
form_elements: uiElements.filter((e) => e.type === 'input'),
submit_element: uiElements.find((e) => e.text === 'Submit'),
success_indicators: [
{ type: 'toast', text: 'Node created successfully' },
{ type: 'element', selector: 'keimenon .node', description: 'New node appears in keimenon' },
],
error_indicators: [
{ type: 'toast', text: /error/i },
{ type: 'message', selector: '.error-message' },
],
};
Verify element existence and accessibility:
const verificationResults = {
endpoint: 'POST /api/v1/nodes',
ui_verified: true,
elements_found: uiElements.length,
critical_elements_verified: [
{ element: 'Create button', found: true, accessible: true },
{ element: 'Title input', found: true, accessible: true },
{ element: 'Content input', found: true, accessible: true },
{ element: 'Submit button', found: true, accessible: true },
],
issues: [],
screenshots: {
page_initial: screenshot.path,
// We'll capture more screenshots during test generation
},
};
// If any critical elements missing:
if (verificationResults.critical_elements_verified.some((e) => !e.found)) {
console.warn(`⚠️ Visual verification found missing UI elements for ${endpoint}
This endpoint may not have a UI implementation yet.
Test generation will proceed with API-only tests.
`);
}
📸 Capture element-specific screenshots (optional, for complex UIs):
// For complex features, capture screenshots of specific regions
const detailedScreenshots = await captureElementScreenshots(uiElements);
// Example:
// - create-button.png: Just the "Create Source Node" button
// - node-form.png: The node creation form (when opened)
// - success-state.png: What success looks like
Generate visual context summary for test generation:
const visualContext = {
endpoint: 'POST /api/v1/nodes',
page_url: targetPage,
screenshots: {
full_page: screenshot.path,
...detailedScreenshots,
},
verified_locators: {
trigger_button: "page.getByRole('button', { name: /create source node/i })",
title_input: "page.getByLabel('Node Title')",
content_input: "page.getByLabel('Content')",
submit_button: "page.getByRole('button', { name: /submit/i })",
},
user_flow: [
"1. Click 'Create Source Node' button",
'2. Dialog/modal opens with form',
"3. Fill 'Node Title' input",
"4. Fill 'Content' input",
"5. Click 'Submit' button",
'6. Wait for success toast',
'7. Verify new node appears in keimenon',
],
success_criteria: [
"Toast message: 'Node created successfully'",
'New node element appears in keimenon',
'API response: 201 Created',
],
timing_considerations: [
'Wait for dialog to open (animation ~300ms)',
'Wait for API response (POST /api/v1/nodes)',
'Wait for keimenon to re-render with new node',
],
responsive_notes: 'Tested at 1920x1080, may need mobile viewport tests',
};
Define test scenarios based on endpoint semantics and visual context:
For each scenario, specify:
Include visual verification in test spec:
const testSpec = {
scenario: 'Create Source Node - Happy Path',
visual_context: visualContext, // ← Attach visual evidence
steps: [
{
action: 'click',
element: 'Create Source Node button',
locator: visualContext.verified_locators.trigger_button,
visual_verification: 'Button highlighted in ' + screenshot.path,
},
{
action: 'wait',
condition: 'Dialog opens',
visual_verification: 'Look for dialog modal in screenshot',
},
// ... more steps with visual references
],
assertions: [
{
type: 'api',
check: 'Response status 201',
},
{
type: 'visual',
check: 'New node appears in keimenon',
how_to_verify: 'Check for new .node element with matching title',
},
{
type: 'visual',
check: 'Success toast displayed',
locator: "page.getByText('Node created successfully')",
},
],
};
tests/e2e/templates/crud-template.spec.tstests/e2e/templates/auth-template.spec.tstests/e2e/templates/multi-tenant-template.spec.tstests/e2e/templates/workflow-template.spec.tsTask({
subagent_type: "playwright-test-generator",
prompt: `Generate a Playwright test for ${endpoint}
📸 VISUAL CONTEXT (verified in live browser): Page URL: ${visualContext.page_url} Screenshot: ${visualContext.screenshots.full_page}
VERIFIED UI ELEMENTS (these exist on the page): ${visualContext.verified_locators.map(el => `
USER FLOW (visually confirmed):
${visualContext.user_flow.map((step, i) => ${i+1}. ${step}).join('\n')}
SUCCESS CRITERIA (what to verify visually):
${visualContext.success_criteria.map(c => - ${c}).join('\n')}
TIMING CONSIDERATIONS (observed in live app):
${visualContext.timing_considerations.map(t => - ${t}).join('\n')}
API SPEC:
TEST SCENARIOS TO GENERATE: ${scenario.name}
REQUIREMENTS:
📸 VISUAL EVIDENCE REFERENCE: All locators above were verified in ${visualContext.screenshots.full_page} Test should interact with elements that ACTUALLY EXIST on the page. ` })
2. **Agent generates test with visual confidence**:
- Uses verified locators (not guesses)
- Follows observed user flow
- Includes timing considerations from visual inspection
- Adds visual assertions (toasts, element appearances)
### Phase 6: Test Enhancement (with Visual Verification)
1. Agent generates base test code
2. Enhance with project-specific patterns:
- Add test isolation headers: `X-Test-DB-Path`
- Use login helper: `await login(page, email, password)`
- Apply proper tagging: `test.describe.configure({ tag: '@smoke' })`
- Add test correlation: `x-test-id` header
- Include data cleanup in `afterEach`
3. **📸 Add visual regression checks**:
```typescript
// After critical UI actions, capture baseline screenshots
test('should create node', async ({ page }) => {
// ... test steps ...
// 📸 Visual verification: Capture success state
await expect(page).toHaveScreenshot('node-created-success.png', {
fullPage: false,
clip: { x: 0, y: 0, width: 800, height: 600 } // Keimenon area
});
});
Add visual evidence comments:
// Related: apps/api/src/routes/nodes.ts:POST /api/v1/nodes
// Schema: ai_context/schemas/Node.json
// 📸 Visual verification: .claude/test-generation/2025-11-01/nodes-create-page.png
// Verified locators from live UI inspection (2025-11-01 12:00)
// TODO: Add edge case for invalid node kinds
Validate test follows CLAUDE.md standards
If endpoint supports multiple account types, generate variants with visual verification for each account view:
Client Account Test (with visual context):
test.describe('Nodes API - Client Account', () => {
test.beforeEach(async ({ page }) => {
await login(page, '[email protected]', 'password');
});
test('should create node in own account', async ({ page }) => {
// 📸 Visual verification: Client sees "Create" button
await expect(page.getByRole('button', { name: /create source/i })).toBeVisible();
// Click button (locator verified in Phase 2.5)
await page.getByRole('button', { name: /create source node/i }).click();
// 📸 Visual verification: Dialog opens
await expect(page.getByRole('dialog')).toBeVisible();
// ... rest of test with verified locators
});
test('should not access other account nodes', async ({ page }) => {
// Multi-tenant isolation test with visual verification
});
});
Admin Account Test (with admin-specific visual verification):
test.describe('Nodes API - Admin Account', () => {
test.beforeEach(async ({ page }) => {
await login(page, '[email protected]', 'admin123');
});
test('should access all nodes across accounts', async ({ page }) => {
// 📸 Visual verification: Admin sees "All Accounts" filter
await expect(page.getByLabel('Account Filter')).toBeVisible();
// Admin-specific UI verified in Phase 2.5 (admin session)
});
});
tests/e2e/${resource}-crud.spec.tstests/e2e/auth-${flow}.spec.tstests/e2e/workflow-${feature}.spec.tstests/e2e/multi-tenant-${resource}.spec.tsdescribe blocksSyntax check: Run TypeScript compiler
Lint check: eslint tests/e2e/${filename}
📸 Dry run with visual capture: Execute test in headed mode
// Run generated test once to verify it works
const dryRunResult =
(await mcp__playwright) -
e2e__pw_run({
grep: testFileName,
headed: true,
project: 'chromium',
});
if (!dryRunResult.passed) {
// Test failed on first run - this shouldn't happen with visual verification
console.error(`❌ Generated test failed dry run:
File: ${testFileName}
Error: ${dryRunResult.error}
This indicates visual reconnaissance didn't match actual app behavior.
Possible causes:
- Timing issue (need more waits)
- Dynamic content (need better selectors)
- Multi-step flow (missing intermediate steps)
🔧 Auto-healing...
`);
// Invoke healer to fix generated test
await invokeAutoHealer(testFileName);
}
📸 Compare generated test behavior to visual reconnaissance:
// Verify test follows the observed user flow
const testBehaviorMatch = await compareTestToVisualFlow(generatedTest, visualContext.user_flow);
if (!testBehaviorMatch.aligned) {
console.warn(`⚠️ Generated test deviates from observed visual flow:
${testBehaviorMatch.differences.map((d) => `- ${d}`).join('\n')}
`);
}
Coverage verification: Confirm endpoint is now tested
Generated Test File (tests/e2e/nodes-crud-client.spec.ts) with visual verification:
import { test, expect } from '@playwright/test';
import { login } from './helpers/login';
// Related: apps/api/src/routes/nodes.ts:POST /api/v1/nodes
// Schema: ai_context/schemas/Node.json
// 📸 Visual verification: .claude/test-generation/2025-11-01/nodes-create-verification.png
// Verified locators from live UI (2025-11-01 12:00:00)
// All selectors tested in actual browser before test generation
// TODO: Add edge case for invalid node kinds
test.describe('Nodes CRUD - Client Account', () => {
test.describe.configure({ tag: '@smoke' });
test.beforeEach(async ({ page }) => {
await login(page, '[email protected]', '123456');
await page.goto('/keimenon');
await page.waitForLoadState('domcontentloaded');
});
test.afterEach(async ({ page, request }) => {
// Cleanup test data
await request.delete('/api/v1/nodes', {
params: { data_tag: 'test' },
});
});
test('should create a Source node successfully', async ({ page }) => {
// 📸 Verified: Button exists and is visible (see visual-evidence/create-button.png)
// 1. Click create button (locator verified in visual reconnaissance)
await page.getByRole('button', { name: /create source/i }).click();
// 📸 Verified: Dialog opens with ~300ms animation
// 2. Wait for dialog to open (timing observed in live app)
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
// 📸 Verified: Form fields exist in dialog (see visual-evidence/node-form.png)
// 3. Fill in node properties (fields verified to exist)
await page.getByLabel(/title/i).fill('Test Source Node');
await page.getByLabel(/content/i).fill('This is test content');
// 4. Submit form (button verified in dialog)
await page.getByRole('button', { name: /submit|create/i }).click();
// 📸 Verified: Success toast appears (observed pattern in live app)
// 5. Wait for success feedback
await expect(page.getByText(/created successfully/i)).toBeVisible({ timeout: 10000 });
// 📸 Visual verification: New node should appear in keimenon
// 6. Verify node appears in keimenon
await expect(page.getByText('Test Source Node')).toBeVisible();
// 📸 Capture success state for baseline
await expect(page).toHaveScreenshot('node-created-keimenon-state.png', {
fullPage: false,
clip: { x: 0, y: 0, width: 1200, height: 800 },
});
// 7. Verify node in database via API
const response = await page.request.get('/api/v1/nodes', {
params: { kind: 'Source', limit: 1 },
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.nodes).toHaveLength(1);
expect(data.nodes[0].properties.title).toBe('Test Source Node');
});
test('should not access nodes from other accounts', async ({ page, request }) => {
// Multi-tenant test with visual verification...
// (Similar enhancements with visual comments)
});
});
Summary Report (JSON) with visual evidence:
{
"generated": true,
"test_file": "tests/e2e/nodes-crud-client.spec.ts",
"endpoint": "/api/v1/nodes",
"method": "POST",
"visual_verification": {
"performed": true,
"page_url": "/keimenon",
"screenshot_evidence": [
".claude/test-generation/2025-11-01/nodes-create-verification.png",
".claude/test-generation/2025-11-01/create-button.png",
".claude/test-generation/2025-11-01/node-form.png"
],
"elements_verified": 4,
"locators_confidence": "high",
"visual_flow_observed": true
},
"scenarios_covered": ["Create node with valid data", "Multi-tenant isolation verification"],
"account_types": ["client"],
"tags": ["@smoke"],
"estimated_runtime": "15s",
"dependencies": ["tests/e2e/helpers/login.ts", "tests/e2e/fixtures/testId.ts"],
"validation": {
"syntax_check": "passed",
"lint_check": "passed",
"dry_run": "passed",
"visual_alignment": "passed"
},
"baseline_screenshots": ["tests/e2e/__screenshots__/node-created-keimenon-state.png"]
}
// Start application servers
mcp__playwright - e2e__app_start({ env: 'local' });
// Run tests in headed mode for visual inspection
mcp__playwright - e2e__pw_run({ grep: 'pattern', headed: true });
// List available tests
mcp__playwright - e2e__pw_listTests({ grep: 'pattern' });
// Extract element properties from live page
mcp__visual -
feedback__extract_element_properties({
screenshot_path: 'path/to/screenshot.png',
locator: "button:has-text('Create')",
});
// Returns: { found: true, visible: true, text: "Create Source Node", ... }
// Analyze page layout
mcp__visual -
feedback__analyze_layout({
screenshot_path: 'path/to/screenshot.png',
});
// Returns: { elements: [...], spacing_issues: [], alignment_issues: [] }
// Navigate and inspect page
Task({
subagent_type: 'playwright-test-planner',
prompt: 'Navigate to /keimenon and list all interactive elements',
});
// Returns: List of elements with locators, screenshots, page structure
afterEachUser: "Generate E2E tests for the groups batch operations endpoint"
Skill Response (Enhanced):
POST /api/v1/groups/:id/members:batch endpointpage.getByRole('button', { name: /add members/i }))page.getByLabel('Select nodes'))page.getByRole('button', { name: /remove members/i }))tests/e2e/groups-batch-operations.spec.tsVisual Evidence Delivered:
describe('Resource CRUD', () => {
// 📸 Before generating these tests, verify:
// - Create button/form exists
// - List view displays items
// - Edit button/form exists
// - Delete button/confirmation exists
test('Create', () => {
// Use verified locators from Phase 2.5
});
test('Read - Single', () => {
// Verify detail view layout from screenshot
});
test('Read - List', () => {
// Verify list layout from screenshot
});
test('Update', () => {
// Use verified edit form locators
});
test('Delete', () => {
// Use verified delete button locator
});
});
describe('Multi-Tenant Isolation', () => {
// 📸 Visual verification performed for BOTH accounts
// - Account A's view of data (screenshot A)
// - Account B's view of data (screenshot B)
// - Verified: Account B does NOT see Account A's data visually
test.beforeEach(() => {
// Create data in account A
});
test('Account B cannot read account A data', () => {
// Visual + API verification
});
test('Account B cannot update account A data', () => {
// Visual + API verification
});
test('Account B cannot delete account A data', () => {
// Visual + API verification
});
});
describe('Import Workflow', () => {
// 📸 Entire workflow observed visually in Phase 2.5:
// 1. File upload UI
// 2. Progress bar/spinner
// 3. SSE updates (visual feedback)
// 4. Completion state
// 5. Nodes appearing in keimenon
test('End-to-end import flow', async () => {
// 1. Upload file (verified file input exists)
// 2. Monitor progress via SSE (verified progress UI exists)
// 3. Verify nodes created (verified keimenon updates)
// 4. Verify edges created (verified edge rendering)
// 5. Verify organization (verified folder/group UI)
// 📸 Capture each major state for baseline
});
});
.claude/test-generation/YYYY-MM-DD/tests/e2e/__screenshots__/ (tracked in git)