@muggleai/works 5.20.0-staging.135 → 5.20.0-staging.137
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/{chunk-4M3P45WX.js → chunk-BA6EYJRB.js} +140 -80
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
|
@@ -354,15 +354,37 @@ function parentIsAlive(pid) {
|
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
// src/cli/pr-section/
|
|
357
|
+
// src/cli/pr-section/constants.ts
|
|
358
358
|
var DASHBOARD_URL_BASE = "https://www.muggle-ai.com/muggleTestV0/dashboard/projects";
|
|
359
359
|
var DETAIL_IMAGE_WIDTH = 720;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
360
|
+
var STEP_LIST_ELISION_THRESHOLD = 10;
|
|
361
|
+
var STEP_LIST_HEAD_COUNT = 6;
|
|
362
|
+
var STEP_LIST_TAIL_COUNT = 3;
|
|
363
|
+
|
|
364
|
+
// src/cli/pr-section/render.ts
|
|
365
|
+
function resolveLatestRuns(tests) {
|
|
366
|
+
const byCaseId = /* @__PURE__ */ new Map();
|
|
367
|
+
const order = [];
|
|
368
|
+
for (const test of tests) {
|
|
369
|
+
const existing = byCaseId.get(test.testCaseId);
|
|
370
|
+
if (existing) {
|
|
371
|
+
existing.test = test;
|
|
372
|
+
existing.attempts += 1;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
byCaseId.set(test.testCaseId, { test, attempts: 1 });
|
|
376
|
+
order.push(test.testCaseId);
|
|
377
|
+
}
|
|
378
|
+
return order.map((id) => byCaseId.get(id));
|
|
379
|
+
}
|
|
380
|
+
function countTests(latestRuns) {
|
|
381
|
+
const statuses = latestRuns.map((entry) => entry.test.status);
|
|
382
|
+
return {
|
|
383
|
+
total: statuses.length,
|
|
384
|
+
passed: statuses.filter((s) => s === "passed").length,
|
|
385
|
+
failed: statuses.filter((s) => s === "failed").length,
|
|
386
|
+
inconclusive: statuses.filter((s) => s === "inconclusive").length
|
|
387
|
+
};
|
|
366
388
|
}
|
|
367
389
|
function statusEmoji(test) {
|
|
368
390
|
if (test.status === "passed") return "\u2705";
|
|
@@ -370,23 +392,30 @@ function statusEmoji(test) {
|
|
|
370
392
|
return "\u26A0\uFE0F";
|
|
371
393
|
}
|
|
372
394
|
function computeVerdict(report) {
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
if (
|
|
395
|
+
const latestRuns = resolveLatestRuns(report.tests);
|
|
396
|
+
if (latestRuns.length === 0) return "none";
|
|
397
|
+
if (latestRuns.some((entry) => entry.test.status === "failed")) return "fail";
|
|
398
|
+
if (latestRuns.some((entry) => entry.test.status === "inconclusive")) return "inconclusive";
|
|
376
399
|
return "pass";
|
|
377
400
|
}
|
|
378
|
-
function
|
|
401
|
+
function verdictLabel(verdict) {
|
|
379
402
|
switch (verdict) {
|
|
380
403
|
case "pass":
|
|
381
|
-
return "
|
|
404
|
+
return "\u2705 PASS";
|
|
382
405
|
case "fail":
|
|
383
|
-
return "
|
|
406
|
+
return "\u274C FAIL";
|
|
384
407
|
case "inconclusive":
|
|
385
|
-
return "
|
|
408
|
+
return "\u26A0\uFE0F INCONCLUSIVE";
|
|
386
409
|
case "none":
|
|
387
410
|
return null;
|
|
388
411
|
}
|
|
389
412
|
}
|
|
413
|
+
function renderSummaryHeadline(verdict, counts) {
|
|
414
|
+
const { total, passed, failed, inconclusive } = counts;
|
|
415
|
+
const tally = `${total} ${total === 1 ? "test" : "tests"} ran \xB7 ${passed} passed / ${failed} failed / ${inconclusive} inconclusive`;
|
|
416
|
+
const label = verdictLabel(verdict);
|
|
417
|
+
return label ? `**${label}** \u2014 ${tally}` : `**${tally}**`;
|
|
418
|
+
}
|
|
390
419
|
function endingScreenshot(test) {
|
|
391
420
|
if (test.steps.length === 0) {
|
|
392
421
|
return null;
|
|
@@ -400,9 +429,13 @@ function endingScreenshot(test) {
|
|
|
400
429
|
}
|
|
401
430
|
return test.steps[test.steps.length - 1];
|
|
402
431
|
}
|
|
432
|
+
function failureOrdinal(test, failureStepIndex) {
|
|
433
|
+
const position = test.steps.findIndex((s) => s.stepIndex === failureStepIndex);
|
|
434
|
+
return position === -1 ? failureStepIndex : position + 1;
|
|
435
|
+
}
|
|
403
436
|
function defaultEndingCaption(test) {
|
|
404
437
|
if (test.status === "failed") {
|
|
405
|
-
return `Failure at step ${test.failureStepIndex}`;
|
|
438
|
+
return `Failure at step ${failureOrdinal(test, test.failureStepIndex)}`;
|
|
406
439
|
}
|
|
407
440
|
if (test.status === "inconclusive") {
|
|
408
441
|
return "Last frame before run was cut short";
|
|
@@ -431,88 +464,93 @@ function fullSizeImage(url, alt) {
|
|
|
431
464
|
function safeInlineCode(s) {
|
|
432
465
|
return s.replace(/`/g, "\u2018");
|
|
433
466
|
}
|
|
434
|
-
function buildTestNumbering(report) {
|
|
435
|
-
const map = /* @__PURE__ */ new Map();
|
|
436
|
-
report.tests.forEach((t, i) => {
|
|
437
|
-
map.set(t, i + 1);
|
|
438
|
-
});
|
|
439
|
-
return map;
|
|
440
|
-
}
|
|
441
467
|
function renderOverview(report) {
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
468
|
+
const latestRuns = resolveLatestRuns(report.tests);
|
|
469
|
+
const counts = countTests(latestRuns);
|
|
470
|
+
const lines = [
|
|
471
|
+
"## E2E Acceptance Results",
|
|
472
|
+
"",
|
|
473
|
+
"### Summary",
|
|
474
|
+
"",
|
|
475
|
+
renderSummaryHeadline(computeVerdict(report), counts)
|
|
476
|
+
];
|
|
477
|
+
if (counts.total === 0) {
|
|
451
478
|
lines.push("", "_No tests were executed._");
|
|
452
479
|
return lines.join("\n");
|
|
453
480
|
}
|
|
454
|
-
lines.push(""
|
|
455
|
-
const numbering = buildTestNumbering(
|
|
456
|
-
const anyGrouped =
|
|
481
|
+
lines.push("");
|
|
482
|
+
const numbering = buildTestNumbering(latestRuns);
|
|
483
|
+
const anyGrouped = latestRuns.some((entry) => Boolean(entry.test.useCaseName));
|
|
457
484
|
if (!anyGrouped) {
|
|
458
|
-
for (const
|
|
459
|
-
lines.push(
|
|
485
|
+
for (const entry of latestRuns) {
|
|
486
|
+
lines.push(renderOverviewBullet(entry, numbering, ""));
|
|
460
487
|
}
|
|
461
488
|
return lines.join("\n");
|
|
462
489
|
}
|
|
463
|
-
const
|
|
464
|
-
const groups = /* @__PURE__ */ new Map();
|
|
465
|
-
const flat = [];
|
|
466
|
-
const seenGroups = /* @__PURE__ */ new Set();
|
|
467
|
-
for (const t of report.tests) {
|
|
468
|
-
if (t.useCaseName) {
|
|
469
|
-
if (!groups.has(t.useCaseName)) {
|
|
470
|
-
groups.set(t.useCaseName, []);
|
|
471
|
-
groupOrder.push(t.useCaseName);
|
|
472
|
-
}
|
|
473
|
-
groups.get(t.useCaseName).push(t);
|
|
474
|
-
if (!seenGroups.has(t.useCaseName)) {
|
|
475
|
-
seenGroups.add(t.useCaseName);
|
|
476
|
-
flat.push({ type: "group", key: t.useCaseName });
|
|
477
|
-
}
|
|
478
|
-
} else {
|
|
479
|
-
flat.push({ type: "test", test: t });
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
for (const entry of flat) {
|
|
490
|
+
for (const entry of buildGroupedLayout(latestRuns)) {
|
|
483
491
|
if (entry.type === "test") {
|
|
484
|
-
lines.push(
|
|
492
|
+
lines.push(renderOverviewBullet(entry.entry, numbering, ""));
|
|
485
493
|
} else {
|
|
486
494
|
lines.push(`- **${entry.key}**`);
|
|
487
|
-
for (const
|
|
488
|
-
lines.push(
|
|
495
|
+
for (const member of entry.members) {
|
|
496
|
+
lines.push(renderOverviewBullet(member, numbering, " "));
|
|
489
497
|
}
|
|
490
498
|
}
|
|
491
499
|
}
|
|
492
500
|
return lines.join("\n");
|
|
493
501
|
}
|
|
494
|
-
function
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
502
|
+
function buildTestNumbering(latestRuns) {
|
|
503
|
+
const map = /* @__PURE__ */ new Map();
|
|
504
|
+
latestRuns.forEach((entry, i) => {
|
|
505
|
+
map.set(entry.test.testCaseId, i + 1);
|
|
506
|
+
});
|
|
507
|
+
return map;
|
|
508
|
+
}
|
|
509
|
+
function renderOverviewBullet(entry, numbering, indent) {
|
|
510
|
+
const rerunNote = entry.attempts > 1 ? ` _(${entry.attempts} runs)_` : "";
|
|
511
|
+
return `${indent}- ${statusEmoji(entry.test)} **${numbering.get(entry.test.testCaseId)}.** ${entry.test.name}${rerunNote}`;
|
|
512
|
+
}
|
|
513
|
+
function buildGroupedLayout(latestRuns) {
|
|
514
|
+
const groups = /* @__PURE__ */ new Map();
|
|
515
|
+
const layout = [];
|
|
516
|
+
for (const entry of latestRuns) {
|
|
517
|
+
const useCaseName = entry.test.useCaseName;
|
|
518
|
+
if (!useCaseName) {
|
|
519
|
+
layout.push({ type: "test", entry });
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const existing = groups.get(useCaseName);
|
|
523
|
+
if (existing) {
|
|
524
|
+
existing.push(entry);
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const members = [entry];
|
|
528
|
+
groups.set(useCaseName, members);
|
|
529
|
+
layout.push({ type: "group", key: useCaseName, members });
|
|
530
|
+
}
|
|
531
|
+
return layout;
|
|
532
|
+
}
|
|
533
|
+
function renderTestDetails(entry, projectId, testNumber, dashboardBaseUrl = DASHBOARD_URL_BASE) {
|
|
534
|
+
const { test } = entry;
|
|
498
535
|
const body = ["", "<br>", ""];
|
|
536
|
+
body.push(...renderResultSummary(entry, projectId, dashboardBaseUrl));
|
|
537
|
+
const stepList = renderStepList(test.steps);
|
|
538
|
+
if (stepList) {
|
|
539
|
+
body.push("", ...stepList);
|
|
540
|
+
}
|
|
541
|
+
const frameBlock = renderEndingFrame(test);
|
|
499
542
|
if (frameBlock) {
|
|
500
|
-
body.push(...frameBlock
|
|
543
|
+
body.push("", ...frameBlock);
|
|
501
544
|
}
|
|
502
|
-
body.push(...resultLines);
|
|
503
545
|
return `<details>
|
|
504
|
-
<summary>${
|
|
546
|
+
<summary>${renderSummaryLine(test, testNumber)}</summary>
|
|
505
547
|
${body.join("\n")}
|
|
506
548
|
|
|
507
549
|
</details>`;
|
|
508
550
|
}
|
|
509
551
|
function renderSummaryLine(test, testNumber) {
|
|
510
|
-
const base =
|
|
511
|
-
|
|
512
|
-
if (test.description) {
|
|
513
|
-
return `<b>${testNumber}. </b>${head}${base} \u2014 ${test.description}`;
|
|
514
|
-
}
|
|
515
|
-
return `<b>${testNumber}. </b>${head}${base}`;
|
|
552
|
+
const base = `${statusEmoji(test)} <b>${testNumber}. ${test.name}</b>`;
|
|
553
|
+
return test.description ? `${base} \u2014 ${test.description}` : base;
|
|
516
554
|
}
|
|
517
555
|
function renderEndingFrame(test) {
|
|
518
556
|
const frame = endingFrame(test);
|
|
@@ -525,22 +563,42 @@ function renderEndingFrame(test) {
|
|
|
525
563
|
fullSizeImage(frame.url, test.name)
|
|
526
564
|
];
|
|
527
565
|
}
|
|
528
|
-
function
|
|
566
|
+
function renderStepList(steps) {
|
|
567
|
+
if (steps.length === 0) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
const entry = (step, position) => `${position}. ${safeInlineCode(step.action)}`;
|
|
571
|
+
if (steps.length <= STEP_LIST_ELISION_THRESHOLD) {
|
|
572
|
+
return steps.map((step, i) => entry(step, i + 1));
|
|
573
|
+
}
|
|
574
|
+
const head = steps.slice(0, STEP_LIST_HEAD_COUNT).map((step, i) => entry(step, i + 1));
|
|
575
|
+
const tailStart = steps.length - STEP_LIST_TAIL_COUNT;
|
|
576
|
+
const tail = steps.slice(tailStart).map((step, i) => entry(step, tailStart + i + 1));
|
|
577
|
+
const hidden = tailStart - STEP_LIST_HEAD_COUNT;
|
|
578
|
+
return [...head, "", `_\u2026 ${hidden} more steps \u2026_`, "", ...tail];
|
|
579
|
+
}
|
|
580
|
+
function renderResultSummary(entry, projectId, dashboardBaseUrl) {
|
|
581
|
+
const { test } = entry;
|
|
529
582
|
const dashboardUrl = `${dashboardBaseUrl}/${projectId}/scripts?modal=script-details&testCaseId=${encodeURIComponent(test.testCaseId)}`;
|
|
530
583
|
const lines = [];
|
|
531
584
|
if (test.status === "passed") {
|
|
532
585
|
lines.push(`**Result:** \u2705 PASSED`);
|
|
533
586
|
} else if (test.status === "failed") {
|
|
534
|
-
lines.push(`**Result:** \u274C FAILED at step ${test.failureStepIndex}`);
|
|
587
|
+
lines.push(`**Result:** \u274C FAILED at step ${failureOrdinal(test, test.failureStepIndex)}`);
|
|
535
588
|
lines.push(`**Error:** \`${safeInlineCode(test.error)}\``);
|
|
536
589
|
} else {
|
|
537
590
|
lines.push(`**Result:** \u26A0\uFE0F INCONCLUSIVE`);
|
|
538
591
|
lines.push(`**Reason:** \`${safeInlineCode(test.reason)}\``);
|
|
539
592
|
}
|
|
540
|
-
|
|
541
|
-
|
|
593
|
+
if (entry.attempts > 1) {
|
|
594
|
+
lines.push(`**Runs:** ${entry.attempts} \u2014 showing the latest`);
|
|
595
|
+
}
|
|
596
|
+
lines.push(`**Steps:** ${test.steps.length} \xB7 <a href="${dashboardUrl}" target="_blank" rel="noopener noreferrer">View steps on Muggle AI \u2192</a>`);
|
|
542
597
|
return lines;
|
|
543
598
|
}
|
|
599
|
+
function renderDetailBlocks(report, dashboardBaseUrl) {
|
|
600
|
+
return resolveLatestRuns(report.tests).map((entry, i) => renderTestDetails(entry, report.projectId, i + 1, dashboardBaseUrl ?? DASHBOARD_URL_BASE)).join("\n\n");
|
|
601
|
+
}
|
|
544
602
|
function renderBody(report, opts) {
|
|
545
603
|
const overview = renderOverview(report);
|
|
546
604
|
if (report.tests.length === 0) {
|
|
@@ -555,26 +613,28 @@ function renderBody(report, opts) {
|
|
|
555
613
|
"_Full per-test details in the comment below \u2014 the PR description was too large to inline them._"
|
|
556
614
|
].join("\n");
|
|
557
615
|
}
|
|
558
|
-
const detailBlocks = report.tests.map((t, i) => renderTestDetails(t, report.projectId, i + 1, opts.dashboardBaseUrl ?? DASHBOARD_URL_BASE));
|
|
559
616
|
return [
|
|
560
617
|
overview,
|
|
561
618
|
"",
|
|
562
619
|
"---",
|
|
563
620
|
"",
|
|
564
|
-
|
|
621
|
+
"### Test details",
|
|
622
|
+
"",
|
|
623
|
+
renderDetailBlocks(report, opts.dashboardBaseUrl)
|
|
565
624
|
].join("\n");
|
|
566
625
|
}
|
|
567
626
|
function renderComment(report, opts = {}) {
|
|
568
627
|
if (report.tests.length === 0) {
|
|
569
628
|
return "";
|
|
570
629
|
}
|
|
571
|
-
const detailBlocks = report.tests.map((t, i) => renderTestDetails(t, report.projectId, i + 1, opts.dashboardBaseUrl ?? DASHBOARD_URL_BASE));
|
|
572
630
|
return [
|
|
573
631
|
"## E2E acceptance evidence (overflow)",
|
|
574
632
|
"",
|
|
575
633
|
"_This comment was posted because the full per-test details did not fit in the PR description._",
|
|
576
634
|
"",
|
|
577
|
-
|
|
635
|
+
"### Test details",
|
|
636
|
+
"",
|
|
637
|
+
renderDetailBlocks(report, opts.dashboardBaseUrl)
|
|
578
638
|
].join("\n");
|
|
579
639
|
}
|
|
580
640
|
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
1
|
+
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-BA6EYJRB.js';
|
|
2
2
|
export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-ZA5KHVX5.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "5.20.0-staging.
|
|
4
|
+
"version": "5.20.0-staging.137",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "5.20.0-staging.
|
|
5
|
+
"version": "5.20.0-staging.137",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"release": "5.18.0",
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-09-
|
|
3
|
+
"buildId": "run-137-1",
|
|
4
|
+
"commitSha": "0ac93fb98732cc7c350e2269b19b552438f0fced",
|
|
5
|
+
"buildTime": "2026-09-24T18:14:39Z",
|
|
6
6
|
"serviceName": "muggle-ai-works-mcp"
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muggleai/works",
|
|
3
3
|
"mcpName": "io.github.multiplex-ai/muggle",
|
|
4
|
-
"version": "5.20.0-staging.
|
|
4
|
+
"version": "5.20.0-staging.137",
|
|
5
5
|
"description": "Ship quality products with AI-powered E2E acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser end-to-end (E2E) acceptance tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "5.20.0-staging.
|
|
4
|
+
"version": "5.20.0-staging.137",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered end-to-end (E2E) acceptance testing that validates your web app like a real user — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "5.20.0-staging.
|
|
5
|
+
"version": "5.20.0-staging.137",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|