Add accessibility report cookbook recipe

Add a new cookbook recipe that generates WCAG accessibility reports using
the Playwright MCP server. Includes streaming output, structured report
formatting, and optional Playwright test generation.

- C#, TypeScript, Python, and Go implementations
- Markdown documentation for each language
- Updated cookbook.yml, copilot-sdk README, and package.json
This commit is contained in:
Aaron Powell
2026-02-12 11:25:49 +11:00
parent ef3c1e5ad6
commit 2a06b99b9b
11 changed files with 1866 additions and 2 deletions

View File

@@ -61,3 +61,11 @@ cookbooks:
- sessions - sessions
- persistence - persistence
- state-management - state-management
- id: accessibility-report
name: Accessibility Report
description: Generate WCAG accessibility reports using the Playwright MCP server
tags:
- accessibility
- playwright
- mcp
- wcag

View File

@@ -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. - [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. - [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. - [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 ### 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. - [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. - [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. - [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 ### 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. - [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. - [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. - [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 ### 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. - [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. - [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. - [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 ## How to Use
@@ -87,4 +91,4 @@ go run <filename>.go
## Status ## 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.

View File

@@ -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<string, object>()
{
["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 <main> landmark | 1.3.1, 2.4.1 | Wrap main content in <main> 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<string, object>()
{
["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 ===
```

View File

@@ -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<string, object>()
{
["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 <main> landmark | 1.3.1, 2.4.1 | Wrap main content in <main> 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 <main> landmark - Wrap page content in <main role="main"> for screen reader navigation
- Add skip link - Hidden link at start: <a href="#main-content" class="skip-link">Skip to content</a>
- 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 ===");
}

View File

@@ -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 ===
```

View File

@@ -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 <main> landmark | 1.3.1, 2.4.1 | Wrap main content in <main> 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 <main> landmark - Wrap page content in <main role="main"> for screen reader navigation
- Add skip link - Hidden link at start: <a href="#main-content" class="skip-link">Skip to content</a>
- 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 ===")
}
}

View File

@@ -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<string> =>
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<void> =>
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 ===
```

View File

@@ -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<string> =>
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<void> =>
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 <main> landmark | 1.3.1, 2.4.1 | Wrap main content in <main> 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 <main> landmark - Wrap page content in <main role="main"> for screen reader navigation
- Add skip link - Hidden link at start: <a href="#main-content" class="skip-link">Skip to content</a>
- 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);

View File

@@ -8,7 +8,8 @@
"multiple-sessions": "tsx multiple-sessions.ts", "multiple-sessions": "tsx multiple-sessions.ts",
"managing-local-files": "tsx managing-local-files.ts", "managing-local-files": "tsx managing-local-files.ts",
"pr-visualization": "tsx pr-visualization.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": { "dependencies": {
"@github/copilot-sdk": "*" "@github/copilot-sdk": "*"

View File

@@ -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 ===
```

View File

@@ -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 <main> landmark | 1.3.1, 2.4.1 | Wrap main content in <main> 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 <main> landmark - Wrap page content in <main role="main"> for screen reader navigation
- Add skip link - Hidden link at start: <a href="#main-content" class="skip-link">Skip to content</a>
- 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())