@a11y-oracle/visual-engine
v1.3.2
Published
Visual pixel analysis engine for color contrast resolution using CDP screenshot capture and luminance analysis
Maintainers
Readme
@a11y-oracle/visual-engine
Visual pixel analysis engine for resolving incomplete color contrast warnings from axe-core. Provides CSS halo heuristic detection, CDP-based screenshot capture, and pixel-level luminance analysis using the WCAG Safe Assessment Matrix.
Installation
npm install @a11y-oracle/visual-engineNote: Most users should use
@a11y-oracle/axe-bridgeinstead, which wraps this engine and integrates directly with axe-core results. Install the visual-engine directly only if you need fine-grained control over individual analysis steps or are building a custom integration.
Usage
VisualContrastAnalyzer (Recommended)
The coordinator class runs the full pipeline for a single element:
import { VisualContrastAnalyzer } from '@a11y-oracle/visual-engine';
import type { CDPSessionLike } from '@a11y-oracle/cdp-types';
const analyzer = new VisualContrastAnalyzer(cdpSession);
const result = await analyzer.analyzeElement('#hero-text', 4.5);
switch (result.category) {
case 'pass': // Worst-case contrast passes threshold
case 'violation': // Best-case contrast fails threshold
case 'incomplete': // Split decision, dynamic content, or unresolvable
}Individual Pipeline Steps
Each stage of the pipeline is also exported for advanced use:
import {
getElementStyles,
captureElementBackground,
analyzeHalo,
extractPixelLuminance,
} from '@a11y-oracle/visual-engine';
// 1. Get computed styles via CDP
const styles = await getElementStyles(cdp, '#my-element');
// 2. CSS halo fast path (no screenshot needed)
const halo = analyzeHalo(styles, 4.5);
if (halo.hasValidHalo) {
// Element has a valid text stroke or shadow halo
}
// 3. Capture background with text hidden
const capture = await captureElementBackground(cdp, '#my-element');
// 4. Pixel-level luminance analysis
const pixels = extractPixelLuminance(capture.pngBuffer, capture.textColor);Analysis Pipeline
The VisualContrastAnalyzer.analyzeElement() method runs this pipeline:
Get Computed Styles — Fetches
color,backgroundColor,textStrokeWidth,textStrokeColor,textShadow, andbackgroundImagevia CDPRuntime.evaluate.Dynamic Content Check — Detects video/canvas ancestors, sub-1 opacity, and CSS blend modes. Dynamic content is left as
incomplete.CSS Halo Heuristic (fast path) — Checks for:
-webkit-text-stroke>= 1px with sufficient contrast against the backgroundtext-shadowwith 4+ zero-blur directional shadows covering all quadrants
If a valid halo is found and its color passes the threshold against the background, the element passes without a screenshot.
Screenshot Capture — Scrolls the element into the viewport (ensuring off-screen elements produce valid screenshots), hides the element's text (sets
color: transparent), captures a clipped screenshot via CDPPage.captureScreenshot, then restores the text.Pixel Analysis — Decodes the PNG, scans all opaque pixels for luminance extremes (lightest and darkest), and computes contrast ratios against the text color.
Safe Assessment Matrix:
- Pass: Text contrast against the lightest AND darkest background pixels both meet the threshold (worst-case passes)
- Violation: Text contrast against the lightest AND darkest background pixels both fail the threshold (best-case fails)
- Incomplete: One passes and one fails (split decision) — cannot safely categorize
API Reference
VisualContrastAnalyzer
constructor(cdp: CDPSessionLike)
Create an analyzer bound to a CDP session.
analyzeElement(selector, threshold?): Promise<ContrastAnalysisResult>
Run the full pipeline on an element.
- selector — CSS selector targeting the element
- threshold — Minimum contrast ratio (default: 4.5)
- Returns —
ContrastAnalysisResultwithcategory,textColor,halo,pixels, andreason
Halo Detection
analyzeHalo(styles, threshold?): HaloResult
Check if computed styles contain a valid CSS halo.
- styles —
ElementComputedStylesfromgetElementStyles() - threshold — Minimum contrast ratio (default: 4.5)
- Returns —
HaloResultwithhasValidHalo,haloContrast,method, andskipReason
parseTextShadow(css): TextShadowPart[]
Parse a CSS text-shadow value into structured parts.
Pixel Analysis
extractPixelLuminance(pngBuffer, textColor): PixelAnalysisResult | null
Decode a PNG and compute contrast ratios against a text color.
- pngBuffer — Raw PNG buffer from
captureElementBackground() - textColor —
RGBColorof the foreground text - Returns — Luminance extremes and contrast ratios, or
nullif no opaque pixels
decodePng(buffer): { width, height, data: Uint8Array }
Decode a PNG buffer into raw RGBA pixel data.
Screenshot Capture
getElementStyles(cdp, selector): Promise<ElementComputedStyles | null>
Fetch computed styles for an element via CDP.
captureElementBackground(cdp, selector): Promise<{ pngBuffer: Buffer; textColor: RGBColor | null } | null>
Capture a clipped screenshot of an element with its text hidden. Automatically scrolls the element into the viewport before capture, ensuring elements below the fold produce valid screenshots instead of blank images.
Types
import type {
ContrastAnalysisResult, // Full analysis result
ContrastCategory, // 'pass' | 'violation' | 'incomplete'
HaloResult, // CSS halo analysis result
PixelAnalysisResult, // Luminance extremes + contrast ratios
TextShadowPart, // Parsed text-shadow entry
ElementComputedStyles, // Computed CSS properties for contrast analysis
} from '@a11y-oracle/visual-engine';Dependencies
@a11y-oracle/focus-analyzer— ReusesrelativeLuminance(),contrastRatio(), andparseColor()for WCAG-compliant color math@a11y-oracle/cdp-types—CDPSessionLikeinterface for framework-agnostic CDP accessfast-png— Pure TypeScript PNG decoder/encoder (browser + Node.js compatible, no native dependencies)
