/**
* Tool: a11y_scanUrl
* Scans a URL for accessibility violations
*/
import { z } from 'zod';
import { Page } from 'playwright';
import { getBrowser } from '../utils/browser.js';
import { runAccessibilityScan } from '../utils/axe-scanner.js';
import { captureFullPageScreenshot, captureElementScreenshot } from '../utils/screenshot.js';
import { DEFAULT_NAVIGATION_TIMEOUT, SELECTOR_WAIT_TIMEOUT } from '../constants/config.js';
import type { ScanResult } from '../types/scan-result.js';
export const scanUrlSchema = z.object({
url: z.string().describe('URL to scan for accessibility issues'),
selector: z
.string()
.optional()
.describe("CSS selector to scan only a specific section/element (e.g., 'header', '.main-content', '#navigation')"),
waitForSelector: z
.string()
.optional()
.describe('CSS selector to wait for before scanning (useful for dynamic content)'),
captureScreenshot: z
.boolean()
.optional()
.default(false)
.describe('Capture screenshot of the scanned area with violations highlighted'),
timeout: z
.number()
.optional()
.default(DEFAULT_NAVIGATION_TIMEOUT)
.describe('Navigation timeout in milliseconds')
});
export type ScanUrlParams = z.infer<typeof scanUrlSchema>;
/**
* Execute URL accessibility scan
*/
export async function executeScanUrl(params: ScanUrlParams) {
const { url, selector, waitForSelector, captureScreenshot, timeout } = params;
let page: Page | null = null;
try {
const browser = await getBrowser();
const context = await browser.newContext();
page = await context.newPage();
// Navigate to URL
await page.goto(url, {
waitUntil: 'networkidle',
timeout
});
// Wait for specific selector if provided
if (waitForSelector) {
await page.waitForSelector(waitForSelector, { timeout: SELECTOR_WAIT_TIMEOUT });
}
// Verify selector exists if provided
if (selector) {
const elementExists = await page.locator(selector).count() > 0;
if (!elementExists) {
throw new Error(`Selector "${selector}" not found on page`);
}
}
// Run accessibility scan
const { violations, passes, incomplete } = await runAccessibilityScan(page, selector);
// Capture screenshot if requested
let screenshotPath: string | undefined;
if (captureScreenshot) {
if (selector) {
const element = page.locator(selector);
screenshotPath = await captureElementScreenshot(element, 'scan');
} else {
screenshotPath = await captureFullPageScreenshot(page);
}
}
await context.close();
// Format the response
const result: ScanResult = {
url,
timestamp: new Date().toISOString(),
selector,
summary: {
violations: violations.length,
passes,
incomplete
},
violations,
screenshotPath
};
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
if (page) await page.close().catch(() => {});
const errorResult = {
url,
selector,
timestamp: new Date().toISOString(),
error: error instanceof Error ? error.message : String(error)
};
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(errorResult, null, 2)
}
],
isError: true
};
}
}