Visual QA testing with comprehensive screenshot capture, comparison, and verification across multiple states and breakpoints
Captures comprehensive screenshots across all UI states (empty, loading, error, success) and multiple breakpoints (mobile to desktop) for visual regression testing. Claude uses this after UI changes or before deployments to systematically verify visual correctness and detect layout issues.
/plugin marketplace add usmanali4073/stylemate-plugins/plugin install stylemate-architecture@stylemate-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Use this skill to perform visual quality assurance by capturing screenshots of UI states, comparing layouts, and verifying visual correctness across different scenarios.
Captures screenshots for ALL UI states:
For EACH state, capture at ALL breakpoints:
Result: Comprehensive screenshot matrix (10 states × 5 breakpoints = 50 screenshots per page)
Screenshot specific components:
Screenshot each step of user journeys:
Compare screenshots to identify:
mcp__playwright__browser_navigate - Navigate to pagesmcp__playwright__browser_resize - Change viewport CRITICALmcp__playwright__browser_take_screenshot - Capture screenshots PRIMARY TOOLmcp__playwright__browser_click - Trigger interactionsmcp__playwright__browser_type - Fill inputsmcp__playwright__browser_fill_form - Fill formsmcp__playwright__browser_evaluate - Manipulate DOM/trigger statesmcp__playwright__browser_wait_for - Wait for state changesmcp__playwright__browser_snapshot - Get accessibility treeconst testSession = {
feature: 'Schedule Calendar',
url: 'http://172.18.0.5:80/schedules',
breakpoints: [375, 414, 768, 1280, 1920],
states: ['empty', 'loading', 'loaded', 'error'],
screenshots: []
}
// Navigate
mcp__playwright__browser_navigate(testSession.url)
// For each breakpoint
for (const width of testSession.breakpoints) {
// Resize
mcp__playwright__browser_resize(width, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
// Screenshot
await mcp__playwright__browser_take_screenshot(
filename: `empty_state_${width}px.png`,
fullPage: true
)
testSession.screenshots.push({
state: 'empty',
breakpoint: width,
filename: `empty_state_${width}px.png`
})
}
// Trigger loading state (if possible)
await mcp__playwright__browser_evaluate(`
// Show loading skeletons or spinners
document.querySelector('.loading-indicator')?.classList.add('visible')
// Or trigger a slow API call
`)
// Capture at all breakpoints
for (const width of testSession.breakpoints) {
mcp__playwright__browser_resize(width, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `loading_state_${width}px.png`,
fullPage: true
)
}
// Wait for data to load
await mcp__playwright__browser_wait_for(
selector: '.schedule-calendar',
state: 'visible'
)
// Capture at all breakpoints
for (const width of testSession.breakpoints) {
mcp__playwright__browser_resize(width, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `loaded_state_${width}px.png`,
fullPage: true
)
console.log(`✅ Captured loaded state at ${width}px`)
}
// Trigger error state
// Option 1: Fill form with invalid data
await mcp__playwright__browser_fill_form({
'employee': '', // Required field left empty
'date': 'invalid-date'
})
await mcp__playwright__browser_click(selector: 'button[type="submit"]')
// Wait for error to appear
await mcp__playwright__browser_wait_for(
selector: '.error-message',
state: 'visible'
)
// Capture at all breakpoints
for (const width of testSession.breakpoints) {
mcp__playwright__browser_resize(width, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `error_state_${width}px.png`,
fullPage: true
)
console.log(`✅ Captured error state at ${width}px`)
}
// Click into each input field to trigger validation
const formFields = ['employee', 'date', 'startTime', 'endTime']
for (const field of formFields) {
// Click field
await mcp__playwright__browser_click(selector: `[name="${field}"]`)
// Click outside to blur (trigger validation)
await mcp__playwright__browser_click(selector: 'body')
// Wait for validation message
await mcp__playwright__browser_wait_for(timeout: 200)
// Screenshot at mobile only (375px)
mcp__playwright__browser_resize(375, 667)
await mcp__playwright__browser_take_screenshot(
filename: `validation_${field}_375px.png`
)
// Screenshot at desktop (1280px)
mcp__playwright__browser_resize(1280, 720)
await mcp__playwright__browser_take_screenshot(
filename: `validation_${field}_1280px.png`
)
}
// Hover state (desktop only)
mcp__playwright__browser_resize(1280, 720)
// Find interactive elements
const interactiveElements = await mcp__playwright__browser_evaluate(`
Array.from(document.querySelectorAll('button, a, .clickable')).map(el => ({
selector: el.className || el.tagName,
text: el.textContent?.substring(0, 20)
}))
`)
for (const element of interactiveElements.slice(0, 5)) { // First 5 elements
// Hover
await mcp__playwright__browser_evaluate(`
const el = document.querySelector('${element.selector}')
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }))
`)
await mcp__playwright__browser_take_screenshot(
filename: `hover_${element.selector}_1280px.png`
)
console.log(`✅ Captured hover state: ${element.text}`)
}
// Focus state (keyboard navigation)
for (const element of interactiveElements.slice(0, 5)) {
// Focus
await mcp__playwright__browser_evaluate(`
document.querySelector('${element.selector}')?.focus()
`)
await mcp__playwright__browser_take_screenshot(
filename: `focus_${element.selector}_1280px.png`
)
console.log(`✅ Captured focus state: ${element.text}`)
}
// Open modal
await mcp__playwright__browser_click(selector: 'button[data-testid="create-schedule"]')
// Wait for modal to appear
await mcp__playwright__browser_wait_for(
selector: '[role="dialog"]',
state: 'visible'
)
// Capture modal at all breakpoints
for (const width of [375, 768, 1280]) {
mcp__playwright__browser_resize(width, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `modal_create_${width}px.png`,
fullPage: true
)
console.log(`✅ Captured modal at ${width}px`)
}
// Close modal
await mcp__playwright__browser_click(selector: '[aria-label="Close"]')
// Long page content - capture top, middle, bottom
const positions = ['top', 'middle', 'bottom']
for (const width of [375, 1280]) {
mcp__playwright__browser_resize(width, 800)
// Top
await mcp__playwright__browser_evaluate(`window.scrollTo(0, 0)`)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `scroll_top_${width}px.png`,
fullPage: false // Viewport only
)
// Middle
await mcp__playwright__browser_evaluate(`
window.scrollTo(0, document.documentElement.scrollHeight / 2)
`)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `scroll_middle_${width}px.png`,
fullPage: false
)
// Bottom
await mcp__playwright__browser_evaluate(`
window.scrollTo(0, document.documentElement.scrollHeight)
`)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `scroll_bottom_${width}px.png`,
fullPage: false
)
console.log(`✅ Captured scroll positions at ${width}px`)
}
const report = {
feature: testSession.feature,
test_date: new Date().toISOString(),
total_screenshots: testSession.screenshots.length,
breakpoints_tested: testSession.breakpoints,
states_captured: testSession.states,
screenshots: testSession.screenshots,
visual_issues: [],
status: 'PASS'
}
console.log('=== Visual QA Test Report ===')
console.log(`Feature: ${report.feature}`)
console.log(`Screenshots: ${report.total_screenshots}`)
console.log(`Breakpoints: ${report.breakpoints_tested.join(', ')}`)
console.log(`States: ${report.states_captured.join(', ')}`)
CRITICAL: Always use Docker container IP, not localhost.
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {container}_ui
# Wrong:
http://localhost:3003
# Correct:
http://172.18.0.5:80
Use consistent naming for easy organization:
{state}_{component}_{breakpoint}px_{variant}.png
Examples:
- empty_state_calendar_375px.png
- loading_state_table_1280px.png
- error_state_form_768px_validation.png
- hover_state_button_1280px_primary.png
- focus_state_input_375px_email.png
// Test configuration
const visualQATest = {
feature: 'Schedule Calendar Page',
url: 'http://172.18.0.5:80/schedules',
container: 'scheduling_ui',
breakpoints: [375, 768, 1280],
states: [
'empty',
'loading',
'loaded_with_data',
'create_modal',
'edit_modal',
'delete_confirmation',
'validation_errors',
'success_message',
'error_message'
]
}
// Step 1: Navigate
mcp__playwright__browser_navigate(visualQATest.url)
// Step 2: Empty state
console.log('Capturing empty state...')
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_wait_for(timeout: 300)
await mcp__playwright__browser_take_screenshot(
filename: `empty_${bp}px.png`,
fullPage: true
)
}
// Step 3: Loading state (if available)
console.log('Capturing loading state...')
// Trigger loading...
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_take_screenshot(
filename: `loading_${bp}px.png`,
fullPage: true
)
}
// Step 4: Loaded with data
console.log('Capturing loaded state...')
await mcp__playwright__browser_wait_for(selector: '.schedule-item', state: 'visible')
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_take_screenshot(
filename: `loaded_${bp}px.png`,
fullPage: true
)
}
// Step 5: Create modal
console.log('Capturing create modal...')
await mcp__playwright__browser_click(selector: 'button:has-text("Create Schedule")')
await mcp__playwright__browser_wait_for(selector: '[role="dialog"]', state: 'visible')
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_take_screenshot(
filename: `create_modal_${bp}px.png`,
fullPage: true
)
}
// Step 6: Validation errors
console.log('Capturing validation errors...')
await mcp__playwright__browser_click(selector: 'button[type="submit"]')
await mcp__playwright__browser_wait_for(selector: '.error-message', state: 'visible')
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_take_screenshot(
filename: `validation_errors_${bp}px.png`,
fullPage: true
)
}
// Step 7: Success state (fill form correctly)
console.log('Capturing success state...')
await mcp__playwright__browser_fill_form({
'employee': 'John Doe',
'date': '2025-01-20',
'startTime': '09:00',
'endTime': '17:00'
})
await mcp__playwright__browser_click(selector: 'button[type="submit"]')
await mcp__playwright__browser_wait_for(selector: '.success-message', state: 'visible')
for (const bp of visualQATest.breakpoints) {
mcp__playwright__browser_resize(bp, 800)
await mcp__playwright__browser_take_screenshot(
filename: `success_message_${bp}px.png`,
fullPage: true
)
}
// Close browser
mcp__playwright__browser_close()
console.log('✅ Visual QA testing complete!')
console.log(`Total screenshots captured: ${visualQATest.states.length * visualQATest.breakpoints.length}`)
The QA Frontend Engineer agent should use this skill to:
This skill provides comprehensive visual documentation and verification that catches visual bugs, layout issues, and responsive design problems through systematic screenshot capture and analysis.
Master binary analysis patterns including disassembly, decompilation, control flow analysis, and code pattern recognition. Use when analyzing executables, understanding compiled code, or performing static analysis on binaries.