#!/usr/bin/env node // Anti-regression gate for the static accessibility test suite. // // We enforce a minimum number of `*.a11y.test.tsx` / `*.axe.test.tsx` files // in the web app. // Removing a11y coverage is a deliberate act and must show up in code review. // // Pair with `scripts/verify-test-density.mjs` — that script enforces a // per-workspace ratio of test files to source files but does not care // about test type. This script keeps the a11y class of tests from // silently dropping to zero. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { fileURLToPath } from "node:url"; export const repoRoot = path.resolve(fileURLToPath(import.meta.url), "..", ".."); export const componentSearchRoot = path.join(repoRoot, "apps/web/app"); export const e2eSearchRoot = path.join(repoRoot, "tests/e2e"); export const MIN_COMPONENT_A11Y_TESTS = 12; export const MIN_E2E_A11Y_SPECS = 7; export const REQUIRED_E2E_FLOW_ASSERTIONS = { "registration-account-dashboard": { file: "tests/e2e/registration-account-dashboard.mobile-a11y.spec.ts", axe: true, mobileOverflow: true, requiredSnippets: [ "E2E_ALLOW_REGISTRATION_CREATE", "E2E_ALLOW_ACCOUNT_CREATE", "E2E_ALLOW_NO_TENANT_ACCOUNT_CREATE", "guards seeded tenant account membership before dashboard and account smoke", "replays new-account login redirects and returns to the dashboard on mobile", "can create a tenant account and reach its dashboard when explicitly enabled", "can onboard a registered user with no tenant through account creation when explicitly enabled", "can register a new user and stop at the email verification state when explicitly enabled", "E2E_REGISTRATION_EXPECTS_EMAIL_VERIFICATION", "zeroTenantAccountStatePattern", "/login?redirectTo=", '[data-ui-id="ui.account.create.form"]', "failOnPaymentProviderNetworkRequests(", "tenantSettingsSubscriptionPath(", '[data-ui-id^="ui.billing.selfServiceSubscription."]', '[data-ui-id="ui.subscription.billing.portal"]', ], }, "plugin-settings": { file: "tests/e2e/plugin-settings.mobile-a11y.spec.ts", axe: true, mobileOverflow: true, requiredSnippets: [ "lifecycle controls submit through a non-mutating interception contract", "lifecycle errors stay announced and keep controls retryable", "lifecycle controls persist enable and disable state for the seeded lifecycle plugin", "app.plugins.use users cannot submit forged lifecycle mutations without manage permission", "settings iframe exposes sandbox and CSP contract when available", "settings iframe persists values across reload when write slice is enabled", // AK-UI-05: lifecycle and revoke are ~/ui controls, not hidden-input