|
| 1 | +# Generating Accessibility Reports |
| 2 | + |
| 3 | +Build a CLI tool that analyzes web page accessibility using the Playwright MCP server and generates detailed WCAG-compliant reports with optional test generation. |
| 4 | + |
| 5 | +> **Runnable example:** [recipe/AccessibilityReport.java](recipe/AccessibilityReport.java) |
| 6 | +> |
| 7 | +> ```bash |
| 8 | +> jbang recipe/AccessibilityReport.java |
| 9 | +> ``` |
| 10 | +
|
| 11 | +## Example scenario |
| 12 | +
|
| 13 | +You want to audit a website's accessibility compliance. This tool navigates to a URL using Playwright, captures an accessibility snapshot, and produces a structured report covering WCAG criteria like landmarks, heading hierarchy, focus management, and touch targets. It can also generate Playwright test files to automate future accessibility checks. |
| 14 | +
|
| 15 | +## Prerequisites |
| 16 | +
|
| 17 | +Install [JBang](https://www.jbang.dev/) and ensure `npx` is available (Node.js installed) for the Playwright MCP server: |
| 18 | +
|
| 19 | +```bash |
| 20 | +# macOS (using Homebrew) |
| 21 | +brew install jbangdev/tap/jbang |
| 22 | +
|
| 23 | +# Verify npx is available (needed for Playwright MCP) |
| 24 | +npx --version |
| 25 | +``` |
| 26 | +
|
| 27 | +## Usage |
| 28 | +
|
| 29 | +```bash |
| 30 | +jbang recipe/AccessibilityReport.java |
| 31 | +# Enter a URL when prompted |
| 32 | +``` |
| 33 | +
|
| 34 | +## Full example: AccessibilityReport.java |
| 35 | +
|
| 36 | +```java |
| 37 | +///usr/bin/env jbang "$0" "$@" ; exit $? |
| 38 | +//DEPS com.github:copilot-sdk-java:0.2.1-java.1 |
| 39 | +
|
| 40 | +import com.github.copilot.sdk.*; |
| 41 | +import com.github.copilot.sdk.events.*; |
| 42 | +import com.github.copilot.sdk.json.*; |
| 43 | +import java.io.*; |
| 44 | +import java.util.*; |
| 45 | +import java.util.concurrent.*; |
| 46 | +
|
| 47 | +public class AccessibilityReport { |
| 48 | + public static void main(String[] args) throws Exception { |
| 49 | + System.out.println("=== Accessibility Report Generator ===\n"); |
| 50 | +
|
| 51 | + var reader = new BufferedReader(new InputStreamReader(System.in)); |
| 52 | +
|
| 53 | + System.out.print("Enter URL to analyze: "); |
| 54 | + String url = reader.readLine().trim(); |
| 55 | + if (url.isEmpty()) { |
| 56 | + System.out.println("No URL provided. Exiting."); |
| 57 | + return; |
| 58 | + } |
| 59 | + if (!url.startsWith("http://") && !url.startsWith("https://")) { |
| 60 | + url = "https://" + url; |
| 61 | + } |
| 62 | +
|
| 63 | + System.out.printf("%nAnalyzing: %s%n", url); |
| 64 | + System.out.println("Please wait...\n"); |
| 65 | +
|
| 66 | + try (var client = new CopilotClient()) { |
| 67 | + client.start().get(); |
| 68 | +
|
| 69 | + // Configure Playwright MCP server for browser automation |
| 70 | + var mcpConfig = new McpServerConfig() |
| 71 | + .setType("local") |
| 72 | + .setCommand("npx") |
| 73 | + .setArgs(List.of("@playwright/mcp@latest")) |
| 74 | + .setTools(List.of("*")); |
| 75 | +
|
| 76 | + var session = client.createSession( |
| 77 | + new SessionConfig() |
| 78 | + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) |
| 79 | + .setModel("claude-opus-4.6") |
| 80 | + .setStreaming(true) |
| 81 | + .setMcpServers(Map.of("playwright", mcpConfig)) |
| 82 | + ).get(); |
| 83 | +
|
| 84 | + // Stream output token-by-token |
| 85 | + var idleLatch = new CountDownLatch(1); |
| 86 | +
|
| 87 | + session.on(AssistantMessageDeltaEvent.class, |
| 88 | + ev -> System.out.print(ev.getData().deltaContent())); |
| 89 | +
|
| 90 | + session.on(SessionIdleEvent.class, |
| 91 | + ev -> idleLatch.countDown()); |
| 92 | +
|
| 93 | + session.on(SessionErrorEvent.class, ev -> { |
| 94 | + System.err.printf("%nError: %s%n", ev.getData().message()); |
| 95 | + idleLatch.countDown(); |
| 96 | + }); |
| 97 | +
|
| 98 | + String prompt = """ |
| 99 | + Use the Playwright MCP server to analyze the accessibility of this webpage: %s |
| 100 | +
|
| 101 | + Please: |
| 102 | + 1. Navigate to the URL using playwright-browser_navigate |
| 103 | + 2. Take an accessibility snapshot using playwright-browser_snapshot |
| 104 | + 3. Analyze the snapshot and provide a detailed accessibility report |
| 105 | +
|
| 106 | + Format the report with emoji indicators: |
| 107 | + - 📊 Accessibility Report header |
| 108 | + - ✅ What's Working Well (table with Category, Status, Details) |
| 109 | + - ⚠️ Issues Found (table with Severity, Issue, WCAG Criterion, Recommendation) |
| 110 | + - 📋 Stats Summary (links, headings, focusable elements, landmarks) |
| 111 | + - ⚙️ Priority Recommendations |
| 112 | +
|
| 113 | + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. |
| 114 | + Include actual findings from the page analysis. |
| 115 | + """.formatted(url); |
| 116 | +
|
| 117 | + session.send(new MessageOptions().setPrompt(prompt)); |
| 118 | + idleLatch.await(); |
| 119 | +
|
| 120 | + System.out.println("\n\n=== Report Complete ===\n"); |
| 121 | +
|
| 122 | + // Prompt user for test generation |
| 123 | + System.out.print("Would you like to generate Playwright accessibility tests? (y/n): "); |
| 124 | + String generateTests = reader.readLine().trim(); |
| 125 | +
|
| 126 | + if (generateTests.equalsIgnoreCase("y") || generateTests.equalsIgnoreCase("yes")) { |
| 127 | + var testLatch = new CountDownLatch(1); |
| 128 | +
|
| 129 | + session.on(SessionIdleEvent.class, |
| 130 | + ev -> testLatch.countDown()); |
| 131 | +
|
| 132 | + String testPrompt = """ |
| 133 | + Based on the accessibility report you just generated for %s, |
| 134 | + create Playwright accessibility tests in Java. |
| 135 | +
|
| 136 | + Include tests for: lang attribute, title, heading hierarchy, alt text, |
| 137 | + landmarks, skip navigation, focus indicators, and touch targets. |
| 138 | + Use Playwright's accessibility testing features with helpful comments. |
| 139 | + Output the complete test file. |
| 140 | + """.formatted(url); |
| 141 | +
|
| 142 | + System.out.println("\nGenerating accessibility tests...\n"); |
| 143 | + session.send(new MessageOptions().setPrompt(testPrompt)); |
| 144 | + testLatch.await(); |
| 145 | +
|
| 146 | + System.out.println("\n\n=== Tests Generated ==="); |
| 147 | + } |
| 148 | +
|
| 149 | + session.close(); |
| 150 | + } |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
| 154 | +
|
| 155 | +## How it works |
| 156 | +
|
| 157 | +1. **Playwright MCP server**: Configures a local MCP server running `@playwright/mcp` to provide browser automation tools |
| 158 | +2. **Streaming output**: Uses `streaming: true` and `AssistantMessageDeltaEvent` for real-time token-by-token output |
| 159 | +3. **Accessibility snapshot**: Playwright's `browser_snapshot` tool captures the full accessibility tree of the page |
| 160 | +4. **Structured report**: The prompt engineers a consistent WCAG-aligned report format with emoji severity indicators |
| 161 | +5. **Test generation**: Optionally generates Playwright accessibility tests based on the analysis |
| 162 | +
|
| 163 | +## Key concepts |
| 164 | +
|
| 165 | +### MCP server configuration |
| 166 | +
|
| 167 | +The recipe configures a local MCP server that runs alongside the session: |
| 168 | +
|
| 169 | +```java |
| 170 | +var mcpConfig = new McpServerConfig() |
| 171 | + .setType("local") |
| 172 | + .setCommand("npx") |
| 173 | + .setArgs(List.of("@playwright/mcp@latest")) |
| 174 | + .setTools(List.of("*")); |
| 175 | +
|
| 176 | +var session = client.createSession( |
| 177 | + new SessionConfig() |
| 178 | + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) |
| 179 | + .setMcpServers(Map.of("playwright", mcpConfig)) |
| 180 | +).get(); |
| 181 | +``` |
| 182 | +
|
| 183 | +This gives the model access to Playwright browser tools like `browser_navigate`, `browser_snapshot`, and `browser_click`. |
| 184 | + |
| 185 | +### Streaming with events |
| 186 | + |
| 187 | +Unlike `sendAndWait`, this recipe uses streaming for real-time output: |
| 188 | + |
| 189 | +```java |
| 190 | +session.on(AssistantMessageDeltaEvent.class, |
| 191 | + ev -> System.out.print(ev.getData().deltaContent())); |
| 192 | + |
| 193 | +session.on(SessionIdleEvent.class, |
| 194 | + ev -> idleLatch.countDown()); |
| 195 | +``` |
| 196 | + |
| 197 | +A `CountDownLatch` synchronizes the main thread with the async event stream — when the session becomes idle, the latch releases and the program continues. |
| 198 | + |
| 199 | +## Sample interaction |
| 200 | + |
| 201 | +``` |
| 202 | +=== Accessibility Report Generator === |
| 203 | +
|
| 204 | +Enter URL to analyze: github.com |
| 205 | +
|
| 206 | +Analyzing: https://github.com |
| 207 | +Please wait... |
| 208 | +
|
| 209 | +📊 Accessibility Report: GitHub (github.com) |
| 210 | +
|
| 211 | +✅ What's Working Well |
| 212 | +| Category | Status | Details | |
| 213 | +|----------|--------|---------| |
| 214 | +| Language | ✅ Pass | lang="en" properly set | |
| 215 | +| Page Title | ✅ Pass | "GitHub" is recognizable | |
| 216 | +| Heading Hierarchy | ✅ Pass | Proper H1/H2 structure | |
| 217 | +| Images | ✅ Pass | All images have alt text | |
| 218 | +
|
| 219 | +⚠️ Issues Found |
| 220 | +| Severity | Issue | WCAG Criterion | Recommendation | |
| 221 | +|----------|-------|----------------|----------------| |
| 222 | +| 🟡 Medium | Some links lack descriptive text | 2.4.4 | Add aria-label to icon-only links | |
| 223 | +
|
| 224 | +📋 Stats Summary |
| 225 | +- Total Links: 47 |
| 226 | +- Total Headings: 8 (1× H1, proper hierarchy) |
| 227 | +- Focusable Elements: 52 |
| 228 | +- Landmarks Found: banner ✅, navigation ✅, main ✅, footer ✅ |
| 229 | +
|
| 230 | +=== Report Complete === |
| 231 | +
|
| 232 | +Would you like to generate Playwright accessibility tests? (y/n): y |
| 233 | +
|
| 234 | +Generating accessibility tests... |
| 235 | +[Generated test file output...] |
| 236 | +
|
| 237 | +=== Tests Generated === |
| 238 | +``` |
0 commit comments