@diegovelasquezweb/a11y-engine 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "WCAG 2.2 AA accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli/audit.mjs CHANGED
@@ -46,6 +46,7 @@ Execution & Emulation:
46
46
  --skip-reports Omit HTML and PDF report generation (default).
47
47
  --skip-patterns Skip source code pattern scanning even if --project-dir is set.
48
48
  --affected-only Re-scan only routes that had violations in the previous scan (faster re-audits).
49
+ --engines <csv> Comma-separated engines to run: axe,cdp,pa11y (default: all).
49
50
  --wait-ms <num> Time to wait after page load (default: 2000).
50
51
  --timeout-ms <num> Network timeout (default: 30000).
51
52
  -h, --help Show this help.
@@ -154,6 +155,7 @@ async function main() {
154
155
  const colorScheme = getArgValue("color-scheme");
155
156
  const target = getArgValue("target");
156
157
  const headless = !argv.includes("--headed");
158
+ const enginesArg = getArgValue("engines");
157
159
 
158
160
  const onlyRule = getArgValue("only-rule");
159
161
  const skipReports = argv.includes("--skip-reports") || !argv.includes("--with-reports");
@@ -257,6 +259,7 @@ async function main() {
257
259
  scanArgs.push("--viewport", `${viewport.width}x${viewport.height}`);
258
260
  }
259
261
  if (axeTags) scanArgs.push("--axe-tags", axeTags);
262
+ if (enginesArg) scanArgs.push("--engines", enginesArg);
260
263
 
261
264
  await runScript("../pipeline/dom-scanner.mjs", scanArgs, childEnv);
262
265
 
package/src/index.d.mts CHANGED
@@ -209,6 +209,16 @@ export interface SourcePatternOptions {
209
209
  onlyPattern?: string;
210
210
  }
211
211
 
212
+ // ---------------------------------------------------------------------------
213
+ // Engine selection
214
+ // ---------------------------------------------------------------------------
215
+
216
+ export interface EngineSelection {
217
+ axe?: boolean;
218
+ cdp?: boolean;
219
+ pa11y?: boolean;
220
+ }
221
+
212
222
  // ---------------------------------------------------------------------------
213
223
  // Audit options
214
224
  // ---------------------------------------------------------------------------
@@ -232,6 +242,7 @@ export interface RunAuditOptions {
232
242
  projectDir?: string;
233
243
  skipPatterns?: boolean;
234
244
  screenshotsDir?: string;
245
+ engines?: EngineSelection;
235
246
  onProgress?: (step: string, status: string, extra?: Record<string, unknown>) => void;
236
247
  }
237
248
 
package/src/index.mjs CHANGED
@@ -447,6 +447,7 @@ export function getOverview(findings, payload = null) {
447
447
  * framework?: string,
448
448
  * projectDir?: string,
449
449
  * skipPatterns?: boolean,
450
+ * engines?: { axe?: boolean, cdp?: boolean, pa11y?: boolean },
450
451
  * onProgress?: (step: string, status: string, extra?: object) => void,
451
452
  * }} options
452
453
  * @returns {Promise<{ findings: object[], metadata: object, incomplete_findings?: object[] }>}
