Skip to Content
Developer GuideContributingAdding Scan Engines

Adding Scan Engines

Scan engines are the modular units that detect accessibility issues. ClearSight ships with three:

  • AxeCoreEngine — runs axe-core rules
  • LinkTextEngine — checks link text quality
  • TouchTargetEngine — checks touch target sizes

You can add custom engines to detect issues beyond what axe-core covers.

Engine interface

All engines in src/modules/scanner/engines/ follow this pattern:

export interface ScanEngine { name: string run(page: Page, url: string): Promise<EngineResult[]> } export interface EngineResult { type: 'confirmed' | 'potential' severity: 'critical' | 'serious' | 'moderate' | 'minor' wcagCriterion: string wcagLevel: 'A' | 'AA' elementSelector: string elementHtml: string description: string axeRuleId?: string confidenceScore?: number // 0-1, required if type === 'potential' }

page is a Playwright Page object with the target URL already loaded.

Example: writing a custom engine

// src/modules/scanner/engines/HeadingHierarchyEngine.ts import type { Page } from 'playwright' import type { ScanEngine, EngineResult } from './types' export class HeadingHierarchyEngine implements ScanEngine { name = 'HeadingHierarchyEngine' async run(page: Page, url: string): Promise<EngineResult[]> { const issues = await page.evaluate(() => { const headings = Array.from(document.querySelectorAll('h1,h2,h3,h4,h5,h6')) const results: Array<{ selector: string; html: string; level: number }> = [] let prevLevel = 0 headings.forEach((el, i) => { const level = parseInt(el.tagName[1]) if (level > prevLevel + 1) { results.push({ selector: `${el.tagName.toLowerCase()}:nth-of-type(${i + 1})`, html: el.outerHTML.slice(0, 200), level, }) } prevLevel = level }) return results }) return issues.map(issue => ({ type: 'confirmed' as const, severity: 'moderate' as const, wcagCriterion: '1.3.1', wcagLevel: 'A' as const, elementSelector: issue.selector, elementHtml: issue.html, description: `Heading level skipped from h${issue.level - 1} to h${issue.level}.`, })) } }

Plugging in the engine

Register your engine in the CustomChecks stage in src/modules/pipeline/:

// In the CustomChecks stage implementation const engines = [ new LinkTextEngine(), new TouchTargetEngine(), new HeadingHierarchyEngine(), // add your engine here ] const results = await Promise.allSettled( engines.map(engine => engine.run(page, url)) )

Engines run with Promise.allSettled — if your engine throws, it won’t break the other engines or the scan. Failed engines are logged but don’t fail the scan.

Testing your engine

Write a test that creates a minimal HTML page with the violation, loads it in Playwright, and asserts your engine finds the expected issues.

Next steps

Last updated on