@diegosouzacdv/jev-browser-mcp 0.6.1 → 0.7.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/README.md +385 -138
- package/config/ui-testing.json +12 -4
- package/docs/jev-browser-mcp.md +385 -138
- package/mcp_servers/jev-browser-npm/src/accessibility.mjs +18 -9
- package/mcp_servers/jev-browser-npm/src/config.mjs +55 -19
- package/mcp_servers/jev-browser-npm/src/flow.mjs +1610 -339
- package/mcp_servers/jev-browser-npm/src/server.mjs +250 -49
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ export const ACCESSIBILITY_STANDARDS = Object.freeze({
|
|
|
4
4
|
wcag2aa: Object.freeze(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]),
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
export async function runAccessibilityAudit(page, standard, settings) {
|
|
7
|
+
export async function runAccessibilityAudit(page, standard, settings, { scope, failOnImpact } = {}) {
|
|
8
8
|
const tags = ACCESSIBILITY_STANDARDS[standard];
|
|
9
9
|
if (!tags) throw new JevBrowserError("audit_accessibility standard must be wcag2aa");
|
|
10
10
|
|
|
@@ -15,17 +15,26 @@ export async function runAccessibilityAudit(page, standard, settings) {
|
|
|
15
15
|
throw new JevBrowserError("accessibility auditing requires the installed @axe-core/playwright dependency");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
let results;
|
|
19
|
-
try {
|
|
20
|
-
|
|
18
|
+
let results;
|
|
19
|
+
try {
|
|
20
|
+
let audit = new AxeBuilder({ page }).withTags([...tags]);
|
|
21
|
+
if (scope) audit = audit.include(scope);
|
|
22
|
+
results = await audit.analyze();
|
|
21
23
|
} catch {
|
|
22
24
|
throw new JevBrowserError("axe-core could not complete the accessibility audit");
|
|
23
25
|
}
|
|
24
|
-
const violations = Array.isArray(results?.violations) ? results.violations : [];
|
|
25
|
-
const incomplete = Array.isArray(results?.incomplete) ? results.incomplete : [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const violations = Array.isArray(results?.violations) ? results.violations : [];
|
|
27
|
+
const incomplete = Array.isArray(results?.incomplete) ? results.incomplete : [];
|
|
28
|
+
const impactOrder = ["minor", "moderate", "serious", "critical"];
|
|
29
|
+
const threshold = impactOrder.indexOf(failOnImpact || settings.browser.accessibilityFailOnImpactDefault);
|
|
30
|
+
if (threshold < 0) throw new JevBrowserError("accessibility fail_on_impact must be minor, moderate, serious, or critical");
|
|
31
|
+
const blockingViolations = violations.filter((violation) => impactOrder.indexOf(violation.impact) >= threshold);
|
|
32
|
+
return {
|
|
33
|
+
standard,
|
|
34
|
+
...(scope ? { scope } : {}),
|
|
35
|
+
fail_on_impact: impactOrder[threshold],
|
|
36
|
+
violations_count: violations.length,
|
|
37
|
+
blocking_violations_count: blockingViolations.length,
|
|
29
38
|
violations_truncated: violations.length > settings.jev.maxAccessibilityViolations,
|
|
30
39
|
violations: violations.slice(0, settings.jev.maxAccessibilityViolations).map((violation) => ({
|
|
31
40
|
id: String(violation.id || "unknown").slice(0, 100),
|
|
@@ -32,8 +32,15 @@ const CONFIG_KEYS = {
|
|
|
32
32
|
"snapshot_include_hidden_default",
|
|
33
33
|
"visibility_poll_interval_ms_default",
|
|
34
34
|
"capture_network_error_bodies_default",
|
|
35
|
-
"max_network_error_body_bytes",
|
|
36
|
-
"max_network_error_message_chars",
|
|
35
|
+
"max_network_error_body_bytes",
|
|
36
|
+
"max_network_error_message_chars",
|
|
37
|
+
"max_network_history_events",
|
|
38
|
+
"max_tool_response_bytes",
|
|
39
|
+
"max_viewport_width",
|
|
40
|
+
"max_viewport_height",
|
|
41
|
+
"accessibility_fail_on_impact_default",
|
|
42
|
+
"record_video_width",
|
|
43
|
+
"record_video_height",
|
|
37
44
|
"max_upload_files",
|
|
38
45
|
"max_upload_path_chars",
|
|
39
46
|
"max_upload_file_bytes",
|
|
@@ -48,7 +55,8 @@ const CONFIG_KEYS = {
|
|
|
48
55
|
"max_frame_snapshots",
|
|
49
56
|
"fast_path_single_plan_default",
|
|
50
57
|
"screenshot_on_failure_default",
|
|
51
|
-
"trace_on_failure_default",
|
|
58
|
+
"trace_on_failure_default",
|
|
59
|
+
"trace_on_success_default",
|
|
52
60
|
"capture_console_errors_default",
|
|
53
61
|
"capture_network_errors_default",
|
|
54
62
|
"block_trackers_default",
|
|
@@ -162,11 +170,20 @@ function validProviderUrl(value) {
|
|
|
162
170
|
return parsed.toString();
|
|
163
171
|
}
|
|
164
172
|
|
|
165
|
-
function profileDirectory(template, channel, override) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
173
|
+
function profileDirectory(template, channel, sessionId, override) {
|
|
174
|
+
const requestedOverride = override?.trim();
|
|
175
|
+
const unsupportedPlaceholder = requestedOverride?.replaceAll("{session}", "");
|
|
176
|
+
if (unsupportedPlaceholder?.includes("{") || unsupportedPlaceholder?.includes("}")) {
|
|
177
|
+
throw new JevBrowserError("JEV_BROWSER_PROFILE supports only the {session} placeholder");
|
|
178
|
+
}
|
|
179
|
+
const hasSessionPlaceholder = Boolean(requestedOverride?.includes("{session}"));
|
|
180
|
+
const profileBase = requestedOverride?.replaceAll("{session}", sessionId);
|
|
181
|
+
const value = expandHomeDirectory(requestedOverride
|
|
182
|
+
? hasSessionPlaceholder ? profileBase : `${profileBase}-session-${sessionId}`
|
|
183
|
+
: template.replace("{browser}", channel).replace("{session}", sessionId));
|
|
184
|
+
if (!path.isAbsolute(value)) throw new JevBrowserError("JEV_BROWSER_PROFILE must be an absolute path");
|
|
185
|
+
return requireOutsidePackage(path.resolve(value), "browser profile");
|
|
186
|
+
}
|
|
170
187
|
|
|
171
188
|
export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {}) {
|
|
172
189
|
let document;
|
|
@@ -205,12 +222,17 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
205
222
|
if (!new Set(["chrome", "msedge"]).has(channel)) {
|
|
206
223
|
throw new JevBrowserError("JEV_BROWSER_CHANNEL must be chrome or msedge");
|
|
207
224
|
}
|
|
208
|
-
const profileTemplate = document.browser.computer_user_data_dir;
|
|
209
|
-
const profileRemainder = typeof profileTemplate === "string"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
const profileTemplate = document.browser.computer_user_data_dir;
|
|
226
|
+
const profileRemainder = typeof profileTemplate === "string"
|
|
227
|
+
? profileTemplate.replace("{browser}", "").replace("{session}", "") : "";
|
|
228
|
+
if (typeof profileTemplate !== "string" || profileTemplate.split("{browser}").length !== 2
|
|
229
|
+
|| profileTemplate.split("{session}").length !== 2 || profileRemainder.includes("{") || profileRemainder.includes("}")) {
|
|
230
|
+
throw new JevBrowserError("config/ui-testing.json browser.computer_user_data_dir must contain {browser} and {session}");
|
|
231
|
+
}
|
|
232
|
+
const sessionId = (env.JEV_BROWSER_SESSION_ID?.trim() || `pid-${process.pid}`);
|
|
233
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(sessionId)) {
|
|
234
|
+
throw new JevBrowserError("JEV_BROWSER_SESSION_ID must contain 1-64 letters, numbers, underscores, or hyphens");
|
|
235
|
+
}
|
|
214
236
|
|
|
215
237
|
const configuredProviderUrl = validProviderUrl(document.jev.provider_url);
|
|
216
238
|
const providerUrl = validProviderUrl(env.JEV_PROVIDER_URL?.trim() || configuredProviderUrl);
|
|
@@ -259,9 +281,12 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
259
281
|
browserOptions.login_text_default,
|
|
260
282
|
"jev_browser_mcp.browser.login_text_default",
|
|
261
283
|
);
|
|
262
|
-
if (!new Set(["none", "diff", "full"]).has(browserOptions.return_snapshot_default)) {
|
|
284
|
+
if (!new Set(["none", "diff", "full"]).has(browserOptions.return_snapshot_default)) {
|
|
263
285
|
throw new JevBrowserError("config/ui-testing.json return_snapshot_default must be none, diff, or full");
|
|
264
|
-
}
|
|
286
|
+
}
|
|
287
|
+
if (!new Set(["minor", "moderate", "serious", "critical"]).has(browserOptions.accessibility_fail_on_impact_default)) {
|
|
288
|
+
throw new JevBrowserError("config/ui-testing.json accessibility_fail_on_impact_default must be minor, moderate, serious, or critical");
|
|
289
|
+
}
|
|
265
290
|
const maxReadyTimeoutSeconds = positiveNumber(browserOptions.max_ready_timeout_seconds, "jev_browser_mcp.browser.max_ready_timeout_seconds");
|
|
266
291
|
const readyTimeoutSecondsDefault = positiveNumber(browserOptions.ready_timeout_seconds_default, "jev_browser_mcp.browser.ready_timeout_seconds_default");
|
|
267
292
|
const maxReadyStableMs = positiveNumber(browserOptions.max_ready_stable_ms, "jev_browser_mcp.browser.max_ready_stable_ms", true);
|
|
@@ -285,7 +310,8 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
285
310
|
browser: Object.freeze({
|
|
286
311
|
mode,
|
|
287
312
|
channel,
|
|
288
|
-
profileDir: profileDirectory(profileTemplate, channel, env.JEV_BROWSER_PROFILE),
|
|
313
|
+
profileDir: profileDirectory(profileTemplate, channel, sessionId, env.JEV_BROWSER_PROFILE),
|
|
314
|
+
sessionId,
|
|
289
315
|
maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
|
|
290
316
|
maxTextEntryChars: positiveNumber(document.browser.max_text_entry_chars, "browser.max_text_entry_chars", true),
|
|
291
317
|
actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
|
|
@@ -297,7 +323,16 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
297
323
|
keyDelayMs: positiveNumber(browserOptions.key_delay_ms_default, "jev_browser_mcp.browser.key_delay_ms_default", true),
|
|
298
324
|
visibilityPollMs: positiveNumber(browserOptions.visibility_poll_interval_ms_default, "jev_browser_mcp.browser.visibility_poll_interval_ms_default", true),
|
|
299
325
|
maxNetworkErrorBodyBytes: positiveNumber(browserOptions.max_network_error_body_bytes, "jev_browser_mcp.browser.max_network_error_body_bytes", true),
|
|
300
|
-
maxNetworkErrorMessageChars: positiveNumber(browserOptions.max_network_error_message_chars, "jev_browser_mcp.browser.max_network_error_message_chars", true),
|
|
326
|
+
maxNetworkErrorMessageChars: positiveNumber(browserOptions.max_network_error_message_chars, "jev_browser_mcp.browser.max_network_error_message_chars", true),
|
|
327
|
+
maxNetworkHistoryEvents: positiveNumber(browserOptions.max_network_history_events, "jev_browser_mcp.browser.max_network_history_events", true),
|
|
328
|
+
maxToolResponseBytes: positiveNumber(browserOptions.max_tool_response_bytes, "jev_browser_mcp.browser.max_tool_response_bytes", true),
|
|
329
|
+
maxViewportWidth: positiveNumber(browserOptions.max_viewport_width, "jev_browser_mcp.browser.max_viewport_width", true),
|
|
330
|
+
maxViewportHeight: positiveNumber(browserOptions.max_viewport_height, "jev_browser_mcp.browser.max_viewport_height", true),
|
|
331
|
+
accessibilityFailOnImpactDefault: browserOptions.accessibility_fail_on_impact_default,
|
|
332
|
+
recordVideoSize: Object.freeze({
|
|
333
|
+
width: positiveNumber(browserOptions.record_video_width, "jev_browser_mcp.browser.record_video_width", true),
|
|
334
|
+
height: positiveNumber(browserOptions.record_video_height, "jev_browser_mcp.browser.record_video_height", true),
|
|
335
|
+
}),
|
|
301
336
|
maxUploadFiles: positiveNumber(browserOptions.max_upload_files, "jev_browser_mcp.browser.max_upload_files", true),
|
|
302
337
|
maxUploadPathChars: positiveNumber(browserOptions.max_upload_path_chars, "jev_browser_mcp.browser.max_upload_path_chars", true),
|
|
303
338
|
maxUploadFileBytes: positiveNumber(browserOptions.max_upload_file_bytes, "jev_browser_mcp.browser.max_upload_file_bytes", true),
|
|
@@ -312,7 +347,8 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
312
347
|
defaults: Object.freeze({
|
|
313
348
|
fastPath: booleanValue(browserOptions.fast_path_single_plan_default, "jev_browser_mcp.browser.fast_path_single_plan_default"),
|
|
314
349
|
screenshotOnFailure: booleanValue(browserOptions.screenshot_on_failure_default, "jev_browser_mcp.browser.screenshot_on_failure_default"),
|
|
315
|
-
traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
|
|
350
|
+
traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
|
|
351
|
+
traceOnSuccess: booleanValue(browserOptions.trace_on_success_default, "jev_browser_mcp.browser.trace_on_success_default"),
|
|
316
352
|
captureConsoleErrors: booleanValue(browserOptions.capture_console_errors_default, "jev_browser_mcp.browser.capture_console_errors_default"),
|
|
317
353
|
captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
|
|
318
354
|
captureNetworkErrorBodies: booleanValue(browserOptions.capture_network_error_bodies_default, "jev_browser_mcp.browser.capture_network_error_bodies_default"),
|