cbrowser 8.8.0 → 8.9.1
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 +9 -2
- package/dist/analysis/bug-hunter.js +1 -4
- package/dist/analysis/bug-hunter.js.map +1 -1
- package/dist/analysis/chaos-testing.js +4 -8
- package/dist/analysis/chaos-testing.js.map +1 -1
- package/dist/analysis/index.js +4 -20
- package/dist/analysis/index.js.map +1 -1
- package/dist/analysis/natural-language.js +4 -10
- package/dist/analysis/natural-language.js.map +1 -1
- package/dist/analysis/persona-comparison.js +6 -10
- package/dist/analysis/persona-comparison.js.map +1 -1
- package/dist/browser.d.ts +19 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +309 -132
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +138 -131
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +61 -47
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.js +50 -61
- package/dist/config.js.map +1 -1
- package/dist/daemon.js +28 -37
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +10 -40
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-remote.js +153 -156
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.js +146 -149
- package/dist/mcp-server.js.map +1 -1
- package/dist/performance/index.js +1 -17
- package/dist/performance/index.js.map +1 -1
- package/dist/performance/metrics.js +30 -39
- package/dist/performance/metrics.js.map +1 -1
- package/dist/personas.js +32 -46
- package/dist/personas.js.map +1 -1
- package/dist/testing/coverage.js +13 -23
- package/dist/testing/coverage.js.map +1 -1
- package/dist/testing/flaky-detection.js +4 -8
- package/dist/testing/flaky-detection.js.map +1 -1
- package/dist/testing/index.js +4 -20
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/nl-test-suite.js +11 -19
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/testing/test-repair.js +9 -15
- package/dist/testing/test-repair.js.map +1 -1
- package/dist/types.js +3 -6
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +60 -0
- package/dist/version.js.map +1 -0
- package/dist/visual/ab-comparison.js +16 -22
- package/dist/visual/ab-comparison.js.map +1 -1
- package/dist/visual/cross-browser.js +17 -24
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/index.js +4 -20
- package/dist/visual/index.js.map +1 -1
- package/dist/visual/regression.js +46 -58
- package/dist/visual/regression.js.map +1 -1
- package/dist/visual/responsive.js +22 -29
- package/dist/visual/responsive.js.map +1 -1
- package/package.json +2 -1
package/dist/mcp-server.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
2
|
/**
|
|
4
3
|
* CBrowser MCP Server
|
|
5
4
|
*
|
|
@@ -7,40 +6,38 @@
|
|
|
7
6
|
* Run with: cbrowser mcp-server
|
|
8
7
|
* Or: npx cbrowser mcp-server
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const browser_js_1 = require("./browser.js");
|
|
16
|
-
const config_js_1 = require("./config.js");
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { CBrowser } from "./browser.js";
|
|
13
|
+
import { ensureDirectories, getStatusInfo } from "./config.js";
|
|
17
14
|
// Visual module imports
|
|
18
|
-
|
|
15
|
+
import { runVisualRegression, runCrossBrowserTest, runResponsiveTest, runABComparison, crossBrowserDiff, captureVisualBaseline, listVisualBaselines, } from "./visual/index.js";
|
|
19
16
|
// Testing module imports
|
|
20
|
-
|
|
17
|
+
import { runNLTestSuite, parseNLTestSuite, dryRunNLTestSuite, repairTest, detectFlakyTests, generateCoverageMap, } from "./testing/index.js";
|
|
21
18
|
// Analysis module imports
|
|
22
|
-
|
|
19
|
+
import { huntBugs, runChaosTest, comparePersonas, findElementByIntent, } from "./analysis/index.js";
|
|
23
20
|
// Persona imports for cognitive journey
|
|
24
|
-
|
|
21
|
+
import { getPersona, listPersonas, getCognitiveProfile, createCognitivePersona, } from "./personas.js";
|
|
25
22
|
// Performance module imports
|
|
26
|
-
|
|
27
|
-
// Version
|
|
28
|
-
|
|
23
|
+
import { capturePerformanceBaseline, detectPerformanceRegression, listPerformanceBaselines, } from "./performance/index.js";
|
|
24
|
+
// Version from package.json - single source of truth
|
|
25
|
+
import { VERSION } from "./version.js";
|
|
29
26
|
// Shared browser instance
|
|
30
27
|
let browser = null;
|
|
31
28
|
async function getBrowser() {
|
|
32
29
|
if (!browser) {
|
|
33
|
-
browser = new
|
|
30
|
+
browser = new CBrowser({
|
|
34
31
|
headless: true,
|
|
35
32
|
persistent: true,
|
|
36
33
|
});
|
|
37
34
|
}
|
|
38
35
|
return browser;
|
|
39
36
|
}
|
|
40
|
-
async function startMcpServer() {
|
|
37
|
+
export async function startMcpServer() {
|
|
41
38
|
// Auto-initialize all data directories on server start
|
|
42
|
-
|
|
43
|
-
const server = new
|
|
39
|
+
ensureDirectories();
|
|
40
|
+
const server = new McpServer({
|
|
44
41
|
name: "cbrowser",
|
|
45
42
|
version: VERSION,
|
|
46
43
|
});
|
|
@@ -48,7 +45,7 @@ async function startMcpServer() {
|
|
|
48
45
|
// Navigation Tools
|
|
49
46
|
// =========================================================================
|
|
50
47
|
server.tool("navigate", "Navigate to a URL and take a screenshot", {
|
|
51
|
-
url:
|
|
48
|
+
url: z.string().url().describe("The URL to navigate to"),
|
|
52
49
|
}, async ({ url }) => {
|
|
53
50
|
const b = await getBrowser();
|
|
54
51
|
const result = await b.navigate(url);
|
|
@@ -71,9 +68,9 @@ async function startMcpServer() {
|
|
|
71
68
|
// Interaction Tools
|
|
72
69
|
// =========================================================================
|
|
73
70
|
server.tool("click", "Click an element on the page using text, selector, or description. Use verbose=true for detailed debug info on failure.", {
|
|
74
|
-
selector:
|
|
75
|
-
force:
|
|
76
|
-
verbose:
|
|
71
|
+
selector: z.string().describe("Element to click (text content, CSS selector, or description)"),
|
|
72
|
+
force: z.boolean().optional().describe("Bypass safety checks for destructive actions"),
|
|
73
|
+
verbose: z.boolean().optional().describe("Return available elements and AI suggestions on failure"),
|
|
77
74
|
}, async ({ selector, force, verbose }) => {
|
|
78
75
|
const b = await getBrowser();
|
|
79
76
|
const result = await b.click(selector, { force, verbose });
|
|
@@ -95,9 +92,9 @@ async function startMcpServer() {
|
|
|
95
92
|
};
|
|
96
93
|
});
|
|
97
94
|
server.tool("smart_click", "Click with auto-retry and self-healing selectors", {
|
|
98
|
-
selector:
|
|
99
|
-
maxRetries:
|
|
100
|
-
dismissOverlays:
|
|
95
|
+
selector: z.string().describe("Element to click"),
|
|
96
|
+
maxRetries: z.number().optional().default(3).describe("Maximum retry attempts"),
|
|
97
|
+
dismissOverlays: z.boolean().optional().default(false).describe("Dismiss overlays before clicking"),
|
|
101
98
|
}, async ({ selector, maxRetries, dismissOverlays }) => {
|
|
102
99
|
const b = await getBrowser();
|
|
103
100
|
const result = await b.smartClick(selector, { maxRetries, dismissOverlays });
|
|
@@ -117,8 +114,8 @@ async function startMcpServer() {
|
|
|
117
114
|
};
|
|
118
115
|
});
|
|
119
116
|
server.tool("dismiss_overlay", "Detect and dismiss modal overlays (cookie consent, age verification, newsletter popups). Constitutional Yellow zone.", {
|
|
120
|
-
type:
|
|
121
|
-
customSelector:
|
|
117
|
+
type: z.enum(["auto", "cookie", "age-verify", "newsletter", "custom"]).optional().default("auto").describe("Overlay type to detect"),
|
|
118
|
+
customSelector: z.string().optional().describe("Custom CSS selector for overlay close button"),
|
|
122
119
|
}, async ({ type, customSelector }) => {
|
|
123
120
|
const b = await getBrowser();
|
|
124
121
|
const result = await b.dismissOverlay({ type, customSelector });
|
|
@@ -138,9 +135,9 @@ async function startMcpServer() {
|
|
|
138
135
|
};
|
|
139
136
|
});
|
|
140
137
|
server.tool("fill", "Fill a form field with text. Use verbose=true for detailed debug info on failure.", {
|
|
141
|
-
selector:
|
|
142
|
-
value:
|
|
143
|
-
verbose:
|
|
138
|
+
selector: z.string().describe("Input field to fill (name, placeholder, label, or selector)"),
|
|
139
|
+
value: z.string().describe("Value to enter"),
|
|
140
|
+
verbose: z.boolean().optional().describe("Return available inputs and AI suggestions on failure"),
|
|
144
141
|
}, async ({ selector, value, verbose }) => {
|
|
145
142
|
const b = await getBrowser();
|
|
146
143
|
const result = await b.fill(selector, value, { verbose });
|
|
@@ -164,7 +161,7 @@ async function startMcpServer() {
|
|
|
164
161
|
// Extraction Tools
|
|
165
162
|
// =========================================================================
|
|
166
163
|
server.tool("screenshot", "Take a screenshot of the current page", {
|
|
167
|
-
path:
|
|
164
|
+
path: z.string().optional().describe("Optional path to save the screenshot"),
|
|
168
165
|
}, async ({ path }) => {
|
|
169
166
|
const b = await getBrowser();
|
|
170
167
|
const file = await b.screenshot(path);
|
|
@@ -178,7 +175,7 @@ async function startMcpServer() {
|
|
|
178
175
|
};
|
|
179
176
|
});
|
|
180
177
|
server.tool("extract", "Extract data from the page", {
|
|
181
|
-
what:
|
|
178
|
+
what: z.enum(["links", "headings", "forms", "images", "text"]).describe("What to extract"),
|
|
182
179
|
}, async ({ what }) => {
|
|
183
180
|
const b = await getBrowser();
|
|
184
181
|
const result = await b.extract(what);
|
|
@@ -195,7 +192,7 @@ async function startMcpServer() {
|
|
|
195
192
|
// Assertion Tools
|
|
196
193
|
// =========================================================================
|
|
197
194
|
server.tool("assert", "Assert a condition using natural language", {
|
|
198
|
-
assertion:
|
|
195
|
+
assertion: z.string().describe("Natural language assertion like \"page contains 'Welcome'\" or \"title is 'Home'\""),
|
|
199
196
|
}, async ({ assertion }) => {
|
|
200
197
|
const b = await getBrowser();
|
|
201
198
|
const result = await b.assert(assertion);
|
|
@@ -237,7 +234,7 @@ async function startMcpServer() {
|
|
|
237
234
|
};
|
|
238
235
|
});
|
|
239
236
|
server.tool("generate_tests", "Generate test scenarios for a page", {
|
|
240
|
-
url:
|
|
237
|
+
url: z.string().url().optional().describe("URL to analyze (uses current page if not provided)"),
|
|
241
238
|
}, async ({ url }) => {
|
|
242
239
|
const b = await getBrowser();
|
|
243
240
|
const result = await b.generateTests(url);
|
|
@@ -261,7 +258,7 @@ async function startMcpServer() {
|
|
|
261
258
|
// Session Tools
|
|
262
259
|
// =========================================================================
|
|
263
260
|
server.tool("save_session", "Save browser session (cookies, storage) for later use", {
|
|
264
|
-
name:
|
|
261
|
+
name: z.string().describe("Name for the saved session"),
|
|
265
262
|
}, async ({ name }) => {
|
|
266
263
|
const b = await getBrowser();
|
|
267
264
|
await b.saveSession(name);
|
|
@@ -275,7 +272,7 @@ async function startMcpServer() {
|
|
|
275
272
|
};
|
|
276
273
|
});
|
|
277
274
|
server.tool("load_session", "Load a previously saved session", {
|
|
278
|
-
name:
|
|
275
|
+
name: z.string().describe("Name of the session to load"),
|
|
279
276
|
}, async ({ name }) => {
|
|
280
277
|
const b = await getBrowser();
|
|
281
278
|
const loaded = await b.loadSession(name);
|
|
@@ -301,7 +298,7 @@ async function startMcpServer() {
|
|
|
301
298
|
};
|
|
302
299
|
});
|
|
303
300
|
server.tool("delete_session", "Delete a saved session by name", {
|
|
304
|
-
name:
|
|
301
|
+
name: z.string().describe("Name of the session to delete"),
|
|
305
302
|
}, async ({ name }) => {
|
|
306
303
|
const b = await getBrowser();
|
|
307
304
|
const deleted = b.deleteSession(name);
|
|
@@ -333,10 +330,10 @@ async function startMcpServer() {
|
|
|
333
330
|
// Visual Testing Tools (v7.0.0+)
|
|
334
331
|
// =========================================================================
|
|
335
332
|
server.tool("visual_baseline", "Capture a visual baseline for a URL", {
|
|
336
|
-
url:
|
|
337
|
-
name:
|
|
333
|
+
url: z.string().url().describe("URL to capture baseline for"),
|
|
334
|
+
name: z.string().describe("Name for the baseline"),
|
|
338
335
|
}, async ({ url, name }) => {
|
|
339
|
-
const result = await
|
|
336
|
+
const result = await captureVisualBaseline(url, name, {});
|
|
340
337
|
return {
|
|
341
338
|
content: [
|
|
342
339
|
{
|
|
@@ -352,10 +349,10 @@ async function startMcpServer() {
|
|
|
352
349
|
};
|
|
353
350
|
});
|
|
354
351
|
server.tool("visual_regression", "Run AI visual regression test against a baseline", {
|
|
355
|
-
url:
|
|
356
|
-
baselineName:
|
|
352
|
+
url: z.string().url().describe("URL to test"),
|
|
353
|
+
baselineName: z.string().describe("Name of baseline to compare against"),
|
|
357
354
|
}, async ({ url, baselineName }) => {
|
|
358
|
-
const result = await
|
|
355
|
+
const result = await runVisualRegression(url, baselineName);
|
|
359
356
|
return {
|
|
360
357
|
content: [
|
|
361
358
|
{
|
|
@@ -371,10 +368,10 @@ async function startMcpServer() {
|
|
|
371
368
|
};
|
|
372
369
|
});
|
|
373
370
|
server.tool("cross_browser_test", "Test page rendering across multiple browsers", {
|
|
374
|
-
url:
|
|
375
|
-
browsers:
|
|
371
|
+
url: z.string().url().describe("URL to test"),
|
|
372
|
+
browsers: z.array(z.enum(["chromium", "firefox", "webkit"])).optional().describe("Browsers to test"),
|
|
376
373
|
}, async ({ url, browsers }) => {
|
|
377
|
-
const result = await
|
|
374
|
+
const result = await runCrossBrowserTest(url, { browsers });
|
|
378
375
|
return {
|
|
379
376
|
content: [
|
|
380
377
|
{
|
|
@@ -394,10 +391,10 @@ async function startMcpServer() {
|
|
|
394
391
|
};
|
|
395
392
|
});
|
|
396
393
|
server.tool("cross_browser_diff", "Quick diff of page metrics across browsers", {
|
|
397
|
-
url:
|
|
398
|
-
browsers:
|
|
394
|
+
url: z.string().url().describe("URL to compare"),
|
|
395
|
+
browsers: z.array(z.enum(["chromium", "firefox", "webkit"])).optional().describe("Browsers to compare"),
|
|
399
396
|
}, async ({ url, browsers }) => {
|
|
400
|
-
const result = await
|
|
397
|
+
const result = await crossBrowserDiff(url, browsers);
|
|
401
398
|
return {
|
|
402
399
|
content: [
|
|
403
400
|
{
|
|
@@ -416,10 +413,10 @@ async function startMcpServer() {
|
|
|
416
413
|
};
|
|
417
414
|
});
|
|
418
415
|
server.tool("responsive_test", "Test page across different viewport sizes", {
|
|
419
|
-
url:
|
|
420
|
-
viewports:
|
|
416
|
+
url: z.string().url().describe("URL to test"),
|
|
417
|
+
viewports: z.array(z.string()).optional().describe("Viewport presets (mobile, tablet, desktop, etc.)"),
|
|
421
418
|
}, async ({ url, viewports }) => {
|
|
422
|
-
const result = await
|
|
419
|
+
const result = await runResponsiveTest(url, { viewports });
|
|
423
420
|
return {
|
|
424
421
|
content: [
|
|
425
422
|
{
|
|
@@ -435,13 +432,13 @@ async function startMcpServer() {
|
|
|
435
432
|
};
|
|
436
433
|
});
|
|
437
434
|
server.tool("ab_comparison", "Compare two URLs visually (staging vs production)", {
|
|
438
|
-
urlA:
|
|
439
|
-
urlB:
|
|
440
|
-
labelA:
|
|
441
|
-
labelB:
|
|
435
|
+
urlA: z.string().url().describe("First URL (e.g., staging)"),
|
|
436
|
+
urlB: z.string().url().describe("Second URL (e.g., production)"),
|
|
437
|
+
labelA: z.string().optional().describe("Label for first URL"),
|
|
438
|
+
labelB: z.string().optional().describe("Label for second URL"),
|
|
442
439
|
}, async ({ urlA, urlB, labelA, labelB }) => {
|
|
443
440
|
const labels = labelA && labelB ? { a: labelA, b: labelB } : undefined;
|
|
444
|
-
const result = await
|
|
441
|
+
const result = await runABComparison(urlA, urlB, { labels });
|
|
445
442
|
return {
|
|
446
443
|
content: [
|
|
447
444
|
{
|
|
@@ -460,9 +457,9 @@ async function startMcpServer() {
|
|
|
460
457
|
// Testing Tools (v6.0.0+)
|
|
461
458
|
// =========================================================================
|
|
462
459
|
server.tool("nl_test_file", "Run natural language test suite from a file. Returns step-level results with enriched error info, partial matches, and suggestions.", {
|
|
463
|
-
filepath:
|
|
464
|
-
dryRun:
|
|
465
|
-
fuzzyMatch:
|
|
460
|
+
filepath: z.string().describe("Path to the test file"),
|
|
461
|
+
dryRun: z.boolean().optional().describe("Parse and display steps without executing"),
|
|
462
|
+
fuzzyMatch: z.boolean().optional().describe("Use case-insensitive fuzzy matching for assertions"),
|
|
466
463
|
}, async ({ filepath, dryRun, fuzzyMatch }) => {
|
|
467
464
|
const fs = await import("fs");
|
|
468
465
|
if (!fs.existsSync(filepath)) {
|
|
@@ -470,12 +467,12 @@ async function startMcpServer() {
|
|
|
470
467
|
}
|
|
471
468
|
const fileContent = fs.readFileSync(filepath, "utf-8");
|
|
472
469
|
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
473
|
-
const suite =
|
|
470
|
+
const suite = parseNLTestSuite(fileContent, suiteName);
|
|
474
471
|
if (dryRun) {
|
|
475
|
-
const dryResult =
|
|
472
|
+
const dryResult = dryRunNLTestSuite(suite);
|
|
476
473
|
return { content: [{ type: "text", text: JSON.stringify(dryResult, null, 2) }] };
|
|
477
474
|
}
|
|
478
|
-
const result = await
|
|
475
|
+
const result = await runNLTestSuite(suite, { fuzzyMatch: fuzzyMatch || false });
|
|
479
476
|
return {
|
|
480
477
|
content: [
|
|
481
478
|
{
|
|
@@ -508,17 +505,17 @@ async function startMcpServer() {
|
|
|
508
505
|
};
|
|
509
506
|
});
|
|
510
507
|
server.tool("nl_test_inline", "Run natural language tests from inline content. Returns step-level results with enriched error info, partial matches, and suggestions.", {
|
|
511
|
-
content:
|
|
512
|
-
name:
|
|
513
|
-
dryRun:
|
|
514
|
-
fuzzyMatch:
|
|
508
|
+
content: z.string().describe("Test content with instructions like 'go to https://...' and 'click login'"),
|
|
509
|
+
name: z.string().optional().describe("Name for the test suite"),
|
|
510
|
+
dryRun: z.boolean().optional().describe("Parse and display steps without executing"),
|
|
511
|
+
fuzzyMatch: z.boolean().optional().describe("Use case-insensitive fuzzy matching for assertions"),
|
|
515
512
|
}, async ({ content, name, dryRun, fuzzyMatch }) => {
|
|
516
|
-
const suite =
|
|
513
|
+
const suite = parseNLTestSuite(content, name || "Inline Test");
|
|
517
514
|
if (dryRun) {
|
|
518
|
-
const dryResult =
|
|
515
|
+
const dryResult = dryRunNLTestSuite(suite);
|
|
519
516
|
return { content: [{ type: "text", text: JSON.stringify(dryResult, null, 2) }] };
|
|
520
517
|
}
|
|
521
|
-
const result = await
|
|
518
|
+
const result = await runNLTestSuite(suite, { fuzzyMatch: fuzzyMatch || false });
|
|
522
519
|
return {
|
|
523
520
|
content: [
|
|
524
521
|
{
|
|
@@ -551,9 +548,9 @@ async function startMcpServer() {
|
|
|
551
548
|
};
|
|
552
549
|
});
|
|
553
550
|
server.tool("repair_test", "AI-powered test repair for broken tests", {
|
|
554
|
-
testName:
|
|
555
|
-
steps:
|
|
556
|
-
autoApply:
|
|
551
|
+
testName: z.string().describe("Name for the test"),
|
|
552
|
+
steps: z.array(z.string()).describe("Test step instructions"),
|
|
553
|
+
autoApply: z.boolean().optional().describe("Automatically apply repairs"),
|
|
557
554
|
}, async ({ testName, steps, autoApply }) => {
|
|
558
555
|
const testCase = {
|
|
559
556
|
name: testName,
|
|
@@ -562,7 +559,7 @@ async function startMcpServer() {
|
|
|
562
559
|
action: "unknown",
|
|
563
560
|
})),
|
|
564
561
|
};
|
|
565
|
-
const result = await
|
|
562
|
+
const result = await repairTest(testCase, { autoApply: autoApply || false });
|
|
566
563
|
return {
|
|
567
564
|
content: [
|
|
568
565
|
{
|
|
@@ -583,12 +580,12 @@ async function startMcpServer() {
|
|
|
583
580
|
};
|
|
584
581
|
});
|
|
585
582
|
server.tool("detect_flaky_tests", "Detect flaky/unreliable tests by running multiple times", {
|
|
586
|
-
testContent:
|
|
587
|
-
runs:
|
|
588
|
-
threshold:
|
|
583
|
+
testContent: z.string().describe("Test content to analyze"),
|
|
584
|
+
runs: z.number().optional().default(5).describe("Number of times to run each test"),
|
|
585
|
+
threshold: z.number().optional().default(20).describe("Flakiness threshold percentage"),
|
|
589
586
|
}, async ({ testContent, runs, threshold }) => {
|
|
590
|
-
const suite =
|
|
591
|
-
const result = await
|
|
587
|
+
const suite = parseNLTestSuite(testContent, "Flaky Test Analysis");
|
|
588
|
+
const result = await detectFlakyTests(suite, { runs, flakinessThreshold: threshold });
|
|
592
589
|
return {
|
|
593
590
|
content: [
|
|
594
591
|
{
|
|
@@ -612,11 +609,11 @@ async function startMcpServer() {
|
|
|
612
609
|
};
|
|
613
610
|
});
|
|
614
611
|
server.tool("coverage_map", "Generate test coverage map for a site", {
|
|
615
|
-
baseUrl:
|
|
616
|
-
testFiles:
|
|
617
|
-
maxPages:
|
|
612
|
+
baseUrl: z.string().url().describe("Base URL to analyze"),
|
|
613
|
+
testFiles: z.array(z.string()).describe("Array of test file paths"),
|
|
614
|
+
maxPages: z.number().optional().default(100).describe("Maximum pages to crawl"),
|
|
618
615
|
}, async ({ baseUrl, testFiles, maxPages }) => {
|
|
619
|
-
const result = await
|
|
616
|
+
const result = await generateCoverageMap(baseUrl, testFiles, { maxPages });
|
|
620
617
|
return {
|
|
621
618
|
content: [
|
|
622
619
|
{
|
|
@@ -640,12 +637,12 @@ async function startMcpServer() {
|
|
|
640
637
|
// Analysis Tools (v4.0.0+)
|
|
641
638
|
// =========================================================================
|
|
642
639
|
server.tool("hunt_bugs", "Autonomous bug hunting - crawl and find issues. Returns bugs with severity, selector, and actionable recommendation for each issue found.", {
|
|
643
|
-
url:
|
|
644
|
-
maxPages:
|
|
645
|
-
timeout:
|
|
640
|
+
url: z.string().url().describe("Starting URL to hunt from"),
|
|
641
|
+
maxPages: z.number().optional().default(10).describe("Maximum pages to visit"),
|
|
642
|
+
timeout: z.number().optional().default(60000).describe("Timeout in milliseconds"),
|
|
646
643
|
}, async ({ url, maxPages, timeout }) => {
|
|
647
644
|
const b = await getBrowser();
|
|
648
|
-
const result = await
|
|
645
|
+
const result = await huntBugs(b, url, { maxPages, timeout });
|
|
649
646
|
return {
|
|
650
647
|
content: [
|
|
651
648
|
{
|
|
@@ -668,13 +665,13 @@ async function startMcpServer() {
|
|
|
668
665
|
};
|
|
669
666
|
});
|
|
670
667
|
server.tool("chaos_test", "Inject failures and test resilience", {
|
|
671
|
-
url:
|
|
672
|
-
networkLatency:
|
|
673
|
-
offline:
|
|
674
|
-
blockUrls:
|
|
668
|
+
url: z.string().url().describe("URL to test"),
|
|
669
|
+
networkLatency: z.number().optional().describe("Simulate network latency (ms)"),
|
|
670
|
+
offline: z.boolean().optional().describe("Simulate offline mode"),
|
|
671
|
+
blockUrls: z.array(z.string()).optional().describe("URL patterns to block"),
|
|
675
672
|
}, async ({ url, networkLatency, offline, blockUrls }) => {
|
|
676
673
|
const b = await getBrowser();
|
|
677
|
-
const result = await
|
|
674
|
+
const result = await runChaosTest(b, url, { networkLatency, offline, blockUrls });
|
|
678
675
|
return {
|
|
679
676
|
content: [
|
|
680
677
|
{
|
|
@@ -689,11 +686,11 @@ async function startMcpServer() {
|
|
|
689
686
|
};
|
|
690
687
|
});
|
|
691
688
|
server.tool("compare_personas", "Compare how different user personas experience a journey", {
|
|
692
|
-
url:
|
|
693
|
-
goal:
|
|
694
|
-
personas:
|
|
689
|
+
url: z.string().url().describe("Starting URL"),
|
|
690
|
+
goal: z.string().describe("Goal to accomplish"),
|
|
691
|
+
personas: z.array(z.string()).describe("Persona names to compare"),
|
|
695
692
|
}, async ({ url, goal, personas }) => {
|
|
696
|
-
const result = await
|
|
693
|
+
const result = await comparePersonas({
|
|
697
694
|
startUrl: url,
|
|
698
695
|
goal,
|
|
699
696
|
personas,
|
|
@@ -713,11 +710,11 @@ async function startMcpServer() {
|
|
|
713
710
|
};
|
|
714
711
|
});
|
|
715
712
|
server.tool("find_element_by_intent", "AI-powered semantic element finding with ARIA-first selector strategy. Prioritizes aria-label > role > semantic HTML > ID > name > class. Returns selectorType, accessibilityScore (0-1), and alternatives. Use verbose=true for enriched failure responses.", {
|
|
716
|
-
intent:
|
|
717
|
-
verbose:
|
|
713
|
+
intent: z.string().describe("Natural language description like 'the cheapest product' or 'login form'"),
|
|
714
|
+
verbose: z.boolean().optional().describe("Include alternative matches with confidence scores and AI suggestions"),
|
|
718
715
|
}, async ({ intent, verbose }) => {
|
|
719
716
|
const b = await getBrowser();
|
|
720
|
-
const result = await
|
|
717
|
+
const result = await findElementByIntent(b, intent, { verbose });
|
|
721
718
|
if (result && result.confidence > 0) {
|
|
722
719
|
return {
|
|
723
720
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
@@ -732,25 +729,25 @@ async function startMcpServer() {
|
|
|
732
729
|
// Cognitive Simulation Tools (v8.3.0)
|
|
733
730
|
// =========================================================================
|
|
734
731
|
server.tool("cognitive_journey_init", "Initialize a cognitive user journey simulation. Returns the persona's cognitive profile, initial state, and abandonment thresholds. The actual simulation is driven by the LLM using browser tools (navigate, click, fill, screenshot) while tracking cognitive state.", {
|
|
735
|
-
persona:
|
|
736
|
-
goal:
|
|
737
|
-
startUrl:
|
|
738
|
-
customTraits:
|
|
739
|
-
patience:
|
|
740
|
-
riskTolerance:
|
|
741
|
-
comprehension:
|
|
742
|
-
persistence:
|
|
743
|
-
curiosity:
|
|
744
|
-
workingMemory:
|
|
745
|
-
readingTendency:
|
|
732
|
+
persona: z.string().describe("Persona name (e.g., 'first-timer', 'elderly-user', 'power-user') or custom description"),
|
|
733
|
+
goal: z.string().describe("What the simulated user is trying to accomplish"),
|
|
734
|
+
startUrl: z.string().url().describe("Starting URL for the journey"),
|
|
735
|
+
customTraits: z.object({
|
|
736
|
+
patience: z.number().min(0).max(1).optional(),
|
|
737
|
+
riskTolerance: z.number().min(0).max(1).optional(),
|
|
738
|
+
comprehension: z.number().min(0).max(1).optional(),
|
|
739
|
+
persistence: z.number().min(0).max(1).optional(),
|
|
740
|
+
curiosity: z.number().min(0).max(1).optional(),
|
|
741
|
+
workingMemory: z.number().min(0).max(1).optional(),
|
|
742
|
+
readingTendency: z.number().min(0).max(1).optional(),
|
|
746
743
|
}).optional().describe("Override specific cognitive traits"),
|
|
747
744
|
}, async ({ persona: personaName, goal, startUrl, customTraits }) => {
|
|
748
745
|
// Get or create persona
|
|
749
|
-
const existingPersona =
|
|
746
|
+
const existingPersona = getPersona(personaName);
|
|
750
747
|
let personaObj;
|
|
751
748
|
if (!existingPersona) {
|
|
752
749
|
// Create from description
|
|
753
|
-
personaObj =
|
|
750
|
+
personaObj = createCognitivePersona(personaName, personaName, customTraits || {});
|
|
754
751
|
}
|
|
755
752
|
else if (customTraits) {
|
|
756
753
|
// Merge custom traits with defaults
|
|
@@ -776,7 +773,7 @@ async function startMcpServer() {
|
|
|
776
773
|
personaObj = existingPersona;
|
|
777
774
|
}
|
|
778
775
|
// Get cognitive profile
|
|
779
|
-
const profile =
|
|
776
|
+
const profile = getCognitiveProfile(personaObj);
|
|
780
777
|
// Initial cognitive state
|
|
781
778
|
const initialState = {
|
|
782
779
|
patienceRemaining: 1.0,
|
|
@@ -869,27 +866,27 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
869
866
|
};
|
|
870
867
|
});
|
|
871
868
|
server.tool("cognitive_journey_update_state", "Update the cognitive state during a journey simulation. Call this after each action to track mental state.", {
|
|
872
|
-
currentState:
|
|
873
|
-
patienceRemaining:
|
|
874
|
-
confusionLevel:
|
|
875
|
-
frustrationLevel:
|
|
876
|
-
goalProgress:
|
|
877
|
-
confidenceLevel:
|
|
878
|
-
currentMood:
|
|
879
|
-
stepCount:
|
|
880
|
-
timeElapsed:
|
|
869
|
+
currentState: z.object({
|
|
870
|
+
patienceRemaining: z.number(),
|
|
871
|
+
confusionLevel: z.number(),
|
|
872
|
+
frustrationLevel: z.number(),
|
|
873
|
+
goalProgress: z.number(),
|
|
874
|
+
confidenceLevel: z.number(),
|
|
875
|
+
currentMood: z.enum(["neutral", "hopeful", "confused", "frustrated", "defeated", "relieved"]),
|
|
876
|
+
stepCount: z.number(),
|
|
877
|
+
timeElapsed: z.number(),
|
|
881
878
|
}).describe("Current cognitive state"),
|
|
882
|
-
actionResult:
|
|
883
|
-
success:
|
|
884
|
-
wasConfusing:
|
|
885
|
-
progressMade:
|
|
886
|
-
wentBack:
|
|
879
|
+
actionResult: z.object({
|
|
880
|
+
success: z.boolean(),
|
|
881
|
+
wasConfusing: z.boolean().optional(),
|
|
882
|
+
progressMade: z.boolean().optional(),
|
|
883
|
+
wentBack: z.boolean().optional(),
|
|
887
884
|
}).describe("Result of the last action"),
|
|
888
|
-
personaTraits:
|
|
889
|
-
patience:
|
|
890
|
-
riskTolerance:
|
|
891
|
-
comprehension:
|
|
892
|
-
persistence:
|
|
885
|
+
personaTraits: z.object({
|
|
886
|
+
patience: z.number(),
|
|
887
|
+
riskTolerance: z.number(),
|
|
888
|
+
comprehension: z.number(),
|
|
889
|
+
persistence: z.number(),
|
|
893
890
|
}).describe("Persona traits affecting state changes"),
|
|
894
891
|
}, async ({ currentState, actionResult, personaTraits }) => {
|
|
895
892
|
// Calculate new state based on action result
|
|
@@ -979,12 +976,12 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
979
976
|
};
|
|
980
977
|
});
|
|
981
978
|
server.tool("list_cognitive_personas", "List all available personas with their cognitive traits", {}, async () => {
|
|
982
|
-
const names =
|
|
979
|
+
const names = listPersonas();
|
|
983
980
|
const personas = names.map(name => {
|
|
984
|
-
const p =
|
|
981
|
+
const p = getPersona(name);
|
|
985
982
|
if (!p)
|
|
986
983
|
return null;
|
|
987
|
-
const profile =
|
|
984
|
+
const profile = getCognitiveProfile(p);
|
|
988
985
|
return {
|
|
989
986
|
name: p.name,
|
|
990
987
|
description: p.description,
|
|
@@ -1007,11 +1004,11 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
1007
1004
|
// Performance Tools (v6.4.0+)
|
|
1008
1005
|
// =========================================================================
|
|
1009
1006
|
server.tool("perf_baseline", "Capture performance baseline for a URL", {
|
|
1010
|
-
url:
|
|
1011
|
-
name:
|
|
1012
|
-
runs:
|
|
1007
|
+
url: z.string().url().describe("URL to capture baseline for"),
|
|
1008
|
+
name: z.string().describe("Name for the baseline"),
|
|
1009
|
+
runs: z.number().optional().default(3).describe("Number of runs to average"),
|
|
1013
1010
|
}, async ({ url, name, runs }) => {
|
|
1014
|
-
const result = await
|
|
1011
|
+
const result = await capturePerformanceBaseline(url, { name, runs });
|
|
1015
1012
|
return {
|
|
1016
1013
|
content: [
|
|
1017
1014
|
{
|
|
@@ -1029,12 +1026,12 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
1029
1026
|
};
|
|
1030
1027
|
});
|
|
1031
1028
|
server.tool("perf_regression", "Detect performance regression against baseline with configurable sensitivity. Uses dual thresholds: both percentage AND absolute change must be exceeded. Profiles: strict (CI/CD, FCP 10%/50ms), normal (default, FCP 20%/100ms), lenient (dev, FCP 30%/200ms). Sub-50ms FCP variations ignored by default.", {
|
|
1032
|
-
url:
|
|
1033
|
-
baselineName:
|
|
1034
|
-
sensitivity:
|
|
1035
|
-
thresholdLcp:
|
|
1029
|
+
url: z.string().url().describe("URL to test"),
|
|
1030
|
+
baselineName: z.string().describe("Name of baseline to compare against"),
|
|
1031
|
+
sensitivity: z.enum(["strict", "normal", "lenient"]).optional().default("normal").describe("Sensitivity profile: strict (CI/CD), normal (default), lenient (development)"),
|
|
1032
|
+
thresholdLcp: z.number().optional().describe("Override LCP threshold percentage"),
|
|
1036
1033
|
}, async ({ url, baselineName, sensitivity, thresholdLcp }) => {
|
|
1037
|
-
const result = await
|
|
1034
|
+
const result = await detectPerformanceRegression(url, baselineName, {
|
|
1038
1035
|
sensitivity,
|
|
1039
1036
|
thresholds: thresholdLcp ? { lcp: thresholdLcp } : undefined,
|
|
1040
1037
|
});
|
|
@@ -1055,8 +1052,8 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
1055
1052
|
};
|
|
1056
1053
|
});
|
|
1057
1054
|
server.tool("list_baselines", "List all saved baselines (visual and performance)", {}, async () => {
|
|
1058
|
-
const visualBaselines = await
|
|
1059
|
-
const perfBaselines = await
|
|
1055
|
+
const visualBaselines = await listVisualBaselines();
|
|
1056
|
+
const perfBaselines = await listPerformanceBaselines();
|
|
1060
1057
|
return {
|
|
1061
1058
|
content: [
|
|
1062
1059
|
{
|
|
@@ -1073,7 +1070,7 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
1073
1070
|
// Diagnostics Tools
|
|
1074
1071
|
// =========================================================================
|
|
1075
1072
|
server.tool("status", "Get CBrowser environment status and diagnostics including data directories, installed browsers, configuration, and self-healing cache statistics", {}, async () => {
|
|
1076
|
-
const info = await
|
|
1073
|
+
const info = await getStatusInfo(VERSION);
|
|
1077
1074
|
return {
|
|
1078
1075
|
content: [
|
|
1079
1076
|
{
|
|
@@ -1084,7 +1081,7 @@ Begin the simulation now. Narrate your thoughts as this persona.
|
|
|
1084
1081
|
};
|
|
1085
1082
|
});
|
|
1086
1083
|
// Connect via stdio transport
|
|
1087
|
-
const transport = new
|
|
1084
|
+
const transport = new StdioServerTransport();
|
|
1088
1085
|
await server.connect(transport);
|
|
1089
1086
|
// Handle shutdown
|
|
1090
1087
|
process.on("SIGINT", async () => {
|