How to Build Accessible Gates
Contributed by @grok
Goal: ensure gates and their outputs support accessibility (a11y) best practices.
Why accessibility matters for gates
Gates that drive browser actions interact with real UIs. If your gate triggers a signup flow, the UI it tests should be accessible. Gates can verify accessibility, not just functionality.
Adding alt text in browser gate examples
When gates use Act.browser() to test pages with images, ensure the pages under test include alt text. You can assert this with a custom gate:
import { Gate, Act, Assert, createHttpObserveResource } from "gateproof";
export async function run() {
return Gate.run({
name: "images-have-alt-text",
observe: createHttpObserveResource({ url: "https://app.example.com/logs" }),
act: [
Act.browser({
url: "https://app.example.com",
headless: true,
waitMs: 3000,
}),
],
assert: [
Assert.custom("all_images_have_alt", async () => {
// Use playwright to check all images have alt text
const { chromium } = await import("playwright");
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://app.example.com");
const images = await page.locator("img").all();
const results = await Promise.all(
images.map(async (img) => {
const alt = await img.getAttribute("alt");
return alt !== null && alt.trim().length > 0;
})
);
await browser.close();
return results.every(Boolean);
}),
Assert.noErrors(),
],
stop: { idleMs: 3000, maxMs: 30000 },
});
}ARIA attributes for report UIs
If you build a UI to display gate results (e.g., a dashboard), use ARIA attributes for screen reader support:
pass
- Action: user_created
- Stage: signup_complete
Gate "user-signup" failed: HasAction: missing 'user_created'
Accessibility gates for common checks
Color contrast gate
Assert.custom("color_contrast_ok", async () => {
// Check WCAG AA contrast ratios on key elements
const { chromium } = await import("playwright");
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://app.example.com");
// Use axe-core for automated a11y testing
await page.addScriptTag({ url: "https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.7.0/axe.min.js" });
const results = await page.evaluate(() => (window as any).axe.run());
await browser.close();
return results.violations.length === 0;
})Keyboard navigation gate
Assert.custom("keyboard_navigable", async () => {
const { chromium } = await import("playwright");
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://app.example.com");
// Tab through interactive elements and verify focus is visible
await page.keyboard.press("Tab");
const focused = await page.evaluate(() => document.activeElement?.tagName);
await browser.close();
return focused !== "BODY"; // Something received focus
})Tips
- Use
axe-corevia Playwright for automated WCAG checks in gates - Assert alt text, ARIA labels, and keyboard navigation as positive evidence
- Gate results are JSON-serializable by default, making them screen-reader-pipeline friendly
- When building report UIs, follow WCAG 2.1 AA as a baseline