mcp-accessibility-scanner 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +21 -0
- package/Readme.md +194 -68
- package/cli.js +18 -0
- package/lib/browserContextFactory.js +228 -0
- package/lib/browserContextFactory.js.map +1 -0
- package/lib/browserServer.js +152 -0
- package/lib/browserServer.js.map +1 -0
- package/lib/config.js +190 -0
- package/lib/config.js.map +1 -0
- package/lib/connection.js +83 -0
- package/lib/connection.js.map +1 -0
- package/lib/context.js +292 -0
- package/lib/context.js.map +1 -0
- package/lib/fileUtils.js +33 -0
- package/lib/fileUtils.js.map +1 -0
- package/lib/httpServer.js +202 -0
- package/lib/httpServer.js.map +1 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/javascript.js +50 -0
- package/lib/javascript.js.map +1 -0
- package/lib/manualPromise.js +112 -0
- package/lib/manualPromise.js.map +1 -0
- package/lib/package.js +21 -0
- package/lib/package.js.map +1 -0
- package/lib/pageSnapshot.js +44 -0
- package/lib/pageSnapshot.js.map +1 -0
- package/lib/program.js +72 -0
- package/lib/program.js.map +1 -0
- package/lib/server.js +49 -0
- package/lib/server.js.map +1 -0
- package/lib/tab.js +102 -0
- package/lib/tab.js.map +1 -0
- package/lib/tools/common.js +69 -0
- package/lib/tools/common.js.map +1 -0
- package/lib/tools/console.js +45 -0
- package/lib/tools/console.js.map +1 -0
- package/lib/tools/dialogs.js +53 -0
- package/lib/tools/dialogs.js.map +1 -0
- package/lib/tools/files.js +52 -0
- package/lib/tools/files.js.map +1 -0
- package/lib/tools/install.js +57 -0
- package/lib/tools/install.js.map +1 -0
- package/lib/tools/keyboard.js +47 -0
- package/lib/tools/keyboard.js.map +1 -0
- package/lib/tools/navigate.js +94 -0
- package/lib/tools/navigate.js.map +1 -0
- package/lib/tools/network.js +52 -0
- package/lib/tools/network.js.map +1 -0
- package/lib/tools/pdf.js +50 -0
- package/lib/tools/pdf.js.map +1 -0
- package/lib/tools/screenshot.js +78 -0
- package/lib/tools/screenshot.js.map +1 -0
- package/lib/tools/snapshot.js +245 -0
- package/lib/tools/snapshot.js.map +1 -0
- package/lib/tools/tabs.js +119 -0
- package/lib/tools/tabs.js.map +1 -0
- package/lib/tools/tool.js +19 -0
- package/lib/tools/tool.js.map +1 -0
- package/lib/tools/utils.js +81 -0
- package/lib/tools/utils.js.map +1 -0
- package/lib/tools/vision.js +190 -0
- package/lib/tools/vision.js.map +1 -0
- package/lib/tools/wait.js +60 -0
- package/lib/tools/wait.js.map +1 -0
- package/lib/tools.js +59 -0
- package/lib/tools.js.map +1 -0
- package/lib/transport.js +134 -0
- package/lib/transport.js.map +1 -0
- package/package.json +41 -13
- package/build/accessibilityChecker.js +0 -379
- package/build/index.js +0 -958
- package/build/server.js +0 -50
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.AccessibilityScanner = void 0;
|
|
16
|
-
exports.scanViolations = scanViolations;
|
|
17
|
-
const playwright_1 = require("playwright");
|
|
18
|
-
const playwright_2 = require("@axe-core/playwright");
|
|
19
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
21
|
-
class AccessibilityScanner {
|
|
22
|
-
constructor() {
|
|
23
|
-
this.browser = null;
|
|
24
|
-
this.context = null;
|
|
25
|
-
this.page = null;
|
|
26
|
-
this.defaultViewport = { width: 1920, height: 1080 };
|
|
27
|
-
this.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
28
|
-
}
|
|
29
|
-
initialize() {
|
|
30
|
-
return __awaiter(this, arguments, void 0, function* (shouldRunInHeadless = true) {
|
|
31
|
-
this.browser = yield playwright_1.chromium.launch({
|
|
32
|
-
headless: shouldRunInHeadless,
|
|
33
|
-
args: [
|
|
34
|
-
"--disable-blink-features=AutomationControlled",
|
|
35
|
-
"--disable-dev-shm-usage",
|
|
36
|
-
"--no-sandbox",
|
|
37
|
-
"--disable-setuid-sandbox",
|
|
38
|
-
],
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
createContext() {
|
|
43
|
-
return __awaiter(this, arguments, void 0, function* (viewport = this.defaultViewport) {
|
|
44
|
-
if (!this.browser) {
|
|
45
|
-
throw new Error("Browser not initialized. Call initialize() first.");
|
|
46
|
-
}
|
|
47
|
-
this.context = yield this.browser.newContext({
|
|
48
|
-
viewport,
|
|
49
|
-
userAgent: this.userAgent,
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
createPage() {
|
|
54
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
-
if (!this.context) {
|
|
56
|
-
throw new Error("Context not created. Call createContext() first.");
|
|
57
|
-
}
|
|
58
|
-
this.page = yield this.context.newPage();
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
navigateToUrl(url) {
|
|
62
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
-
if (!this.page) {
|
|
64
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
65
|
-
}
|
|
66
|
-
yield this.page.goto(url);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
clickElement(selector) {
|
|
70
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
-
if (!this.page) {
|
|
72
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
73
|
-
}
|
|
74
|
-
yield this.page.click(selector);
|
|
75
|
-
yield this.page.waitForLoadState('load');
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
typeText(selector, text) {
|
|
79
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
-
if (!this.page) {
|
|
81
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
82
|
-
}
|
|
83
|
-
yield this.page.fill(selector, text);
|
|
84
|
-
yield this.page.waitForLoadState('load');
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
getElementByText(text, elementType) {
|
|
88
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
-
if (!this.page) {
|
|
90
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
91
|
-
}
|
|
92
|
-
const elements = yield this.page.locator(elementType || '*', { hasText: text }).all();
|
|
93
|
-
if (elements.length === 0) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
if (elements.length === 1) {
|
|
97
|
-
return yield elements[0].evaluate(el => {
|
|
98
|
-
if (el.id)
|
|
99
|
-
return `#${el.id}`;
|
|
100
|
-
if (el.className)
|
|
101
|
-
return `.${el.className.split(' ')[0]}`;
|
|
102
|
-
return el.tagName.toLowerCase();
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
for (let i = 0; i < elements.length; i++) {
|
|
106
|
-
const element = elements[i];
|
|
107
|
-
const isExactMatch = yield element.evaluate((el, searchText) => {
|
|
108
|
-
var _a, _b;
|
|
109
|
-
return ((_a = el.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === searchText ||
|
|
110
|
-
('innerText' in el && ((_b = el.innerText) === null || _b === void 0 ? void 0 : _b.trim()) === searchText) ||
|
|
111
|
-
el.value === searchText;
|
|
112
|
-
}, text);
|
|
113
|
-
if (isExactMatch) {
|
|
114
|
-
return yield element.evaluate(el => {
|
|
115
|
-
var _a;
|
|
116
|
-
if (el.id)
|
|
117
|
-
return `#${el.id}`;
|
|
118
|
-
if (el.className)
|
|
119
|
-
return `.${el.className.split(' ')[0]}`;
|
|
120
|
-
return `:nth-of-type(${Array.from(((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.children) || []).indexOf(el) + 1})`;
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return yield elements[0].evaluate(el => {
|
|
125
|
-
if (el.id)
|
|
126
|
-
return `#${el.id}`;
|
|
127
|
-
if (el.className)
|
|
128
|
-
return `.${el.className.split(' ')[0]}`;
|
|
129
|
-
return el.tagName.toLowerCase();
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
analyzePage() {
|
|
134
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
if (!this.page) {
|
|
136
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
137
|
-
}
|
|
138
|
-
return yield this.page.evaluate(() => {
|
|
139
|
-
const getElementInfo = (el) => {
|
|
140
|
-
const text = (el.textContent || '').trim();
|
|
141
|
-
const ariaLabel = el.getAttribute('aria-label');
|
|
142
|
-
const title = el.getAttribute('title');
|
|
143
|
-
const value = el.value;
|
|
144
|
-
const placeholder = el.placeholder;
|
|
145
|
-
return text || ariaLabel || title || value || placeholder || 'No text';
|
|
146
|
-
};
|
|
147
|
-
const buttons = Array.from(document.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]'))
|
|
148
|
-
.map(el => getElementInfo(el))
|
|
149
|
-
.filter(text => text !== 'No text');
|
|
150
|
-
const links = Array.from(document.querySelectorAll('a[href]'))
|
|
151
|
-
.map(el => getElementInfo(el))
|
|
152
|
-
.filter(text => text !== 'No text');
|
|
153
|
-
const inputs = Array.from(document.querySelectorAll('input:not([type="button"]):not([type="submit"]), textarea, select'))
|
|
154
|
-
.map(el => {
|
|
155
|
-
var _a, _b, _c;
|
|
156
|
-
const label = (_c = (_b = (_a = el.labels) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.textContent) === null || _c === void 0 ? void 0 : _c.trim();
|
|
157
|
-
const placeholder = el.placeholder;
|
|
158
|
-
const name = el.getAttribute('name');
|
|
159
|
-
const id = el.getAttribute('id');
|
|
160
|
-
return label || placeholder || name || id || 'Unnamed input';
|
|
161
|
-
});
|
|
162
|
-
return { buttons, links, inputs };
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
clickElementByText(text, elementType) {
|
|
167
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
-
if (!this.page) {
|
|
169
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
170
|
-
}
|
|
171
|
-
const selector = yield this.getElementByText(text, elementType);
|
|
172
|
-
if (!selector) {
|
|
173
|
-
const analysis = yield this.analyzePage();
|
|
174
|
-
throw new Error(`Element with text "${text}" not found. Available elements:\nButtons: ${analysis.buttons.join(', ')}\nLinks: ${analysis.links.join(', ')}`);
|
|
175
|
-
}
|
|
176
|
-
yield this.page.click(selector);
|
|
177
|
-
yield this.page.waitForLoadState('load');
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
typeTextByLabel(labelText, text) {
|
|
181
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
182
|
-
if (!this.page) {
|
|
183
|
-
throw new Error("Page not created. Call createPage() first.");
|
|
184
|
-
}
|
|
185
|
-
const inputSelector = yield this.page.evaluate((label) => {
|
|
186
|
-
const labels = Array.from(document.querySelectorAll('label'));
|
|
187
|
-
const matchingLabel = labels.find(l => { var _a; return (_a = l.textContent) === null || _a === void 0 ? void 0 : _a.trim().includes(label); });
|
|
188
|
-
if (matchingLabel) {
|
|
189
|
-
const forAttr = matchingLabel.getAttribute('for');
|
|
190
|
-
if (forAttr)
|
|
191
|
-
return `#${forAttr}`;
|
|
192
|
-
const input = matchingLabel.querySelector('input, textarea, select');
|
|
193
|
-
if (input && input.id)
|
|
194
|
-
return `#${input.id}`;
|
|
195
|
-
if (input)
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
const inputsWithPlaceholder = Array.from(document.querySelectorAll(`input[placeholder*="${label}"], textarea[placeholder*="${label}"]`));
|
|
199
|
-
if (inputsWithPlaceholder.length > 0) {
|
|
200
|
-
const input = inputsWithPlaceholder[0];
|
|
201
|
-
if (input.id)
|
|
202
|
-
return `#${input.id}`;
|
|
203
|
-
}
|
|
204
|
-
return null;
|
|
205
|
-
}, labelText);
|
|
206
|
-
if (!inputSelector) {
|
|
207
|
-
const analysis = yield this.analyzePage();
|
|
208
|
-
throw new Error(`Input field with label "${labelText}" not found. Available inputs: ${analysis.inputs.join(', ')}`);
|
|
209
|
-
}
|
|
210
|
-
yield this.page.fill(inputSelector, text);
|
|
211
|
-
yield this.page.waitForLoadState('load');
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
addViolationStyles() {
|
|
215
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
216
|
-
if (!this.page) {
|
|
217
|
-
throw new Error("Page not created.");
|
|
218
|
-
}
|
|
219
|
-
yield this.page.addStyleTag({
|
|
220
|
-
content: `
|
|
221
|
-
.a11y-violation {
|
|
222
|
-
position: relative !important;
|
|
223
|
-
outline: 4px solid #FF4444 !important;
|
|
224
|
-
margin: 2px !important;
|
|
225
|
-
}
|
|
226
|
-
.violation-number {
|
|
227
|
-
position: absolute !important;
|
|
228
|
-
top: -12px !important;
|
|
229
|
-
left: -12px !important;
|
|
230
|
-
background: #FF4444;
|
|
231
|
-
color: white !important;
|
|
232
|
-
width: 25px;
|
|
233
|
-
height: 25px;
|
|
234
|
-
border-radius: 50%;
|
|
235
|
-
display: flex !important;
|
|
236
|
-
align-items: center;
|
|
237
|
-
justify-content: center;
|
|
238
|
-
font-weight: bold;
|
|
239
|
-
font-size: 14px;
|
|
240
|
-
z-index: 10000;
|
|
241
|
-
}
|
|
242
|
-
.a11y-violation-info {
|
|
243
|
-
position: absolute !important;
|
|
244
|
-
background: #333333 !important;
|
|
245
|
-
color: white !important;
|
|
246
|
-
padding: 12px !important;
|
|
247
|
-
border-radius: 4px !important;
|
|
248
|
-
font-size: 14px !important;
|
|
249
|
-
max-width: 300px !important;
|
|
250
|
-
z-index: 9999 !important;
|
|
251
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
252
|
-
}
|
|
253
|
-
`,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
highlightViolations(violations) {
|
|
258
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
-
if (!this.page) {
|
|
260
|
-
throw new Error("Page not created.");
|
|
261
|
-
}
|
|
262
|
-
let violationCounter = 1;
|
|
263
|
-
for (const violation of violations) {
|
|
264
|
-
for (const node of violation.nodes) {
|
|
265
|
-
try {
|
|
266
|
-
const targetSelector = node.target[0];
|
|
267
|
-
const selector = Array.isArray(targetSelector)
|
|
268
|
-
? targetSelector.join(" ")
|
|
269
|
-
: targetSelector;
|
|
270
|
-
yield this.page.evaluate(({ selector, violationData, counter }) => {
|
|
271
|
-
const elements = document.querySelectorAll(selector);
|
|
272
|
-
elements.forEach((element) => {
|
|
273
|
-
const numberBadge = document.createElement("div");
|
|
274
|
-
numberBadge.className = "violation-number";
|
|
275
|
-
numberBadge.textContent = counter.toString();
|
|
276
|
-
element.classList.add("a11y-violation");
|
|
277
|
-
element.appendChild(numberBadge);
|
|
278
|
-
const listItem = document.createElement("div");
|
|
279
|
-
listItem.style.marginBottom = "15px";
|
|
280
|
-
listItem.innerHTML = `
|
|
281
|
-
<div style="color: #FF4444; font-weight: bold;">
|
|
282
|
-
Violation #${counter}: ${violationData.impact.toUpperCase()}
|
|
283
|
-
</div>
|
|
284
|
-
<div style="margin: 5px 0; font-size: 14px;">
|
|
285
|
-
${violationData.description}
|
|
286
|
-
</div>
|
|
287
|
-
`;
|
|
288
|
-
document.body.appendChild(listItem);
|
|
289
|
-
const rect = element.getBoundingClientRect();
|
|
290
|
-
listItem.style.left = `${rect.left + window.scrollX}px`;
|
|
291
|
-
listItem.style.top = `${rect.bottom + window.scrollY + 10}px`;
|
|
292
|
-
});
|
|
293
|
-
}, {
|
|
294
|
-
selector: selector,
|
|
295
|
-
violationData: {
|
|
296
|
-
impact: violation.impact,
|
|
297
|
-
description: violation.description,
|
|
298
|
-
},
|
|
299
|
-
counter: violationCounter,
|
|
300
|
-
});
|
|
301
|
-
violationCounter++;
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
console.log(`Failed to highlight element: ${error}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
generateReport(violations) {
|
|
311
|
-
var _a;
|
|
312
|
-
let reportCounter = 1;
|
|
313
|
-
const report = [];
|
|
314
|
-
for (const violation of violations) {
|
|
315
|
-
for (const node of violation.nodes) {
|
|
316
|
-
report.push({
|
|
317
|
-
index: reportCounter++,
|
|
318
|
-
element: node.target[0],
|
|
319
|
-
impactLevel: violation.impact,
|
|
320
|
-
description: violation.description,
|
|
321
|
-
wcagCriteria: ((_a = violation.tags) === null || _a === void 0 ? void 0 : _a.join(", ")) || "",
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return report;
|
|
326
|
-
}
|
|
327
|
-
takeScreenshot() {
|
|
328
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
329
|
-
if (!this.page) {
|
|
330
|
-
throw new Error("Page not created.");
|
|
331
|
-
}
|
|
332
|
-
const filePath = node_path_1.default.join(node_path_1.default.join(node_os_1.default.homedir(), "Downloads"), `a11y-report-${Date.now()}.png`);
|
|
333
|
-
const screenshot = yield this.page.screenshot({
|
|
334
|
-
path: filePath,
|
|
335
|
-
});
|
|
336
|
-
return screenshot.toString("base64");
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
scanViolations(violationsTags) {
|
|
340
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
341
|
-
if (!this.page) {
|
|
342
|
-
throw new Error("Page not created.");
|
|
343
|
-
}
|
|
344
|
-
yield this.addViolationStyles();
|
|
345
|
-
const axe = new playwright_2.AxeBuilder({ page: this.page }).withTags(violationsTags);
|
|
346
|
-
const results = yield axe.analyze();
|
|
347
|
-
yield this.highlightViolations(results.violations);
|
|
348
|
-
const report = this.generateReport(results.violations);
|
|
349
|
-
const base64Screenshot = yield this.takeScreenshot();
|
|
350
|
-
return { report, base64Screenshot };
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
cleanup() {
|
|
354
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
355
|
-
if (this.browser) {
|
|
356
|
-
yield this.browser.close();
|
|
357
|
-
this.browser = null;
|
|
358
|
-
this.context = null;
|
|
359
|
-
this.page = null;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
exports.AccessibilityScanner = AccessibilityScanner;
|
|
365
|
-
function scanViolations(url_1, violationsTags_1) {
|
|
366
|
-
return __awaiter(this, arguments, void 0, function* (url, violationsTags, viewport = { width: 1920, height: 1080 }, shouldRunInHeadless = true) {
|
|
367
|
-
const scanner = new AccessibilityScanner();
|
|
368
|
-
try {
|
|
369
|
-
yield scanner.initialize(shouldRunInHeadless);
|
|
370
|
-
yield scanner.createContext(viewport);
|
|
371
|
-
yield scanner.createPage();
|
|
372
|
-
yield scanner.navigateToUrl(url);
|
|
373
|
-
return yield scanner.scanViolations(violationsTags);
|
|
374
|
-
}
|
|
375
|
-
finally {
|
|
376
|
-
yield scanner.cleanup();
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
}
|