diff --git a/cookbook/cookbook.yml b/cookbook/cookbook.yml index d80454b5..43f66f3f 100644 --- a/cookbook/cookbook.yml +++ b/cookbook/cookbook.yml @@ -61,3 +61,11 @@ cookbooks: - sessions - persistence - state-management + - id: accessibility-report + name: Accessibility Report + description: Generate WCAG accessibility reports using the Playwright MCP server + tags: + - accessibility + - playwright + - mcp + - wcag diff --git a/cookbook/copilot-sdk/README.md b/cookbook/copilot-sdk/README.md index 55981302..3e2738d1 100644 --- a/cookbook/copilot-sdk/README.md +++ b/cookbook/copilot-sdk/README.md @@ -12,6 +12,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t - [Managing Local Files](dotnet/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. - [PR Visualization](dotnet/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. - [Persisting Sessions](dotnet/persisting-sessions.md): Save and resume sessions across restarts. +- [Accessibility Report](dotnet/accessibility-report.md): Generate WCAG accessibility reports using the Playwright MCP server. ### Node.js / TypeScript @@ -21,6 +22,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t - [Managing Local Files](nodejs/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. - [PR Visualization](nodejs/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. - [Persisting Sessions](nodejs/persisting-sessions.md): Save and resume sessions across restarts. +- [Accessibility Report](nodejs/accessibility-report.md): Generate WCAG accessibility reports using the Playwright MCP server. ### Python @@ -30,6 +32,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t - [Managing Local Files](python/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. - [PR Visualization](python/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. - [Persisting Sessions](python/persisting-sessions.md): Save and resume sessions across restarts. +- [Accessibility Report](python/accessibility-report.md): Generate WCAG accessibility reports using the Playwright MCP server. ### Go @@ -39,6 +42,7 @@ This cookbook collects small, focused recipes showing how to accomplish common t - [Managing Local Files](go/managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. - [PR Visualization](go/pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. - [Persisting Sessions](go/persisting-sessions.md): Save and resume sessions across restarts. +- [Accessibility Report](go/accessibility-report.md): Generate WCAG accessibility reports using the Playwright MCP server. ## How to Use @@ -87,4 +91,4 @@ go run .go ## Status -Cookbook structure is complete with 6 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. +Cookbook structure is complete with 7 recipes across all 4 supported languages. Each recipe includes both markdown documentation and runnable examples. diff --git a/cookbook/copilot-sdk/dotnet/accessibility-report.md b/cookbook/copilot-sdk/dotnet/accessibility-report.md new file mode 100644 index 00000000..6c2f1d8d --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/accessibility-report.md @@ -0,0 +1,287 @@ +# Generating Accessibility Reports + +Build a CLI tool that analyzes web page accessibility using the Playwright MCP server and generates detailed WCAG-compliant reports with optional test generation. + +> **Runnable example:** [recipe/accessibility-report.cs](recipe/accessibility-report.cs) +> +> ```bash +> dotnet run recipe/accessibility-report.cs +> ``` + +## Example scenario + +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. + +## Prerequisites + +```bash +dotnet add package GitHub.Copilot.SDK +``` + +You also need `npx` available (Node.js installed) for the Playwright MCP server. + +## Usage + +```bash +dotnet run recipe/accessibility-report.cs +# Enter a URL when prompted +``` + +## Full example: accessibility-report.cs + +```csharp +#:package GitHub.Copilot.SDK@* + +using GitHub.Copilot.SDK; + +// Create and start client +await using var client = new CopilotClient(); +await client.StartAsync(); + +Console.WriteLine("=== Accessibility Report Generator ==="); +Console.WriteLine(); + +Console.Write("Enter URL to analyze: "); +var url = Console.ReadLine()?.Trim(); + +if (string.IsNullOrWhiteSpace(url)) +{ + Console.WriteLine("No URL provided. Exiting."); + return; +} + +// Ensure URL has a scheme +if (!url.StartsWith("http://") && !url.StartsWith("https://")) +{ + url = "https://" + url; +} + +Console.WriteLine($"\nAnalyzing: {url}"); +Console.WriteLine("Please wait...\n"); + +// Create a session with Playwright MCP server +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "claude-opus-4.6", + Streaming = true, + McpServers = new Dictionary() + { + ["playwright"] = + new McpLocalServerConfig + { + Type = "local", + Command = "npx", + Args = ["@playwright/mcp@latest"], + Tools = ["*"] + } + }, +}); + +// Wait for response using session.idle event +var done = new TaskCompletionSource(); + +session.On(evt => +{ + switch (evt) + { + case AssistantMessageDeltaEvent delta: + Console.Write(delta.Data.DeltaContent); + break; + case SessionIdleEvent: + done.TrySetResult(); + break; + case SessionErrorEvent error: + Console.WriteLine($"\nError: {error.Data.Message}"); + done.TrySetResult(); + break; + } +}); + +var prompt = $""" + Use the Playwright MCP server to analyze the accessibility of this webpage: {url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report EXACTLY like this structure with emoji indicators: + + 📊 Accessibility Report: [Page Title] (domain.com) + + ✅ What's Working Well + | Category | Status | Details | + |----------|--------|---------| + | Language | ✅ Pass | lang="en-US" properly set | + | Page Title | ✅ Pass | "[Title]" is descriptive | + | Heading Hierarchy | ✅ Pass | Single H1, proper H2/H3 structure | + | Images | ✅ Pass | All X images have alt text | + + ⚠️ Issues Found + | Severity | Issue | WCAG Criterion | Recommendation | + |----------|-------|----------------|----------------| + | 🔴 High | No
landmark | 1.3.1, 2.4.1 | Wrap main content in
element | + | 🟡 Medium | Focus outlines disabled | 2.4.7 | Ensure visible :focus styles exist | + + 📋 Stats Summary + - Total Links: X + - Total Headings: X + - Focusable Elements: X + - Landmarks Found: banner ✅, navigation ✅, main ❌, footer ✅ + + ⚙️ Priority Recommendations + ... + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis - don't just copy the example. + """; + +await session.SendAsync(new MessageOptions { Prompt = prompt }); +await done.Task; + +Console.WriteLine("\n\n=== Report Complete ===\n"); + +// Prompt user for test generation +Console.Write("Would you like to generate Playwright accessibility tests? (y/n): "); +var generateTests = Console.ReadLine()?.Trim().ToLowerInvariant(); + +if (generateTests == "y" || generateTests == "yes") +{ + // Reset for next interaction + done = new TaskCompletionSource(); + + var detectLanguagePrompt = $""" + Analyze the current working directory to detect the primary programming language used in this project. + Respond with ONLY the detected language name and a brief explanation. + If no project is detected, suggest "TypeScript" as the default for Playwright tests. + """; + + Console.WriteLine("\nDetecting project language...\n"); + await session.SendAsync(new MessageOptions { Prompt = detectLanguagePrompt }); + await done.Task; + + Console.Write("\n\nConfirm language for tests (or enter a different one): "); + var language = Console.ReadLine()?.Trim(); + + if (string.IsNullOrWhiteSpace(language)) + { + language = "TypeScript"; + } + + // Reset for test generation + done = new TaskCompletionSource(); + + var testGenerationPrompt = $""" + Based on the accessibility report you just generated for {url}, create Playwright accessibility tests in {language}. + + The tests should: + 1. Verify all the accessibility checks from the report + 2. Test for the issues that were found (to ensure they get fixed) + 3. Include tests for landmarks, heading hierarchy, alt text, focus indicators, and more + 4. Use Playwright's accessibility testing features + 5. Include helpful comments explaining each test + + Output the complete test file that can be saved and run. + """; + + Console.WriteLine("\nGenerating accessibility tests...\n"); + await session.SendAsync(new MessageOptions { Prompt = testGenerationPrompt }); + await done.Task; + + Console.WriteLine("\n\n=== Tests Generated ==="); +} +``` + +## How it works + +1. **Playwright MCP server**: Configures a local MCP server running `@playwright/mcp` to provide browser automation tools +2. **Streaming output**: Uses `Streaming = true` and `AssistantMessageDeltaEvent` for real-time token-by-token output +3. **Accessibility snapshot**: Playwright's `browser_snapshot` tool captures the full accessibility tree of the page +4. **Structured report**: The prompt engineers a consistent WCAG-aligned report format with emoji severity indicators +5. **Test generation**: Optionally detects the project language and generates Playwright accessibility tests + +## Key concepts + +### MCP server configuration + +The recipe configures a local MCP server that runs alongside the session: + +```csharp +McpServers = new Dictionary() +{ + ["playwright"] = new McpLocalServerConfig + { + Type = "local", + Command = "npx", + Args = ["@playwright/mcp@latest"], + Tools = ["*"] + } +} +``` + +This gives the model access to Playwright browser tools like `browser_navigate`, `browser_snapshot`, and `browser_click`. + +### Streaming with events + +Unlike `SendAndWaitAsync`, this recipe uses streaming for real-time output: + +```csharp +session.On(evt => +{ + switch (evt) + { + case AssistantMessageDeltaEvent delta: + Console.Write(delta.Data.DeltaContent); // Token-by-token + break; + case SessionIdleEvent: + done.TrySetResult(); // Model finished + break; + } +}); +``` + +## Sample interaction + +``` +=== Accessibility Report Generator === + +Enter URL to analyze: github.com + +Analyzing: https://github.com +Please wait... + +📊 Accessibility Report: GitHub (github.com) + +✅ What's Working Well +| Category | Status | Details | +|----------|--------|---------| +| Language | ✅ Pass | lang="en" properly set | +| Page Title | ✅ Pass | "GitHub" is recognizable | +| Heading Hierarchy | ✅ Pass | Proper H1/H2 structure | +| Images | ✅ Pass | All images have alt text | + +⚠️ Issues Found +| Severity | Issue | WCAG Criterion | Recommendation | +|----------|-------|----------------|----------------| +| 🟡 Medium | Some links lack descriptive text | 2.4.4 | Add aria-label to icon-only links | + +📋 Stats Summary +- Total Links: 47 +- Total Headings: 8 (1× H1, proper hierarchy) +- Focusable Elements: 52 +- Landmarks Found: banner ✅, navigation ✅, main ✅, footer ✅ + +=== Report Complete === + +Would you like to generate Playwright accessibility tests? (y/n): y + +Detecting project language... +TypeScript detected (package.json found) + +Confirm language for tests (or enter a different one): + +Generating accessibility tests... +[Generated test file output...] + +=== Tests Generated === +``` diff --git a/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs b/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs new file mode 100644 index 00000000..3fe4e387 --- /dev/null +++ b/cookbook/copilot-sdk/dotnet/recipe/accessibility-report.cs @@ -0,0 +1,184 @@ +#:package GitHub.Copilot.SDK@* + +using GitHub.Copilot.SDK; + +// Create and start client +await using var client = new CopilotClient(); +await client.StartAsync(); + +Console.WriteLine("=== Accessibility Report Generator ==="); +Console.WriteLine(); + +Console.Write("Enter URL to analyze: "); +var url = Console.ReadLine()?.Trim(); + +if (string.IsNullOrWhiteSpace(url)) +{ + Console.WriteLine("No URL provided. Exiting."); + return; +} + +// Ensure URL has a scheme +if (!url.StartsWith("http://") && !url.StartsWith("https://")) +{ + url = "https://" + url; +} + +Console.WriteLine($"\nAnalyzing: {url}"); +Console.WriteLine("Please wait...\n"); + +// Create a session with Playwright MCP server +await using var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "claude-opus-4.6", + Streaming = true, + McpServers = new Dictionary() + { + ["playwright"] = + new McpLocalServerConfig + { + Type = "local", + Command = "npx", + Args = ["@playwright/mcp@latest"], + Tools = ["*"] + } + }, +}); + +// Wait for response using session.idle event +var done = new TaskCompletionSource(); + +session.On(evt => +{ + switch (evt) + { + case AssistantMessageDeltaEvent delta: + Console.Write(delta.Data.DeltaContent); + break; + case SessionIdleEvent: + done.TrySetResult(); + break; + case SessionErrorEvent error: + Console.WriteLine($"\nError: {error.Data.Message}"); + done.TrySetResult(); + break; + } +}); + +var prompt = $""" + Use the Playwright MCP server to analyze the accessibility of this webpage: {url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report EXACTLY like this structure with emoji indicators: + + 📊 Accessibility Report: [Page Title] (domain.com) + + ✅ What's Working Well + | Category | Status | Details | + |----------|--------|---------| + | Language | ✅ Pass | lang="en-US" properly set | + | Page Title | ✅ Pass | "[Title]" is descriptive | + | Heading Hierarchy | ✅ Pass | Single H1, proper H2/H3 structure | + | Images | ✅ Pass | All X images have alt text | + | Viewport | ✅ Pass | Allows pinch-zoom (no user-scalable=no) | + | Links | ✅ Pass | No ambiguous "click here" links | + | Reduced Motion | ✅ Pass | Supports prefers-reduced-motion | + | Autoplay Media | ✅ Pass | No autoplay audio/video | + | Font Selector | ✅ Excellent | Includes OpenDyslexic option for dyslexia | + | Dark/Light Mode | ✅ Excellent | User-controlled theme toggle | + + ⚠️ Issues Found + | Severity | Issue | WCAG Criterion | Recommendation | + |----------|-------|----------------|----------------| + | 🔴 High | No
landmark | 1.3.1, 2.4.1 | Wrap main content in
element | + | 🔴 High | No skip navigation link | 2.4.1 | Add "Skip to content" link at top | + | 🟡 Medium | Focus outlines disabled | 2.4.7 | Default outline is none - ensure visible :focus styles exist | + | 🟡 Medium | Small touch targets | 2.5.8 | Navigation links are 37px tall (below 44px minimum) | + + 📋 Stats Summary + - Total Links: X + - Total Headings: X (1× H1, proper hierarchy) + - Focusable Elements: X + - Landmarks Found: banner ✅, navigation ✅, main ❌, footer ✅ + + ⚙️ Priority Recommendations + - Add
landmark - Wrap page content in
for screen reader navigation + - Add skip link - Hidden link at start: + - Increase touch targets - Add padding to nav links and tags to meet 44×44px minimum + - Verify focus styles - Test keyboard navigation; add visible :focus or :focus-visible outlines + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis - don't just copy the example. + """; + +await session.SendAsync(new MessageOptions { Prompt = prompt }); +await done.Task; + +Console.WriteLine("\n\n=== Report Complete ===\n"); + +// Prompt user for test generation +Console.Write("Would you like to generate Playwright accessibility tests? (y/n): "); +var generateTests = Console.ReadLine()?.Trim().ToLowerInvariant(); + +if (generateTests == "y" || generateTests == "yes") +{ + // Reset for next interaction + done = new TaskCompletionSource(); + + var detectLanguagePrompt = $""" + Analyze the current working directory to detect the primary programming language used in this project. + Look for project files like package.json, *.csproj, pom.xml, requirements.txt, go.mod, etc. + + Respond with ONLY the detected language name (e.g., "TypeScript", "JavaScript", "C#", "Python", "Java") + and a brief explanation of why you detected it. + If no project is detected, suggest "TypeScript" as the default for Playwright tests. + """; + + Console.WriteLine("\nDetecting project language...\n"); + await session.SendAsync(new MessageOptions { Prompt = detectLanguagePrompt }); + await done.Task; + + Console.Write("\n\nConfirm language for tests (or enter a different one): "); + var language = Console.ReadLine()?.Trim(); + + if (string.IsNullOrWhiteSpace(language)) + { + language = "TypeScript"; + } + + // Reset for test generation + done = new TaskCompletionSource(); + + var testGenerationPrompt = $""" + Based on the accessibility report you just generated for {url}, create Playwright accessibility tests in {language}. + + The tests should: + 1. Verify all the accessibility checks from the report + 2. Test for the issues that were found (to ensure they get fixed) + 3. Include tests for: + - Page has proper lang attribute + - Page has descriptive title + - Heading hierarchy is correct (single H1, proper nesting) + - All images have alt text + - No autoplay media + - Landmark regions exist (banner, nav, main, footer) + - Skip navigation link exists and works + - Focus indicators are visible + - Touch targets meet minimum size requirements + 4. Use Playwright's accessibility testing features + 5. Include helpful comments explaining each test + + Output the complete test file that can be saved and run. + Use the Playwright MCP server tools if you need to verify any page details. + """; + + Console.WriteLine("\nGenerating accessibility tests...\n"); + await session.SendAsync(new MessageOptions { Prompt = testGenerationPrompt }); + await done.Task; + + Console.WriteLine("\n\n=== Tests Generated ==="); +} diff --git a/cookbook/copilot-sdk/go/accessibility-report.md b/cookbook/copilot-sdk/go/accessibility-report.md new file mode 100644 index 00000000..afe7ea27 --- /dev/null +++ b/cookbook/copilot-sdk/go/accessibility-report.md @@ -0,0 +1,291 @@ +# Generating Accessibility Reports + +Build a CLI tool that analyzes web page accessibility using the Playwright MCP server and generates detailed WCAG-compliant reports with optional test generation. + +> **Runnable example:** [recipe/accessibility-report.go](recipe/accessibility-report.go) +> +> ```bash +> go run recipe/accessibility-report.go +> ``` + +## Example scenario + +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. + +## Prerequisites + +```bash +go get github.com/github/copilot-sdk/go +``` + +You also need `npx` available (Node.js installed) for the Playwright MCP server. + +## Usage + +```bash +go run accessibility-report.go +# Enter a URL when prompted +``` + +## Full example: accessibility-report.go + +```go +package main + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + ctx := context.Background() + reader := bufio.NewReader(os.Stdin) + + fmt.Println("=== Accessibility Report Generator ===") + fmt.Println() + + fmt.Print("Enter URL to analyze: ") + url, _ := reader.ReadString('\n') + url = strings.TrimSpace(url) + + if url == "" { + fmt.Println("No URL provided. Exiting.") + return + } + + // Ensure URL has a scheme + if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { + url = "https://" + url + } + + fmt.Printf("\nAnalyzing: %s\n", url) + fmt.Println("Please wait...\n") + + // Create Copilot client with Playwright MCP server + client := copilot.NewClient(nil) + + if err := client.Start(ctx); err != nil { + log.Fatal(err) + } + defer client.Stop() + + streaming := true + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "claude-opus-4.6", + Streaming: &streaming, + McpServers: map[string]interface{}{ + "playwright": map[string]interface{}{ + "type": "local", + "command": "npx", + "args": []string{"@playwright/mcp@latest"}, + "tools": []string{"*"}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + defer session.Destroy() + + // Set up streaming event handling + done := make(chan struct{}, 1) + + session.On(func(event copilot.SessionEvent) { + switch event.Type { + case "assistant.message.delta": + if event.Data.DeltaContent != nil { + fmt.Print(*event.Data.DeltaContent) + } + case "session.idle": + select { + case done <- struct{}{}: + default: + } + case "session.error": + if event.Data.Message != nil { + fmt.Printf("\nError: %s\n", *event.Data.Message) + } + select { + case done <- struct{}{}: + default: + } + } + }) + + prompt := fmt.Sprintf(` + Use the Playwright MCP server to analyze the accessibility of this webpage: %s + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report with emoji indicators: + - 📊 Accessibility Report header + - ✅ What's Working Well (table with Category, Status, Details) + - ⚠️ Issues Found (table with Severity, Issue, WCAG Criterion, Recommendation) + - 📋 Stats Summary (links, headings, focusable elements, landmarks) + - ⚙️ Priority Recommendations + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis. + `, url) + + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: prompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Println("\n\n=== Report Complete ===\n") + + // Prompt user for test generation + fmt.Print("Would you like to generate Playwright accessibility tests? (y/n): ") + generateTests, _ := reader.ReadString('\n') + generateTests = strings.TrimSpace(strings.ToLower(generateTests)) + + if generateTests == "y" || generateTests == "yes" { + detectLanguagePrompt := ` + Analyze the current working directory to detect the primary programming language. + Respond with ONLY the detected language name and a brief explanation. + If no project is detected, suggest "TypeScript" as the default. + ` + + fmt.Println("\nDetecting project language...\n") + select { + case <-done: + default: + } + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: detectLanguagePrompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Print("\n\nConfirm language for tests (or enter a different one): ") + language, _ := reader.ReadString('\n') + language = strings.TrimSpace(language) + if language == "" { + language = "TypeScript" + } + + testGenerationPrompt := fmt.Sprintf(` + Based on the accessibility report you just generated for %s, + create Playwright accessibility tests in %s. + + Include tests for: lang attribute, title, heading hierarchy, alt text, + landmarks, skip navigation, focus indicators, and touch targets. + Use Playwright's accessibility testing features with helpful comments. + Output the complete test file. + `, url, language) + + fmt.Println("\nGenerating accessibility tests...\n") + select { + case <-done: + default: + } + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: testGenerationPrompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Println("\n\n=== Tests Generated ===") + } +} +``` + +## How it works + +1. **Playwright MCP server**: Configures a local MCP server running `@playwright/mcp` to provide browser automation tools +2. **Streaming output**: Uses `Streaming: &streaming` and `assistant.message.delta` events for real-time token-by-token output +3. **Accessibility snapshot**: Playwright's `browser_snapshot` tool captures the full accessibility tree of the page +4. **Structured report**: The prompt engineers a consistent WCAG-aligned report format with emoji severity indicators +5. **Test generation**: Optionally detects the project language and generates Playwright accessibility tests + +## Key concepts + +### MCP server configuration + +The recipe configures a local MCP server that runs alongside the session: + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + McpServers: map[string]interface{}{ + "playwright": map[string]interface{}{ + "type": "local", + "command": "npx", + "args": []string{"@playwright/mcp@latest"}, + "tools": []string{"*"}, + }, + }, +}) +``` + +This gives the model access to Playwright browser tools like `browser_navigate`, `browser_snapshot`, and `browser_click`. + +### Streaming with events + +Unlike `SendAndWait`, this recipe uses streaming for real-time output: + +```go +session.On(func(event copilot.SessionEvent) { + switch event.Type { + case "assistant.message.delta": + if event.Data.DeltaContent != nil { + fmt.Print(*event.Data.DeltaContent) + } + case "session.idle": + done <- struct{}{} + } +}) +``` + +## Sample interaction + +``` +=== Accessibility Report Generator === + +Enter URL to analyze: github.com + +Analyzing: https://github.com +Please wait... + +📊 Accessibility Report: GitHub (github.com) + +✅ What's Working Well +| Category | Status | Details | +|----------|--------|---------| +| Language | ✅ Pass | lang="en" properly set | +| Page Title | ✅ Pass | "GitHub" is recognizable | +| Heading Hierarchy | ✅ Pass | Proper H1/H2 structure | +| Images | ✅ Pass | All images have alt text | + +⚠️ Issues Found +| Severity | Issue | WCAG Criterion | Recommendation | +|----------|-------|----------------|----------------| +| 🟡 Medium | Some links lack descriptive text | 2.4.4 | Add aria-label to icon-only links | + +📋 Stats Summary +- Total Links: 47 +- Total Headings: 8 (1× H1, proper hierarchy) +- Focusable Elements: 52 +- Landmarks Found: banner ✅, navigation ✅, main ✅, footer ✅ + +=== Report Complete === + +Would you like to generate Playwright accessibility tests? (y/n): y + +Detecting project language... +TypeScript detected (package.json found) + +Confirm language for tests (or enter a different one): + +Generating accessibility tests... +[Generated test file output...] + +=== Tests Generated === +``` diff --git a/cookbook/copilot-sdk/go/recipe/accessibility-report.go b/cookbook/copilot-sdk/go/recipe/accessibility-report.go new file mode 100644 index 00000000..e1ae2a49 --- /dev/null +++ b/cookbook/copilot-sdk/go/recipe/accessibility-report.go @@ -0,0 +1,213 @@ +package main + +import ( + "bufio" + "context" + "fmt" + "log" + "os" + "strings" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + ctx := context.Background() + reader := bufio.NewReader(os.Stdin) + + fmt.Println("=== Accessibility Report Generator ===") + fmt.Println() + + fmt.Print("Enter URL to analyze: ") + url, _ := reader.ReadString('\n') + url = strings.TrimSpace(url) + + if url == "" { + fmt.Println("No URL provided. Exiting.") + return + } + + // Ensure URL has a scheme + if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { + url = "https://" + url + } + + fmt.Printf("\nAnalyzing: %s\n", url) + fmt.Println("Please wait...\n") + + // Create Copilot client with Playwright MCP server + client := copilot.NewClient(nil) + + if err := client.Start(ctx); err != nil { + log.Fatal(err) + } + defer client.Stop() + + streaming := true + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Model: "claude-opus-4.6", + Streaming: &streaming, + McpServers: map[string]interface{}{ + "playwright": map[string]interface{}{ + "type": "local", + "command": "npx", + "args": []string{"@playwright/mcp@latest"}, + "tools": []string{"*"}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + defer session.Destroy() + + // Set up streaming event handling + done := make(chan struct{}, 1) + + session.On(func(event copilot.SessionEvent) { + switch event.Type { + case "assistant.message.delta": + if event.Data.DeltaContent != nil { + fmt.Print(*event.Data.DeltaContent) + } + case "session.idle": + select { + case done <- struct{}{}: + default: + } + case "session.error": + if event.Data.Message != nil { + fmt.Printf("\nError: %s\n", *event.Data.Message) + } + select { + case done <- struct{}{}: + default: + } + } + }) + + prompt := fmt.Sprintf(` + Use the Playwright MCP server to analyze the accessibility of this webpage: %s + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report EXACTLY like this structure with emoji indicators: + + 📊 Accessibility Report: [Page Title] (domain.com) + + ✅ What's Working Well + | Category | Status | Details | + |----------|--------|---------| + | Language | ✅ Pass | lang="en-US" properly set | + | Page Title | ✅ Pass | "[Title]" is descriptive | + | Heading Hierarchy | ✅ Pass | Single H1, proper H2/H3 structure | + | Images | ✅ Pass | All X images have alt text | + | Viewport | ✅ Pass | Allows pinch-zoom (no user-scalable=no) | + | Links | ✅ Pass | No ambiguous "click here" links | + | Reduced Motion | ✅ Pass | Supports prefers-reduced-motion | + | Autoplay Media | ✅ Pass | No autoplay audio/video | + + ⚠️ Issues Found + | Severity | Issue | WCAG Criterion | Recommendation | + |----------|-------|----------------|----------------| + | 🔴 High | No
landmark | 1.3.1, 2.4.1 | Wrap main content in
element | + | 🔴 High | No skip navigation link | 2.4.1 | Add "Skip to content" link at top | + | 🟡 Medium | Focus outlines disabled | 2.4.7 | Default outline is none - ensure visible :focus styles exist | + | 🟡 Medium | Small touch targets | 2.5.8 | Navigation links are 37px tall (below 44px minimum) | + + 📋 Stats Summary + - Total Links: X + - Total Headings: X (1× H1, proper hierarchy) + - Focusable Elements: X + - Landmarks Found: banner ✅, navigation ✅, main ❌, footer ✅ + + ⚙️ Priority Recommendations + - Add
landmark - Wrap page content in
for screen reader navigation + - Add skip link - Hidden link at start: + - Increase touch targets - Add padding to nav links and tags to meet 44×44px minimum + - Verify focus styles - Test keyboard navigation; add visible :focus or :focus-visible outlines + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis - don't just copy the example. + `, url) + + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: prompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Println("\n\n=== Report Complete ===\n") + + // Prompt user for test generation + fmt.Print("Would you like to generate Playwright accessibility tests? (y/n): ") + generateTests, _ := reader.ReadString('\n') + generateTests = strings.TrimSpace(strings.ToLower(generateTests)) + + if generateTests == "y" || generateTests == "yes" { + detectLanguagePrompt := ` + Analyze the current working directory to detect the primary programming language used in this project. + Look for project files like package.json, *.csproj, pom.xml, requirements.txt, go.mod, etc. + + Respond with ONLY the detected language name (e.g., "TypeScript", "JavaScript", "C#", "Python", "Java") + and a brief explanation of why you detected it. + If no project is detected, suggest "TypeScript" as the default for Playwright tests. + ` + + fmt.Println("\nDetecting project language...\n") + // Drain the previous done signal + select { + case <-done: + default: + } + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: detectLanguagePrompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Print("\n\nConfirm language for tests (or enter a different one): ") + language, _ := reader.ReadString('\n') + language = strings.TrimSpace(language) + if language == "" { + language = "TypeScript" + } + + testGenerationPrompt := fmt.Sprintf(` + Based on the accessibility report you just generated for %s, create Playwright accessibility tests in %s. + + The tests should: + 1. Verify all the accessibility checks from the report + 2. Test for the issues that were found (to ensure they get fixed) + 3. Include tests for: + - Page has proper lang attribute + - Page has descriptive title + - Heading hierarchy is correct (single H1, proper nesting) + - All images have alt text + - No autoplay media + - Landmark regions exist (banner, nav, main, footer) + - Skip navigation link exists and works + - Focus indicators are visible + - Touch targets meet minimum size requirements + 4. Use Playwright's accessibility testing features + 5. Include helpful comments explaining each test + + Output the complete test file that can be saved and run. + Use the Playwright MCP server tools if you need to verify any page details. + `, url, language) + + fmt.Println("\nGenerating accessibility tests...\n") + // Drain the previous done signal + select { + case <-done: + default: + } + if _, err := session.Send(ctx, copilot.MessageOptions{Prompt: testGenerationPrompt}); err != nil { + log.Fatal(err) + } + <-done + + fmt.Println("\n\n=== Tests Generated ===") + } +} diff --git a/cookbook/copilot-sdk/nodejs/accessibility-report.md b/cookbook/copilot-sdk/nodejs/accessibility-report.md new file mode 100644 index 00000000..74cb7747 --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/accessibility-report.md @@ -0,0 +1,265 @@ +# Generating Accessibility Reports + +Build a CLI tool that analyzes web page accessibility using the Playwright MCP server and generates detailed WCAG-compliant reports with optional test generation. + +> **Runnable example:** [recipe/accessibility-report.ts](recipe/accessibility-report.ts) +> +> ```bash +> cd recipe && npm install +> npx tsx accessibility-report.ts +> # or: npm run accessibility-report +> ``` + +## Example scenario + +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. + +## Prerequisites + +```bash +npm install @github/copilot-sdk +npm install -D typescript tsx @types/node +``` + +You also need `npx` available (Node.js installed) for the Playwright MCP server. + +## Usage + +```bash +npx tsx accessibility-report.ts +# Enter a URL when prompted +``` + +## Full example: accessibility-report.ts + +```typescript +#!/usr/bin/env npx tsx + +import { CopilotClient } from "@github/copilot-sdk"; +import * as readline from "node:readline"; + +// ============================================================================ +// Main Application +// ============================================================================ + +async function main() { + console.log("=== Accessibility Report Generator ===\n"); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const askQuestion = (query: string): Promise => + new Promise((resolve) => rl.question(query, (answer) => resolve(answer.trim()))); + + let url = await askQuestion("Enter URL to analyze: "); + + if (!url) { + console.log("No URL provided. Exiting."); + rl.close(); + return; + } + + // Ensure URL has a scheme + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "https://" + url; + } + + console.log(`\nAnalyzing: ${url}`); + console.log("Please wait...\n"); + + // Create Copilot client with Playwright MCP server + const client = new CopilotClient(); + + const session = await client.createSession({ + model: "claude-opus-4.6", + streaming: true, + mcpServers: { + playwright: { + type: "local", + command: "npx", + args: ["@playwright/mcp@latest"], + tools: ["*"], + }, + }, + }); + + // Set up streaming event handling + let idleResolve: (() => void) | null = null; + + session.on((event) => { + if (event.type === "assistant.message.delta") { + process.stdout.write(event.data.deltaContent ?? ""); + } else if (event.type === "session.idle") { + idleResolve?.(); + } else if (event.type === "session.error") { + console.error(`\nError: ${event.data.message}`); + idleResolve?.(); + } + }); + + const waitForIdle = (): Promise => + new Promise((resolve) => { + idleResolve = resolve; + }); + + const prompt = ` + Use the Playwright MCP server to analyze the accessibility of this webpage: ${url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report with emoji indicators: + - 📊 Accessibility Report header + - ✅ What's Working Well (table with Category, Status, Details) + - ⚠️ Issues Found (table with Severity, Issue, WCAG Criterion, Recommendation) + - 📋 Stats Summary (links, headings, focusable elements, landmarks) + - ⚙️ Priority Recommendations + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis. + `; + + let idle = waitForIdle(); + await session.send({ prompt }); + await idle; + + console.log("\n\n=== Report Complete ===\n"); + + // Prompt user for test generation + const generateTests = await askQuestion( + "Would you like to generate Playwright accessibility tests? (y/n): " + ); + + if (generateTests.toLowerCase() === "y" || generateTests.toLowerCase() === "yes") { + const detectLanguagePrompt = ` + Analyze the current working directory to detect the primary programming language. + Respond with ONLY the detected language name and a brief explanation. + If no project is detected, suggest "TypeScript" as the default. + `; + + console.log("\nDetecting project language...\n"); + idle = waitForIdle(); + await session.send({ prompt: detectLanguagePrompt }); + await idle; + + let language = await askQuestion("\n\nConfirm language for tests (or enter a different one): "); + if (!language) language = "TypeScript"; + + const testGenerationPrompt = ` + Based on the accessibility report you just generated for ${url}, + create Playwright accessibility tests in ${language}. + + Include tests for: lang attribute, title, heading hierarchy, alt text, + landmarks, skip navigation, focus indicators, and touch targets. + Use Playwright's accessibility testing features with helpful comments. + Output the complete test file. + `; + + console.log("\nGenerating accessibility tests...\n"); + idle = waitForIdle(); + await session.send({ prompt: testGenerationPrompt }); + await idle; + + console.log("\n\n=== Tests Generated ==="); + } + + rl.close(); + await session.destroy(); + await client.stop(); +} + +main().catch(console.error); +``` + +## How it works + +1. **Playwright MCP server**: Configures a local MCP server running `@playwright/mcp` to provide browser automation tools +2. **Streaming output**: Uses `streaming: true` and `assistant.message.delta` events for real-time token-by-token output +3. **Accessibility snapshot**: Playwright's `browser_snapshot` tool captures the full accessibility tree of the page +4. **Structured report**: The prompt engineers a consistent WCAG-aligned report format with emoji severity indicators +5. **Test generation**: Optionally detects the project language and generates Playwright accessibility tests + +## Key concepts + +### MCP server configuration + +The recipe configures a local MCP server that runs alongside the session: + +```typescript +const session = await client.createSession({ + mcpServers: { + playwright: { + type: "local", + command: "npx", + args: ["@playwright/mcp@latest"], + tools: ["*"], + }, + }, +}); +``` + +This gives the model access to Playwright browser tools like `browser_navigate`, `browser_snapshot`, and `browser_click`. + +### Streaming with events + +Unlike `sendAndWait`, this recipe uses streaming for real-time output: + +```typescript +session.on((event) => { + if (event.type === "assistant.message.delta") { + process.stdout.write(event.data.deltaContent ?? ""); + } else if (event.type === "session.idle") { + idleResolve?.(); + } +}); +``` + +## Sample interaction + +``` +=== Accessibility Report Generator === + +Enter URL to analyze: github.com + +Analyzing: https://github.com +Please wait... + +📊 Accessibility Report: GitHub (github.com) + +✅ What's Working Well +| Category | Status | Details | +|----------|--------|---------| +| Language | ✅ Pass | lang="en" properly set | +| Page Title | ✅ Pass | "GitHub" is recognizable | +| Heading Hierarchy | ✅ Pass | Proper H1/H2 structure | +| Images | ✅ Pass | All images have alt text | + +⚠️ Issues Found +| Severity | Issue | WCAG Criterion | Recommendation | +|----------|-------|----------------|----------------| +| 🟡 Medium | Some links lack descriptive text | 2.4.4 | Add aria-label to icon-only links | + +📋 Stats Summary +- Total Links: 47 +- Total Headings: 8 (1× H1, proper hierarchy) +- Focusable Elements: 52 +- Landmarks Found: banner ✅, navigation ✅, main ✅, footer ✅ + +=== Report Complete === + +Would you like to generate Playwright accessibility tests? (y/n): y + +Detecting project language... +TypeScript detected (package.json found) + +Confirm language for tests (or enter a different one): + +Generating accessibility tests... +[Generated test file output...] + +=== Tests Generated === +``` diff --git a/cookbook/copilot-sdk/nodejs/recipe/accessibility-report.ts b/cookbook/copilot-sdk/nodejs/recipe/accessibility-report.ts new file mode 100644 index 00000000..a096726e --- /dev/null +++ b/cookbook/copilot-sdk/nodejs/recipe/accessibility-report.ts @@ -0,0 +1,187 @@ +#!/usr/bin/env tsx + +import { CopilotClient } from "@github/copilot-sdk"; +import * as readline from "node:readline"; + +// ============================================================================ +// Main Application +// ============================================================================ + +async function main() { + console.log("=== Accessibility Report Generator ===\n"); + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + const askQuestion = (query: string): Promise => + new Promise((resolve) => rl.question(query, (answer) => resolve(answer.trim()))); + + let url = await askQuestion("Enter URL to analyze: "); + + if (!url) { + console.log("No URL provided. Exiting."); + rl.close(); + return; + } + + // Ensure URL has a scheme + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "https://" + url; + } + + console.log(`\nAnalyzing: ${url}`); + console.log("Please wait...\n"); + + // Create Copilot client with Playwright MCP server + const client = new CopilotClient(); + + const session = await client.createSession({ + model: "claude-opus-4.6", + streaming: true, + mcpServers: { + playwright: { + type: "local", + command: "npx", + args: ["@playwright/mcp@latest"], + tools: ["*"], + }, + }, + }); + + // Set up streaming event handling + let idleResolve: (() => void) | null = null; + + session.on((event) => { + if (event.type === "assistant.message.delta") { + process.stdout.write(event.data.deltaContent ?? ""); + } else if (event.type === "session.idle") { + idleResolve?.(); + } else if (event.type === "session.error") { + console.error(`\nError: ${event.data.message}`); + idleResolve?.(); + } + }); + + const waitForIdle = (): Promise => + new Promise((resolve) => { + idleResolve = resolve; + }); + + const prompt = ` + Use the Playwright MCP server to analyze the accessibility of this webpage: ${url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report EXACTLY like this structure with emoji indicators: + + 📊 Accessibility Report: [Page Title] (domain.com) + + ✅ What's Working Well + | Category | Status | Details | + |----------|--------|---------| + | Language | ✅ Pass | lang="en-US" properly set | + | Page Title | ✅ Pass | "[Title]" is descriptive | + | Heading Hierarchy | ✅ Pass | Single H1, proper H2/H3 structure | + | Images | ✅ Pass | All X images have alt text | + | Viewport | ✅ Pass | Allows pinch-zoom (no user-scalable=no) | + | Links | ✅ Pass | No ambiguous "click here" links | + | Reduced Motion | ✅ Pass | Supports prefers-reduced-motion | + | Autoplay Media | ✅ Pass | No autoplay audio/video | + + ⚠️ Issues Found + | Severity | Issue | WCAG Criterion | Recommendation | + |----------|-------|----------------|----------------| + | 🔴 High | No
landmark | 1.3.1, 2.4.1 | Wrap main content in
element | + | 🔴 High | No skip navigation link | 2.4.1 | Add "Skip to content" link at top | + | 🟡 Medium | Focus outlines disabled | 2.4.7 | Default outline is none - ensure visible :focus styles exist | + | 🟡 Medium | Small touch targets | 2.5.8 | Navigation links are 37px tall (below 44px minimum) | + + 📋 Stats Summary + - Total Links: X + - Total Headings: X (1× H1, proper hierarchy) + - Focusable Elements: X + - Landmarks Found: banner ✅, navigation ✅, main ❌, footer ✅ + + ⚙️ Priority Recommendations + - Add
landmark - Wrap page content in
for screen reader navigation + - Add skip link - Hidden link at start: + - Increase touch targets - Add padding to nav links and tags to meet 44×44px minimum + - Verify focus styles - Test keyboard navigation; add visible :focus or :focus-visible outlines + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis - don't just copy the example. + `; + + let idle = waitForIdle(); + await session.send({ prompt }); + await idle; + + console.log("\n\n=== Report Complete ===\n"); + + // Prompt user for test generation + const generateTests = await askQuestion( + "Would you like to generate Playwright accessibility tests? (y/n): " + ); + + if (generateTests.toLowerCase() === "y" || generateTests.toLowerCase() === "yes") { + const detectLanguagePrompt = ` + Analyze the current working directory to detect the primary programming language used in this project. + Look for project files like package.json, *.csproj, pom.xml, requirements.txt, go.mod, etc. + + Respond with ONLY the detected language name (e.g., "TypeScript", "JavaScript", "C#", "Python", "Java") + and a brief explanation of why you detected it. + If no project is detected, suggest "TypeScript" as the default for Playwright tests. + `; + + console.log("\nDetecting project language...\n"); + idle = waitForIdle(); + await session.send({ prompt: detectLanguagePrompt }); + await idle; + + let language = await askQuestion("\n\nConfirm language for tests (or enter a different one): "); + if (!language) { + language = "TypeScript"; + } + + const testGenerationPrompt = ` + Based on the accessibility report you just generated for ${url}, create Playwright accessibility tests in ${language}. + + The tests should: + 1. Verify all the accessibility checks from the report + 2. Test for the issues that were found (to ensure they get fixed) + 3. Include tests for: + - Page has proper lang attribute + - Page has descriptive title + - Heading hierarchy is correct (single H1, proper nesting) + - All images have alt text + - No autoplay media + - Landmark regions exist (banner, nav, main, footer) + - Skip navigation link exists and works + - Focus indicators are visible + - Touch targets meet minimum size requirements + 4. Use Playwright's accessibility testing features + 5. Include helpful comments explaining each test + + Output the complete test file that can be saved and run. + Use the Playwright MCP server tools if you need to verify any page details. + `; + + console.log("\nGenerating accessibility tests...\n"); + idle = waitForIdle(); + await session.send({ prompt: testGenerationPrompt }); + await idle; + + console.log("\n\n=== Tests Generated ==="); + } + + rl.close(); + await session.destroy(); + await client.stop(); +} + +main().catch(console.error); diff --git a/cookbook/copilot-sdk/nodejs/recipe/package.json b/cookbook/copilot-sdk/nodejs/recipe/package.json index 53584216..c8ee65a2 100644 --- a/cookbook/copilot-sdk/nodejs/recipe/package.json +++ b/cookbook/copilot-sdk/nodejs/recipe/package.json @@ -8,7 +8,8 @@ "multiple-sessions": "tsx multiple-sessions.ts", "managing-local-files": "tsx managing-local-files.ts", "pr-visualization": "tsx pr-visualization.ts", - "persisting-sessions": "tsx persisting-sessions.ts" + "persisting-sessions": "tsx persisting-sessions.ts", + "accessibility-report": "tsx accessibility-report.ts" }, "dependencies": { "@github/copilot-sdk": "*" diff --git a/cookbook/copilot-sdk/python/accessibility-report.md b/cookbook/copilot-sdk/python/accessibility-report.md new file mode 100644 index 00000000..3d67a5fa --- /dev/null +++ b/cookbook/copilot-sdk/python/accessibility-report.md @@ -0,0 +1,253 @@ +# Generating Accessibility Reports + +Build a CLI tool that analyzes web page accessibility using the Playwright MCP server and generates detailed WCAG-compliant reports with optional test generation. + +> **Runnable example:** [recipe/accessibility_report.py](recipe/accessibility_report.py) +> +> ```bash +> cd recipe && pip install -r requirements.txt +> python accessibility_report.py +> ``` + +## Example scenario + +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. + +## Prerequisites + +```bash +pip install github-copilot-sdk +``` + +You also need `npx` available (Node.js installed) for the Playwright MCP server. + +## Usage + +```bash +python accessibility_report.py +# Enter a URL when prompted +``` + +## Full example: accessibility_report.py + +```python +#!/usr/bin/env python3 + +import asyncio +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) + +# ============================================================================ +# Main Application +# ============================================================================ + +async def main(): + print("=== Accessibility Report Generator ===\n") + + url = input("Enter URL to analyze: ").strip() + + if not url: + print("No URL provided. Exiting.") + return + + # Ensure URL has a scheme + if not url.startswith("http://") and not url.startswith("https://"): + url = "https://" + url + + print(f"\nAnalyzing: {url}") + print("Please wait...\n") + + # Create Copilot client with Playwright MCP server + client = CopilotClient() + await client.start() + + session = await client.create_session(SessionConfig( + model="claude-opus-4.6", + streaming=True, + mcp_servers={ + "playwright": { + "type": "local", + "command": "npx", + "args": ["@playwright/mcp@latest"], + "tools": ["*"], + } + }, + )) + + done = asyncio.Event() + + # Set up streaming event handling + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA: + print(event.data.delta_content or "", end="", flush=True) + elif event.type.value == "session.idle": + done.set() + elif event.type.value == "session.error": + print(f"\nError: {event.data.message}") + done.set() + + session.on(handle_event) + + prompt = f""" + Use the Playwright MCP server to analyze the accessibility of this webpage: {url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report with emoji indicators: + - 📊 Accessibility Report header + - ✅ What's Working Well (table with Category, Status, Details) + - ⚠️ Issues Found (table with Severity, Issue, WCAG Criterion, Recommendation) + - 📋 Stats Summary (links, headings, focusable elements, landmarks) + - ⚙️ Priority Recommendations + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis. + """ + + await session.send(MessageOptions(prompt=prompt)) + await done.wait() + + print("\n\n=== Report Complete ===\n") + + # Prompt user for test generation + generate_tests = input( + "Would you like to generate Playwright accessibility tests? (y/n): " + ).strip().lower() + + if generate_tests in ("y", "yes"): + done.clear() + + detect_language_prompt = """ + Analyze the current working directory to detect the primary programming language. + Respond with ONLY the detected language name and a brief explanation. + If no project is detected, suggest "TypeScript" as the default. + """ + + print("\nDetecting project language...\n") + await session.send(MessageOptions(prompt=detect_language_prompt)) + await done.wait() + + language = input( + "\n\nConfirm language for tests (or enter a different one): " + ).strip() + if not language: + language = "TypeScript" + + done.clear() + + test_generation_prompt = f""" + Based on the accessibility report you just generated for {url}, + create Playwright accessibility tests in {language}. + + Include tests for: lang attribute, title, heading hierarchy, alt text, + landmarks, skip navigation, focus indicators, and touch targets. + Use Playwright's accessibility testing features with helpful comments. + Output the complete test file. + """ + + print("\nGenerating accessibility tests...\n") + await session.send(MessageOptions(prompt=test_generation_prompt)) + await done.wait() + + print("\n\n=== Tests Generated ===") + + await session.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## How it works + +1. **Playwright MCP server**: Configures a local MCP server running `@playwright/mcp` to provide browser automation tools +2. **Streaming output**: Uses `streaming=True` and `ASSISTANT_MESSAGE_DELTA` events for real-time token-by-token output +3. **Accessibility snapshot**: Playwright's `browser_snapshot` tool captures the full accessibility tree of the page +4. **Structured report**: The prompt engineers a consistent WCAG-aligned report format with emoji severity indicators +5. **Test generation**: Optionally detects the project language and generates Playwright accessibility tests + +## Key concepts + +### MCP server configuration + +The recipe configures a local MCP server that runs alongside the session: + +```python +session = await client.create_session(SessionConfig( + mcp_servers={ + "playwright": { + "type": "local", + "command": "npx", + "args": ["@playwright/mcp@latest"], + "tools": ["*"], + } + }, +)) +``` + +This gives the model access to Playwright browser tools like `browser_navigate`, `browser_snapshot`, and `browser_click`. + +### Streaming with events + +Unlike `send_and_wait`, this recipe uses streaming for real-time output: + +```python +def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA: + print(event.data.delta_content or "", end="", flush=True) + elif event.type.value == "session.idle": + done.set() + +session.on(handle_event) +``` + +## Sample interaction + +``` +=== Accessibility Report Generator === + +Enter URL to analyze: github.com + +Analyzing: https://github.com +Please wait... + +📊 Accessibility Report: GitHub (github.com) + +✅ What's Working Well +| Category | Status | Details | +|----------|--------|---------| +| Language | ✅ Pass | lang="en" properly set | +| Page Title | ✅ Pass | "GitHub" is recognizable | +| Heading Hierarchy | ✅ Pass | Proper H1/H2 structure | +| Images | ✅ Pass | All images have alt text | + +⚠️ Issues Found +| Severity | Issue | WCAG Criterion | Recommendation | +|----------|-------|----------------|----------------| +| 🟡 Medium | Some links lack descriptive text | 2.4.4 | Add aria-label to icon-only links | + +📋 Stats Summary +- Total Links: 47 +- Total Headings: 8 (1× H1, proper hierarchy) +- Focusable Elements: 52 +- Landmarks Found: banner ✅, navigation ✅, main ✅, footer ✅ + +=== Report Complete === + +Would you like to generate Playwright accessibility tests? (y/n): y + +Detecting project language... +TypeScript detected (package.json found) + +Confirm language for tests (or enter a different one): + +Generating accessibility tests... +[Generated test file output...] + +=== Tests Generated === +``` diff --git a/cookbook/copilot-sdk/python/recipe/accessibility_report.py b/cookbook/copilot-sdk/python/recipe/accessibility_report.py new file mode 100644 index 00000000..c5e0b6c9 --- /dev/null +++ b/cookbook/copilot-sdk/python/recipe/accessibility_report.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 + +import asyncio +from copilot import ( + CopilotClient, SessionConfig, MessageOptions, + SessionEvent, SessionEventType, +) + +# ============================================================================ +# Main Application +# ============================================================================ + +async def main(): + print("=== Accessibility Report Generator ===\n") + + url = input("Enter URL to analyze: ").strip() + + if not url: + print("No URL provided. Exiting.") + return + + # Ensure URL has a scheme + if not url.startswith("http://") and not url.startswith("https://"): + url = "https://" + url + + print(f"\nAnalyzing: {url}") + print("Please wait...\n") + + # Create Copilot client with Playwright MCP server + client = CopilotClient() + await client.start() + + session = await client.create_session(SessionConfig( + model="claude-opus-4.6", + streaming=True, + mcp_servers={ + "playwright": { + "type": "local", + "command": "npx", + "args": ["@playwright/mcp@latest"], + "tools": ["*"], + } + }, + )) + + done = asyncio.Event() + + # Set up streaming event handling + def handle_event(event: SessionEvent): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA: + print(event.data.delta_content or "", end="", flush=True) + elif event.type.value == "session.idle": + done.set() + elif event.type.value == "session.error": + print(f"\nError: {event.data.message}") + done.set() + + session.on(handle_event) + + prompt = f""" + Use the Playwright MCP server to analyze the accessibility of this webpage: {url} + + Please: + 1. Navigate to the URL using playwright-browser_navigate + 2. Take an accessibility snapshot using playwright-browser_snapshot + 3. Analyze the snapshot and provide a detailed accessibility report + + Format the report EXACTLY like this structure with emoji indicators: + + 📊 Accessibility Report: [Page Title] (domain.com) + + ✅ What's Working Well + | Category | Status | Details | + |----------|--------|---------| + | Language | ✅ Pass | lang="en-US" properly set | + | Page Title | ✅ Pass | "[Title]" is descriptive | + | Heading Hierarchy | ✅ Pass | Single H1, proper H2/H3 structure | + | Images | ✅ Pass | All X images have alt text | + | Viewport | ✅ Pass | Allows pinch-zoom (no user-scalable=no) | + | Links | ✅ Pass | No ambiguous "click here" links | + | Reduced Motion | ✅ Pass | Supports prefers-reduced-motion | + | Autoplay Media | ✅ Pass | No autoplay audio/video | + + ⚠️ Issues Found + | Severity | Issue | WCAG Criterion | Recommendation | + |----------|-------|----------------|----------------| + | 🔴 High | No
landmark | 1.3.1, 2.4.1 | Wrap main content in
element | + | 🔴 High | No skip navigation link | 2.4.1 | Add "Skip to content" link at top | + | 🟡 Medium | Focus outlines disabled | 2.4.7 | Default outline is none - ensure visible :focus styles exist | + | 🟡 Medium | Small touch targets | 2.5.8 | Navigation links are 37px tall (below 44px minimum) | + + 📋 Stats Summary + - Total Links: X + - Total Headings: X (1× H1, proper hierarchy) + - Focusable Elements: X + - Landmarks Found: banner ✅, navigation ✅, main ❌, footer ✅ + + ⚙️ Priority Recommendations + - Add
landmark - Wrap page content in
for screen reader navigation + - Add skip link - Hidden link at start: + - Increase touch targets - Add padding to nav links and tags to meet 44×44px minimum + - Verify focus styles - Test keyboard navigation; add visible :focus or :focus-visible outlines + + Use ✅ for pass, 🔴 for high severity issues, 🟡 for medium severity, ❌ for missing items. + Include actual findings from the page analysis - don't just copy the example. + """ + + await session.send(MessageOptions(prompt=prompt)) + await done.wait() + + print("\n\n=== Report Complete ===\n") + + # Prompt user for test generation + generate_tests = input("Would you like to generate Playwright accessibility tests? (y/n): ").strip().lower() + + if generate_tests in ("y", "yes"): + done.clear() + + detect_language_prompt = """ + Analyze the current working directory to detect the primary programming language used in this project. + Look for project files like package.json, *.csproj, pom.xml, requirements.txt, go.mod, etc. + + Respond with ONLY the detected language name (e.g., "TypeScript", "JavaScript", "C#", "Python", "Java") + and a brief explanation of why you detected it. + If no project is detected, suggest "TypeScript" as the default for Playwright tests. + """ + + print("\nDetecting project language...\n") + await session.send(MessageOptions(prompt=detect_language_prompt)) + await done.wait() + + language = input("\n\nConfirm language for tests (or enter a different one): ").strip() + if not language: + language = "TypeScript" + + done.clear() + + test_generation_prompt = f""" + Based on the accessibility report you just generated for {url}, create Playwright accessibility tests in {language}. + + The tests should: + 1. Verify all the accessibility checks from the report + 2. Test for the issues that were found (to ensure they get fixed) + 3. Include tests for: + - Page has proper lang attribute + - Page has descriptive title + - Heading hierarchy is correct (single H1, proper nesting) + - All images have alt text + - No autoplay media + - Landmark regions exist (banner, nav, main, footer) + - Skip navigation link exists and works + - Focus indicators are visible + - Touch targets meet minimum size requirements + 4. Use Playwright's accessibility testing features + 5. Include helpful comments explaining each test + + Output the complete test file that can be saved and run. + Use the Playwright MCP server tools if you need to verify any page details. + """ + + print("\nGenerating accessibility tests...\n") + await session.send(MessageOptions(prompt=test_generation_prompt)) + await done.wait() + + print("\n\n=== Tests Generated ===") + + await session.destroy() + await client.stop() + +if __name__ == "__main__": + asyncio.run(main())