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.npx claudepluginhub pat-richardson/fastlane-ui-design --plugin fastlane-ui-designGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Implements work from a spec or tickets using TDD at agreed seams, with regular typechecking and test runs, followed by code review.