@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.
@@ -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
- results = await new AxeBuilder({ page }).withTags([...tags]).analyze();
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
- return {
27
- standard,
28
- violations_count: violations.length,
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 value = expandHomeDirectory(override?.trim() || template.replace("{browser}", channel));
167
- if (!path.isAbsolute(value)) throw new JevBrowserError("JEV_BROWSER_PROFILE must be an absolute path");
168
- return requireOutsidePackage(path.resolve(value), "browser profile");
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" ? profileTemplate.replace("{browser}", "") : "";
210
- if (typeof profileTemplate !== "string" || profileTemplate.split("{browser}").length !== 2
211
- || profileRemainder.includes("{") || profileRemainder.includes("}")) {
212
- throw new JevBrowserError("config/ui-testing.json browser.computer_user_data_dir must contain {browser}");
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"),