@nyx-intelligence/val-mcp 0.5.1 → 0.5.4
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/dist/index.js +1 -1
- package/dist/session.js +53 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -66,7 +66,7 @@ async function runServer() {
|
|
|
66
66
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
67
67
|
const { chromium } = await import("playwright");
|
|
68
68
|
const { z } = await import("zod");
|
|
69
|
-
const server = new McpServer({ name: "val", version: "0.5.
|
|
69
|
+
const server = new McpServer({ name: "val", version: "0.5.4" });
|
|
70
70
|
const text = (t) => ({ content: [{ type: "text", text: t }] });
|
|
71
71
|
// ── Passive crawl ──
|
|
72
72
|
server.registerTool("val_scan", {
|
package/dist/session.js
CHANGED
|
@@ -195,13 +195,41 @@ class ValSession {
|
|
|
195
195
|
const startUrl = page.url();
|
|
196
196
|
for (const b of buttons) {
|
|
197
197
|
this.drain();
|
|
198
|
-
const before = await this.sig();
|
|
199
198
|
try {
|
|
199
|
+
const before = await this.sig();
|
|
200
200
|
const loc = page.locator(`[data-val-ex="${b.ref}"]`);
|
|
201
201
|
if ((await loc.count()) === 0)
|
|
202
202
|
continue;
|
|
203
|
+
// Install mutation watcher BEFORE the click. The handler can mutate
|
|
204
|
+
// the DOM synchronously and finish before we start observing — we'd
|
|
205
|
+
// then wait 800ms for nothing and falsely flag the button dead.
|
|
206
|
+
// Watch ANY mutation: node added/removed (modal, dropdown), attribute
|
|
207
|
+
// changed (`value=`, `style`, `aria-*` — React preset buttons), or
|
|
208
|
+
// text changed (counters, totals). Anything = the click did something.
|
|
209
|
+
await page.evaluate(() => {
|
|
210
|
+
const w = window;
|
|
211
|
+
w.__valMut = false;
|
|
212
|
+
const obs = new MutationObserver(() => {
|
|
213
|
+
w.__valMut = true;
|
|
214
|
+
});
|
|
215
|
+
obs.observe(document.body, {
|
|
216
|
+
childList: true,
|
|
217
|
+
subtree: true,
|
|
218
|
+
attributes: true,
|
|
219
|
+
characterData: true,
|
|
220
|
+
});
|
|
221
|
+
w.__valObs = obs;
|
|
222
|
+
});
|
|
203
223
|
await loc.click({ timeout: 4000 });
|
|
204
|
-
await page.waitForTimeout(
|
|
224
|
+
await page.waitForTimeout(800);
|
|
225
|
+
const mutated = await page
|
|
226
|
+
.evaluate(() => {
|
|
227
|
+
const w = window;
|
|
228
|
+
const m = !!w.__valMut;
|
|
229
|
+
w.__valObs?.disconnect();
|
|
230
|
+
return m;
|
|
231
|
+
})
|
|
232
|
+
.catch(() => false);
|
|
205
233
|
if (page.url() !== startUrl) {
|
|
206
234
|
await page.goBack({ waitUntil: "load" }).catch(() => { });
|
|
207
235
|
await page.waitForTimeout(300);
|
|
@@ -211,14 +239,35 @@ class ValSession {
|
|
|
211
239
|
const after = await this.sig();
|
|
212
240
|
const errs = this.drain();
|
|
213
241
|
const label = b.text || b.ref;
|
|
214
|
-
const
|
|
242
|
+
const sigChanged = after.nodes !== before.nodes || Math.abs(after.text - before.text) > 3;
|
|
243
|
+
const moved = mutated || sigChanged;
|
|
215
244
|
if (errs.newPageErrors.length)
|
|
216
245
|
errored.push({ el: label, error: errs.newPageErrors[0] });
|
|
217
246
|
else if (!moved && errs.newConsoleErrors.length === 0 && errs.newNetworkErrors.length === 0)
|
|
218
247
|
dead.push(label);
|
|
219
248
|
}
|
|
220
249
|
catch (e) {
|
|
221
|
-
|
|
250
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
251
|
+
// SPA navigations can destroy our eval context mid-probe — recover silently,
|
|
252
|
+
// don't flag the click as an error of the page under test.
|
|
253
|
+
if (/Execution context was destroyed|Target closed|frame got detached|Navigation/i.test(msg)) {
|
|
254
|
+
try {
|
|
255
|
+
if (page.url() !== startUrl) {
|
|
256
|
+
await page.goBack({ waitUntil: "load" }).catch(() => { });
|
|
257
|
+
await page.waitForTimeout(300);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
await page.waitForLoadState("load", { timeout: 2000 }).catch(() => { });
|
|
261
|
+
}
|
|
262
|
+
await tag();
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
/* nothing more we can do */
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
errored.push({ el: b.text || b.ref, error: msg });
|
|
270
|
+
}
|
|
222
271
|
}
|
|
223
272
|
}
|
|
224
273
|
return { tested: buttons.length, dead, errored };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyx-intelligence/val-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Val — a 100% MCP QA agent for vibecoders. Drives a real browser to catch UX bugs (broken links, 404s, console errors, broken images) so your coding agent can fix them.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|