Agent skill
qa-mcp-helpers
Shared helper patterns for Playwright MCP validation agents. Reuses Page Object patterns from E2E tests.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/qa-mcp-helpers
SKILL.md
Playwright MCP Helper Patterns
"Share code between E2E tests and MCP validation"
This skill provides common patterns for using Playwright MCP tools in validation agents. These patterns align with the Page Object Model used in automated E2E tests.
Quick Reference
| E2E Test Pattern | MCP Equivalent |
|---|---|
new GamePage(page) |
Use same selectors via page.getByRole() |
await gamePage.goto() |
await page.goto('http://localhost:{detected_port}') // ALWAYS detect port first |
await expect(element).toBeVisible() |
Check visibility, take screenshot |
MANDATORY: Port Detection (BEFORE Navigation)
⚠️ CRITICAL: Vite dev server may run on different ports (3000, 3001, 5173, etc.)
Before using Playwright MCP tools, ALWAYS detect the correct port:
# Check which port is serving the Vite app
netstat -an | grep LISTEN | grep -E ":(3000|3001|5173|8080)"
# Alternative: Try curl to detect
curl -s http://localhost:3000 | grep -q "vite" && echo "PORT=3000" || \
curl -s http://localhost:3001 | grep -q "vite" && echo "PORT=3001" || \
curl -s http://localhost:5173 | grep -q "vite" && echo "PORT=5173"
Store detected port in variable for subsequent MCP calls:
// After detecting port, use it in navigation
const detectedPort = 3001; // From bash detection above
await page.goto(`http://localhost:${detectedPort}`);
Multi-Agent Playwright MCP Considerations
⚠️ IMPORTANT: Standard Playwright MCP does NOT support parallel execution
When multiple agents (Developer, QA, Tech Artist) may use Playwright MCP simultaneously:
- Issue: Standard
@playwright/mcpshares a single browser instance - agents interfere with each other - Solution: Use
playwright-parallel-mcpfor isolated browser sessions per agent
To enable parallel Playwright instances, update MCP settings:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["playwright-parallel-mcp"],
"env": {
"MAX_SESSIONS": "5"
}
}
}
}
Usage with sessions:
// Create isolated session for this agent
[create_session] -> sessionId: "qa-session-{timestamp}"
// Use session ID in all subsequent calls
[navigate sessionId="qa-session-{timestamp}" url="http://localhost:3001"]
Reference: playwright-parallel-mcp on LobeHub
Common MCP Patterns
Navigation
// 1. FIRST: Detect port (see above)
const detectedPort = 3001; // From bash detection
// 2. Navigate to the application
await page.goto(`http://localhost:${detectedPort}`);
// 3. Wait for page to fully load
await page.waitForLoadState('networkidle');
// 4. Wait for canvas to be ready
await page.waitForSelector('canvas');
Console Monitoring
// Track console errors during validation
const errors: string[] = [];
const warnings: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
if (msg.type() === 'warning') {
warnings.push(msg.text());
}
});
// After performing actions:
// Filter out known headless WebGL errors (expected, not application bugs)
const filteredErrors = errors.filter((error) => {
const webglHeadlessPatterns = [
/WebGL2RenderingContext/i,
/Error creating WebGL context/i,
/WebGL context could not be created/i,
/Failed to create WebGL2RenderingContext/i,
/WEBGL_debug_renderer_info/i,
/ANGLE flag/i,
/swiftshader/i,
];
return !webglHeadlessPatterns.some((p) => p.test(error));
});
// Check that no actual application errors occurred
if (filteredErrors.length > 0) {
console.error('Application errors found:', filteredErrors);
}
WebGL / Three.js MCP Patterns
⚠️ CRITICAL: Three.js applications require specific patterns for MCP validation.
Scene Readiness Detection
// Wait for Three.js scene to be ready using data attribute
await page.waitForSelector('canvas[data-ready="1"]', { timeout: 15000 });
// Alternative: Wait for canvas and additional delay
await page.waitForSelector('canvas');
await page.waitForTimeout(2000); // Allow Three.js initialization
WebGL Context Verification
// Verify WebGL is working before proceeding
const webglStatus = await page.evaluate(() => {
const canvas = document.querySelector('canvas');
if (!canvas) return { exists: false, hasContext: false };
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
if (!gl) return { exists: true, hasContext: false };
return {
exists: true,
hasContext: true,
version: gl.getParameter(gl.VERSION),
};
});
if (!webglStatus.hasContext) {
throw new Error('WebGL context not available - cannot validate 3D scene');
}
Canvas Element Screenshot
// For WebGL scenes, screenshot only the canvas element
const canvas = await page.locator('canvas').boundingBox();
if (canvas) {
await page.screenshot({
path: '.claude/session/qa-validation/canvas-render.png',
clip: {
x: canvas.x,
y: canvas.y,
width: canvas.width,
height: canvas.height,
},
});
}
Shader Error Detection
// Track shader compilation errors separately
const shaderErrors: string[] = [];
page.on('console', (msg) => {
const text = msg.text();
const shaderErrorPatterns = [
/THREE\.WebGLProgram/i,
/shader error/i,
/program info log/i,
/WEBGL_WARNING/i, // But NOT WEBGL_debug_renderer_info
];
if (shaderErrorPatterns.some((p) => p.test(text))) {
shaderErrors.push(text);
}
});
if (shaderErrors.length > 0) {
throw new Error(`Shader compilation errors: ${shaderErrors.join(', ')}`);
}
Screenshot Evidence
// Capture screenshot for validation evidence
await page.screenshot({
path: '.claude/session/qa-validation/screenshot.png',
fullPage: true,
});
Using Page Object Selectors
When the E2E test uses specific selectors, use the same approach in MCP validation:
// Character Selection Screen
// E2E test: page.locator('#characterName')
// MCP: page.locator('#characterName')
// Select Character Button
// E2E test: page.locator('button:has-text("Select Character")')
// MCP: page.locator('button:has-text("Select Character")')
// Lobby State
// E2E test: page.getByText('LOBBY')
// MCP: page.getByText('LOBBY')
// Connection State
// E2E test: page.getByText('Connected')
// MCP: page.getByText('Connected')
Game-Specific Patterns
Character Selection Flow
// Navigate to game (detectedPort from port detection step above)
await page.goto(`http://localhost:${detectedPort}`);
await page.waitForLoadState('networkidle');
// Check if at Character Selection screen
const atCharacterSelection = await page.evaluate(() => {
const bodyText = document.body.textContent || '';
return bodyText.includes('Choose Your Character') || bodyText.includes('Character Selection');
});
if (atCharacterSelection) {
// Enter character name
await page.fill('#characterName', 'TestPlayer');
// Wait for state update
await page.waitForTimeout(500);
// Click Select Character button
const selectButton = page.locator('button:has-text("Select Character")').first();
await selectButton.click();
// Wait for Lobby screen
await page.waitForFunction(
() => {
const bodyText = document.body.textContent || '';
return bodyText.includes('LOBBY') || bodyText.includes('Connecting to server');
},
{ timeout: 10000 }
);
}
Connection Verification
// Wait for server connection
await page.waitForFunction(
() => {
const bodyText = document.body.textContent || '';
// Must show "Connected" but not "Connecting to server"
return bodyText.includes('Connected') && !bodyText.includes('Connecting to server');
},
{ timeout: 25000 }
);
// Verify connection state
const isConnected = await page.evaluate(() => {
const bodyText = document.body.textContent || '';
return (
bodyText.includes('Connected') &&
bodyText.includes('Players in Lobby') &&
!bodyText.includes('Connecting to server')
);
});
Input Testing Patterns
Keyboard Input (Movement)
// Continuous movement for gameplay testing
await page.keyboard.down('KeyW');
await page.waitForTimeout(1000); // Move for 1 second
await page.keyboard.up('KeyW');
// Individual key presses
await page.keyboard.press('KeyA');
await page.waitForTimeout(500);
Mouse Input (Pointer Lock)
// Click to activate pointer lock
await page.mouse.click(400, 300);
await page.waitForTimeout(500);
// Simulate mouse movement (movementX/Y only work when locked)
await page.mouse.move(100, 100);
await page.mouse.move(200, 150); // movementX: 100, movementY: 50
// Mouse click (shoot action)
await page.mouse.down();
await page.waitForTimeout(200);
await page.mouse.up();
Escape Key (Pause/Unlock)
// Press ESC to unlock pointer and show PAUSED
await page.keyboard.press('Escape');
// Verify pointer is unlocked
const isLocked = await page.evaluate(() => {
return document.pointerLockElement === document.body;
});
expect(isLocked).toBe(false);
Selector Best Practices
When writing MCP validation scripts, follow these selector priorities:
Priority Order
-
Role-based selectors (Preferred - accessible)
typescriptpage.getByRole('button', { name: 'Submit' }); page.getByRole('textbox', { name: 'Username' }); -
Label-based selectors (Good - accessible)
typescriptpage.getByLabel('Character Name'); page.getByLabel('Email address'); -
Test ID selectors (When no accessible name)
typescriptpage.getByTestId('submit-button'); page.getByTestId('character-name-input'); -
Text content (For existing patterns)
typescriptpage.getByText('LOBBY'); page.locator('button:has-text("Select Character")'); -
ID selectors (For legacy/existing code)
typescriptpage.locator('#characterName');
Avoid
❌ Brittle CSS selectors:
// Bad - breaks with CSS changes
page.locator('.btn-primary:first-child');
page.locator('div.container > div:nth-child(2)');
// Bad - fragile to DOM structure changes
page.locator('body > div > div > button');
Anti-Patterns
Don't Use Hard-coded Waits
// Bad - arbitrary delay
await page.waitForTimeout(5000);
// Good - wait for specific condition
await page.waitForSelector('canvas');
await page.waitForFunction(() => {
return document.body.textContent?.includes('Connected');
});
Don't Skip Error Checking
// Always check for console errors
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text());
});
// After validation, verify no errors
if (errors.length > 0) {
console.error('Console errors found:', errors);
}
Alignment with E2E Tests
MCP validation agents should:
- Use same selectors as defined in
tests/pages/*.page.ts - Focus on NEW features - don't duplicate regression tests
- Use Vision MCP for visual validation when appropriate
- Take screenshots as evidence for validation reports
Example Alignment
// E2E test (tests/pages/game.page.ts):
export class GamePage {
readonly characterNameInput: Locator;
constructor(page: Page) {
this.characterNameInput = page.locator('#characterName');
}
}
// MCP validation uses same selector:
await page.fill('#characterName', 'TestPlayer');
Page Object Model Reference
E2E tests and MCP agents share Page Objects from tests/pages/:
| File | Purpose |
|---|---|
| tests/pages/base.page.ts | Base page class (goto, console, screenshot) |
| tests/pages/game.page.ts | Character selection, lobby, connection |
| tests/pages/multiplayer.page.ts | Multi-client setup, connect, cleanup |
Selector priority:
- Role-based:
page.getByRole('button', { name: 'Submit' }) - Label-based:
page.getByLabel('Character Name') - Test ID:
page.getByTestId('submit-button') - Text content:
page.getByText('LOBBY') - ID selector:
page.locator('#characterName')
Avoid: Brittle CSS selectors like .btn-primary:first-child
References
- tests/pages/base.page.ts - Base page class
- tests/pages/game.page.ts - Game-specific interactions
- tests/pages/multiplayer.page.ts - Multiplayer test helpers
- tests/e2e/multiplayer-suite.spec.ts - Example E2E tests
- .claude/skills/qa-browser-testing/SKILL.md - Browser testing skill
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?