From fastlane-ui-design
Automated browser UI testing for Fastlane Angular components — Chrome DevTools MCP for live exploratory checks (screenshots, Lighthouse, axe), Playwright + @axe-core/playwright + playwright-lighthouse for committed test artifacts. Use when reviewing rendered UI, scanning a11y at runtime, gating perf/a11y in CI, or scaffolding component E2E tests.
How this skill is triggered — by the user, by Claude, or both
Slash command
/fastlane-ui-design:browser-ui-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
| Context | Tool |
| Context | Tool |
|---|---|
| Live Claude Code session, exploratory, no commit needed | Chrome DevTools MCP |
| Generated test artifact that runs in CI | Playwright |
| Existing app-level E2E flows (login, campaign creation, etc.) | Cypress 14 — do not port |
Chrome DevTools MCP is already installed and requires zero project dependencies. Playwright is added to the project when you first scaffold a component E2E test.
mcp__chrome-devtools__new_page
mcp__chrome-devtools__navigate_page (url: http://localhost:4200/your-route)
mcp__chrome-devtools__take_screenshot ← light mode
mcp__chrome-devtools__evaluate_script (script: "document.body.classList.add('dark-theme')")
mcp__chrome-devtools__take_screenshot ← dark mode
Save screenshots to .ui-design/reviews/{review_id}/ as light.png and dark.png.
mcp__chrome-devtools__lighthouse_audit (url: http://localhost:4200/your-route)
Interpret: performance, accessibility, best-practices, seo scores. Accessibility score below 100 is a blocker. Performance below 90 warrants investigation. Save the JSON result to .ui-design/reviews/{review_id}/lighthouse.json.
// Via evaluate_script
const script = `
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.3/axe.min.js';
document.head.appendChild(script);
await new Promise(r => script.onload = r);
return await axe.run();
`;
mcp__chrome-devtools__evaluate_script (script)
Parse violations from the result. Classify by impact (critical > serious > moderate > minor). Mark as "runtime" vs "static" in your audit report.
mcp__chrome-devtools__performance_start_trace
// ... user interaction sequence ...
mcp__chrome-devtools__performance_stop_trace
mcp__chrome-devtools__performance_analyze_insight
pnpm add -D @playwright/test @axe-core/playwright playwright-lighthouse
npx playwright install --with-deps
Print this command to the user rather than installing silently if Playwright isn't detected.
playwright.config.ts baseline for Fastlaneimport { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './apps/web/e2e/components',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:4200',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Place at apps/web/playwright.config.ts. Run via npx playwright test — separate from pnpm test (Jest/Cypress) so CI stages don't conflict.
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('my-component is WCAG AA compliant', async ({ page }) => {
await page.goto('/your-component-route');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
Tag these tests with @a11y for selective CI execution: npx playwright test --grep @a11y.
import { playAudit } from 'playwright-lighthouse';
import { chromium } from 'playwright';
test('performance and a11y gate', async () => {
const browser = await chromium.launch({ args: ['--remote-debugging-port=9222'] });
const page = await browser.newPage();
await page.goto('http://localhost:4200/your-route');
await playAudit({
page,
port: 9222,
thresholds: {
performance: 90,
accessibility: 100,
'best-practices': 90,
seo: 80,
},
});
await browser.close();
});
Run selectively (not on every commit): npx playwright test --grep @lighthouse.
test('my-component snapshot @visual', async ({ page }) => {
await page.goto('/your-component-route');
// Disable animations for stable snapshots
await page.addStyleTag({ content: '*, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; }' });
// Light mode snapshot
await expect(page).toHaveScreenshot('my-component-light.png', {
maxDiffPixels: 100,
});
// Dark mode snapshot
await page.evaluate(() => document.body.classList.add('dark-theme'));
await expect(page).toHaveScreenshot('my-component-dark.png', {
maxDiffPixels: 100,
});
});
Run: npx playwright test --grep @visual. Update baselines: npx playwright test --update-snapshots.
Mask dynamic regions (dates, usernames): page.locator('.dynamic-timestamp').
Angular Material components use shadow DOM for some internals. Use Playwright's >> piercing selector:
const internalInput = page.locator('mat-form-field >> input');
Encapsulate traversal in helper functions; never duplicate selector strings across tests.
// Verify a token resolves to a value (not empty string)
const tokenValue = await page.evaluate(() =>
getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary').trim()
);
expect(tokenValue).toBeTruthy();
await page.evaluate(() => document.body.classList.add('dark-theme'));
// Assert something changed
const bgBefore = /* light bg */;
const bgAfter = await page.evaluate(() =>
getComputedStyle(document.body).backgroundColor
);
expect(bgAfter).not.toEqual(bgBefore);
const icon = page.locator('mat-icon.status-success');
await expect(icon).toBeVisible();
await expect(icon).toHaveText('check_circle');
@axe-core/playwright)--grep @a11y)--grep @lighthouse) — run selectively, not on every commit@lighthouse tag and run it on schedule or pre-merge for important routes..ui-design/ which should be in .gitignore.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub pat-richardson/fastlane-ui-design --plugin fastlane-ui-design