@@ -459,7 +460,14 @@ export async function runAudit(options) {
459
460
 
460
461
  const onProgress = options.onProgress || null;
461
462
 
462
- // Step 1: DOM scan (axe + CDP + pa11y)
463
+ // Normalize engines default all enabled
464
+ const engines = {
465
+ axe: options.engines?.axe !== false,
466
+ cdp: options.engines?.cdp !== false,
467
+ pa11y: options.engines?.pa11y !== false,
468
+ };
469
+
470
+ // Step 1: DOM scan (selected engines)
463
471
  if (onProgress) onProgress("page", "running");
464
472
 
465
473
  const scanPayload = await runDomScanner(
@@ -479,6 +487,7 @@ export async function runAudit(options) {
479
487
  excludeSelectors: options.excludeSelectors,
480
488
  screenshotsDir: options.screenshotsDir,
481
489
  projectDir: options.projectDir,
490
+ engines,
482
491
  },
483
492
  { onProgress },
484
493
  );
@@ -531,6 +540,10 @@ export async function runAudit(options) {
531
540
 
532
541
  if (onProgress) onProgress("intelligence", "done");
533
542
 
543
+ // Attach active engines to metadata so consumers know which ran
544
+ findingsPayload.metadata = findingsPayload.metadata || {};
545
+ findingsPayload.metadata.engines = engines;
546
+
534
547
  return findingsPayload;
535
548
  }
536
549
 
@@ -63,6 +63,7 @@ Options:
63
63
  --crawl-depth <number> How deep to follow links during discovery (1-3, default: 2)
64
64
  --wait-until <value> Page load strategy: domcontentloaded|load|networkidle (default: domcontentloaded)
65
65
  --viewport <WxH> Viewport dimensions as WIDTHxHEIGHT (e.g., 375x812)
66
+ --engines <csv> Engines to run: axe,cdp,pa11y (default: all)
66
67
  -h, --help Show this help
67
68
  `);
68
69
  }
@@ -95,6 +96,7 @@ function parseArgs(argv) {
95
96
  crawlDepth: DEFAULTS.crawlDepth,
96
97
  viewport: null,
97
98
  axeTags: null,
99
+ engines: { axe: true, cdp: true, pa11y: true },
98
100
  };
99
101
 
100
102
  for (let i = 0; i < argv.length; i += 1) {
@@ -121,6 +123,10 @@ function parseArgs(argv) {
121
123
  if (key === "--color-scheme") args.colorScheme = value;
122
124
  if (key === "--screenshots-dir") args.screenshotsDir = value;
123
125
  if (key === "--axe-tags") args.axeTags = value.split(",").map((s) => s.trim());
126
+ if (key === "--engines") {
127
+ const active = value.split(",").map((s) => s.trim().toLowerCase());
128
+ args.engines = { axe: active.includes("axe"), cdp: active.includes("cdp"), pa11y: active.includes("pa11y") };
129
+ }
124
130
  if (key === "--viewport") {
125
131
  const [w, h] = value.split("x").map(Number);
126
132
  if (w && h) args.viewport = { width: w, height: h };
@@ -945,6 +951,11 @@ export async function runDomScanner(options = {}, callbacks = {}) {
945
951
  viewport: options.viewport || null,
946
952
  axeTags: options.axeTags || null,
947
953
  projectDir: options.projectDir || null,
954
+ engines: {
955
+ axe: options.engines?.axe !== false,
956
+ cdp: options.engines?.cdp !== false,
957
+ pa11y: options.engines?.pa11y !== false,
958
+ },
948
959
  };
949
960
 
950
961
  if (!args.baseUrl) throw new Error("Missing required option: baseUrl");
@@ -1151,36 +1162,57 @@ async function _runDomScannerInternal(args) {
1151
1162
 
1152
1163
  writeProgress("page", "done");
1153
1164
 
1154
- // Step 1: axe-core
1155
- writeProgress("axe", "running");
1156
- const result = await analyzeRoute(
1157
- tabPage,
1158
- targetUrl,
1159
- args.waitMs,
1160
- args.excludeSelectors,
1161
- args.onlyRule,
1162
- args.timeoutMs,
1163
- 2,
1164
- args.waitUntil,
1165
- args.axeTags,
1166
- );
1167
- const axeViolationCount = result.violations?.length || 0;
1168
- writeProgress("axe", "done", { found: axeViolationCount });
1169
- log.info(`axe-core: ${axeViolationCount} violation(s) found`);
1165
+ let result = { url: targetUrl, violations: [], incomplete: [], passes: [], metadata: {} };
1166
+ let cdpViolations = [];
1167
+ let pa11yViolations = [];
1168
+
1169
+ // Step 1: axe-core (conditional)
1170
+ if (args.engines.axe) {
1171
+ writeProgress("axe", "running");
1172
+ result = await analyzeRoute(
1173
+ tabPage,
1174
+ targetUrl,
1175
+ args.waitMs,
1176
+ args.excludeSelectors,
1177
+ args.onlyRule,
1178
+ args.timeoutMs,
1179
+ 2,
1180
+ args.waitUntil,
1181
+ args.axeTags,
1182
+ );
1183
+ const axeViolationCount = result.violations?.length || 0;
1184
+ writeProgress("axe", "done", { found: axeViolationCount });
1185
+ log.info(`axe-core: ${axeViolationCount} violation(s) found`);
1186
+ } else {
1187
+ // Navigate for CDP/pa11y even if axe is off
1188
+ await tabPage.goto(targetUrl, { waitUntil: args.waitUntil, timeout: args.timeoutMs });
1189
+ await tabPage.waitForLoadState("networkidle", { timeout: args.waitMs }).catch(() => {});
1190
+ result.metadata = await tabPage.evaluate(() => ({ title: document.title }));
1191
+ log.info("axe-core: skipped (disabled)");
1192
+ }
1170
1193
 
1171
- // Step 2: CDP checks
1172
- writeProgress("cdp", "running");
1173
- const cdpViolations = await runCdpChecks(tabPage);
1174
- writeProgress("cdp", "done", { found: cdpViolations.length });
1175
- log.info(`CDP checks: ${cdpViolations.length} issue(s) found`);
1194
+ // Step 2: CDP checks (conditional)
1195
+ if (args.engines.cdp) {
1196
+ writeProgress("cdp", "running");
1197
+ cdpViolations = await runCdpChecks(tabPage);
1198
+ writeProgress("cdp", "done", { found: cdpViolations.length });
1199
+ log.info(`CDP checks: ${cdpViolations.length} issue(s) found`);
1200
+ } else {
1201
+ log.info("CDP checks: skipped (disabled)");
1202
+ }
1176
1203
 
1177
- // Step 3: pa11y
1178
- writeProgress("pa11y", "running");
1179
- const pa11yViolations = await runPa11yChecks(targetUrl, args.axeTags);
1180
- writeProgress("pa11y", "done", { found: pa11yViolations.length });
1181
- log.info(`pa11y: ${pa11yViolations.length} issue(s) found`);
1204
+ // Step 3: pa11y (conditional)
1205
+ if (args.engines.pa11y) {
1206
+ writeProgress("pa11y", "running");
1207
+ pa11yViolations = await runPa11yChecks(targetUrl, args.axeTags);
1208
+ writeProgress("pa11y", "done", { found: pa11yViolations.length });
1209
+ log.info(`pa11y: ${pa11yViolations.length} issue(s) found`);
1210
+ } else {
1211
+ log.info("pa11y: skipped (disabled)");
1212
+ }
1182
1213
 
1183
1214
  // Step 4: Merge results
1215
+ const axeViolationCount = result.violations?.length || 0;
1184
1216
  writeProgress("merge", "running");
1185
1217
  const mergedViolations = mergeViolations(
1186
1218
  result.violations || [],
@@ -1220,6 +1252,7 @@ async function _runDomScannerInternal(args) {
1220
1252
  generated_at: new Date().toISOString(),
1221
1253
  base_url: baseUrl,
1222
1254
  onlyRule: args.onlyRule || null,
1255
+ engines: args.engines,
1223
1256
  projectContext,
1224
1257
  routes: results,
1225
1258
  };