Browser testing with devtools
Skill codexskills/agent-forge/skills/browser-testing-with-devtools
Production-grade AI agent skills — enhanced alternative to addyosmani/agent-skills. 23 lifecycle skills for Define, Plan, Build, Verify, Review, Ship. Works with OpenCode, Claude Code, Codex, Cursor, Gemini CLI.
npx -y skills add codexskills/agent-forge --skill browser-testing-with-devtoolsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Test web applications in real browsers via Chrome DevTools Protocol including DOM inspection, console analysis, network profiling, performance auditing, Lighthouse CI, and visual regression.
SKILL.md
14.9 KB, as published. Nobody here has run it
Browser Testing with DevTools
Overview
Browser testing with Chrome DevTools is the practice of using browser-native debugging and auditing tools to verify web application correctness, performance, accessibility, and reliability. It bridges the gap between unit tests (which verify logic) and real user experiences (which depend on rendering, networking, and runtime behavior).
DevTools-based testing catches issues that unit tests never will: layout shifts, console errors from third-party scripts, network waterfalls, memory leaks, accessibility violations, and real rendering performance. It is the most direct way to test what users actually experience.
When to Use
- Before shipping any user-facing feature
- Debugging layout, styling, or rendering issues
- Investigating performance regressions
- Auditing accessibility compliance
- Analyzing network requests and API calls
- Debugging console errors or warnings
- Profiling JavaScript execution and memory usage
- Verifying responsive design across device sizes
- Testing service workers and offline behavior
- Auditing third-party script impact
- Comparing before/after screenshots for visual regression
- Setting up CI/CD gates for performance and accessibility
Process
Step 1: Session Initialization
Launch the browser with DevTools-specific configurations:
const browser = await puppeteer.launch({
headless: false, // false for visual debugging, 'new' for CI
args: [
'--auto-open-devtools-for-tabs',
'--window-size=1440,900',
'--disable-notifications',
'--disable-geolocation',
'--disable-speech-api',
'--disable-extensions',
'--no-sandbox', // required in CI
'--disable-setuid-sandbox',
],
defaultViewport: { width: 1440, height: 900 }
});
Essential CDP domains to enable:
Network: Request/response capture, throttlingPage: Navigation, DOM events, lifecycleDOM: Element inspection and modificationCSS: Style computation, coverageRuntime: JavaScript execution, consolePerformance: Timeline and metricsAudits: Lighthouse integration
Step 2: DOM Inspection and Verification
Test that the DOM matches expected structure:
async function verifyDOMStructure(page) {
// Verify component exists
const selector = '[data-testid="product-card"]';
await page.waitForSelector(selector, { timeout: 5000 });
// Verify text content
const text = await page.$eval(selector, el => el.textContent);
expect(text).toContain('Expected Product Name');
// Verify attributes
const href = await page.$eval('a.cta-button', el => el.getAttribute('href'));
expect(href).toBe('/checkout');
// Verify computed styles
const color = await page.$eval('h1', el =>
getComputedStyle(el).getPropertyValue('color')
);
expect(color).toBe('rgb(33, 37, 41)');
// Verify visibility (not just existence)
const isVisible = await page.$eval('.mobile-menu', el => {
const style = getComputedStyle(el);
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
});
expect(isVisible).toBe(true);
}
DOM verification checklist:
- All critical elements exist with correct attributes
- Text content matches expected values
- Computed styles match design tokens
- Elements are visible and interactable (not just present in DOM)
- ARIA attributes are present on custom interactive elements
- Focus order matches visual order
- No duplicate IDs
Step 3: Console Error Capture
Console errors are the #1 indicator of runtime problems. Capture every one:
async function captureConsoleErrors(page) {
const errors = [];
page.on('console', msg => {
if (msg.type() === 'error' || msg.type() === 'warning') {
errors.push({
type: msg.type(),
text: msg.text(),
location: msg.location(),
timestamp: Date.now(),
stack: msg.stackTrace ? msg.stackTrace().callFrames : []
});
}
});
page.on('pageerror', err => {
errors.push({
type: 'page_error',
text: err.message,
stack: err.stack,
timestamp: Date.now()
});
});
await page.goto('http://localhost:3000');
await page.waitForLoadState('networkidle');
return errors;
}
// In test:
const errors = await captureConsoleErrors(page);
expect(errors.filter(e => e.type === 'error')).toHaveLength(0);
Error severity classification:
- CRITICAL: Uncaught exceptions, security errors, API failures
- HIGH: Deprecation warnings, unhandled promise rejections
- MEDIUM: Third-party script errors, DevTools protocol errors
- LOW: Verbose warnings, experimental feature notices
Rules:
- Zero CRITICAL errors allowed
- Zero HIGH errors allowed in production
- MEDIUM errors must be documented with tracking issue
- Ignore LOW errors after verification (but log them)
Step 4: Network Analysis
Test request/response behavior, not just status codes:
async function analyzeNetwork(page) {
const requests = [];
page.on('request', req => {
requests.push({
url: req.url(),
method: req.method(),
type: req.resourceType(),
headers: req.headers(),
startTime: performance.now()
});
});
page.on('response', res => {
const req = requests.find(r => r.url === res.url());
if (req) {
req.status = res.status();
req.responseHeaders = res.headers();
req.duration = performance.now() - req.startTime;
req.size = parseInt(res.headers()['content-length'] || '0');
}
});
await page.goto('http://localhost:3000');
await page.waitForLoadState('networkidle');
return requests;
}
Network analysis checklist:
- No 4xx/5xx responses (except expected 404s)
- No API calls to unauthorized endpoints (check CORS, auth headers)
- Request payloads match documented API contracts
- Response times within budget (< 200ms for critical API calls)
- Asset sizes within budget (JS < 300KB, CSS < 50KB)
- Proper caching headers present (
Cache-Control,ETag) - No mixed content (HTTP on HTTPS page)
- No duplicate or redundant requests
- Compression enabled (Content-Encoding: gzip/brotli)
- Request waterfalls show minimal blocking
Step 5: Performance Profiling
Profile runtime performance to identify bottlenecks:
async function profilePerformance(page) {
// Start profiling
await page.tracing.start({
path: 'trace.json',
categories: [
'devtools.timeline',
'loading',
'blink.user_timing',
'disabled-by-default-devtools.timeline',
'disabled-by-default-devtools.timeline.frame',
'disabled-by-default-devtools.timeline.stack'
]
});
// Perform user interaction
await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
await page.click('[data-testid="search-input"]');
await page.type('[data-testid="search-input"]', 'test query');
await page.click('[data-testid="search-button"]');
await page.waitForSelector('[data-testid="search-results"]', { timeout: 5000 });
// Stop profiling
await page.tracing.stop();
// Parse trace
const trace = JSON.parse(fs.readFileSync('trace.json', 'utf8'));
return trace;
}
Performance metrics to extract from traces:
- Main thread idle time / busy time ratio
- Long tasks (>50ms) count and duration
- Layout and style recalc frequency and duration
- JavaScript execution time (parse, compile, execute)
- GC pause time and frequency
- Frame rate (target: 60fps for animations)
- Paint and composite time
Performance budget gates:
- FCP < 1.5s
- LCP < 2.5s
- TBT < 200ms
- CLS < 0.1
- No long tasks on interaction
- No forced reflows (layout thrashing)
- All animations run at 60fps
- Memory usage stable (no leaks over time)
Step 6: Lighthouse Audits
Run automated Lighthouse audits as CI gates:
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
async function runLighthouseAudit(url) {
const chrome = await chromeLauncher.launch({
chromeFlags: ['--headless', '--no-sandbox']
});
const runnerResult = await lighthouse(url, {
port: chrome.port,
output: 'json',
logLevel: 'info',
onlyCategories: [
'performance',
'accessibility',
'best-practices',
'seo',
'pwa'
],
throttling: {
// Mobile simulation
rttMs: 150,
throughputKbps: 1600,
cpuSlowdownMultiplier: 4
}
});
await chrome.kill();
return runnerResult.lhr.categories;
}
// In test:
const scores = await runLighthouseAudit('http://localhost:3000');
expect(scores.performance.score).toBeGreaterThanOrEqual(0.9);
expect(scores.accessibility.score).toBeGreaterThanOrEqual(0.95);
expect(scores['best-practices'].score).toBeGreaterThanOrEqual(0.9);
Lighthouse score thresholds:
- Performance: ≥ 90 (mobile simulated)
- Accessibility: ≥ 95
- Best Practices: ≥ 90
- SEO: ≥ 95
- PWA: ≥ 80 (if applicable)
Step 7: DevTools Accessibility Audit
Beyond Lighthouse, run programmatic accessibility checks:
async function runAccessibilityAudit(page) {
// Inject axe-core
await page.addScriptTag({
path: require.resolve('axe-core/axe.min.js')
});
// Run audit
const results = await page.evaluate(() => {
return axe.run({
runOnly: {
type: 'tag',
values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
}
});
});
return results;
}
// In test:
const results = await runAccessibilityAudit(page);
expect(results.violations.filter(v => v.impact === 'critical')).toHaveLength(0);
expect(results.violations.filter(v => v.impact === 'serious')).toHaveLength(0);
Key WCAG checks beyond automated tools:
- Manual keyboard navigation test (tab through all interactive elements)
- Screen reader test (VoiceOver / NVDA reads content correctly)
- Focus indicator visibility (2px+ outline, 3:1 contrast ratio)
- Zoom to 200% without loss of content or functionality
- Color contrast verification for all text elements
- Touch target size (44×44px minimum)
- prefers-reduced-motion respected
- Skip navigation link functional
Step 8: Screenshot Comparison
Capture and compare screenshots for visual regression:
async function captureScreenshot(page, name) {
await page.screenshot({
path: `screenshots/${name}.png`,
fullPage: true,
type: 'png'
});
}
async function compareScreenshots(baseline, current) {
const pixelmatch = require('pixelmatch');
const PNG = require('pngjs').PNG;
const img1 = PNG.sync.read(fs.readFileSync(baseline));
const img2 = PNG.sync.read(fs.readFileSync(current));
const { width, height } = img1;
const diff = new PNG({ width, height });
const mismatched = pixelmatch(img1.data, img2.data, diff.data, width, height, {
threshold: 0.1
});
const diffPercent = (mismatched / (width * height)) * 100;
fs.writeFileSync('diff.png', PNG.sync.write(diff));
return { mismatched, diffPercent };
}
// In CI:
const result = await compareScreenshots('baseline.png', 'screenshot.png');
expect(result.diffPercent).toBeLessThan(1); // Less than 1% pixel difference
Screenshot comparison rules:
- Baseline screenshots stored in version control (or S3 for large projects)
- Always run with the same viewport size
- Disable animations during capture (consistent rendering)
- Use
fullPage: truefor comprehensive comparison - Set appropriate pixel threshold (0.1-0.5% for CI, 0% for pixel-perfect)
- Automatically approve baseline updates when intentional changes are made
Anti-Rationalization Table
| Excuse | Rebuttal |
|---|---|
| "Unit tests are enough, I don't need browser testing" | Unit tests verify logic in isolation. They do not verify rendering, layout, network behavior, or real browser APIs. A component can pass every unit test and still be broken in the browser. |
| "We'll catch issues in manual QA" | Manual QA is too slow and too inconsistent. It catches maybe 60% of visual/behavioral issues. Automated browser testing catches 95%+ and runs in minutes, not days. |
| "DevTools testing is flaky" | Flakiness is a symptom of poor test design, not a limitation of the tool. Use waitForSelector, networkidle, and retry logic. If your tests are flaky, fix the tests, don't abandon the practice. |
| "Lighthouse scores are just guidelines" | They're guidelines backed by real user experience data. Sites with Lighthouse scores < 50 have 3x higher bounce rates. Improve the score, improve the experience. |
| "Screenshot tests are too brittle" | Use pixel thresholds (0.1%), exclude dynamic content regions, and auto-approve baseline updates on intentional changes. The brittleness is manageable and the value (catching visual regressions) is enormous. |
| "Accessibility is the design team's responsibility" | Accessibility is everyone's responsibility. Engineers implement the UI; engineers must verify it works with assistive technology. Design provides the intent; engineering validates the implementation. |
| "We don't have time to set up all this infrastructure" | You don't have time not to. A single production visual regression costs more to debug than setting up screenshot comparison for the entire app. Start with console error capture — it's 10 lines of code and catches 50% of issues. |
Red Flags
- No console error capture in CI: Silent failures shipping to production
- No Lighthouse audit in CI: Performance and accessibility regressions unchecked
- All tests pass but page has visible layout issues: Missing visual regression testing
- Network tab shows 4xx/5xx errors that "don't affect functionality": They affect user trust and SEO
- JavaScript errors in console that are "expected": If they're expected, handle them. If not, fix them.
- No throttling in performance tests: Real users on mobile networks have very different experiences
- Screenshots not stored in version control: Unable to detect visual regressions over time
- Accessibility violations present but "no one has complained": Users with disabilities don't complain; they leave
Verification
- Test: Run console capture across all pages and verify zero errors
- Test: Run Lighthouse audit and verify all category scores meet thresholds
- Test: Run axe-core audit and verify zero critical/serious violations
- Test: Capture and compare screenshots before/after change
- Test: Throttle to Slow 3G and verify FCP < 3s
- Test: Simulate mobile device (375×812) and verify responsive layout
- Test: Keyboard-navigate the entire page (Tab, Shift+Tab, Enter, Escape)
- Test: Verify no network request failures (all responses 2xx or expected)
- Test: Profile memory usage over 10 page interactions and verify stability
- Test: Run the same test suite headless and headed to verify consistency