@muggleai/works 4.6.1 → 4.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.
@@ -356,12 +356,41 @@ function endingScreenshot(test) {
356
356
  }
357
357
  return test.steps[test.steps.length - 1];
358
358
  }
359
+ function defaultEndingCaption(test) {
360
+ if (test.status === "failed") {
361
+ return `Failure at step ${test.failureStepIndex}`;
362
+ }
363
+ return "Final page after the test completed";
364
+ }
365
+ function endingFrame(test) {
366
+ if (test.endingScreenshotUrl) {
367
+ return {
368
+ url: test.endingScreenshotUrl,
369
+ caption: test.endingScreenshotCaption ?? defaultEndingCaption(test)
370
+ };
371
+ }
372
+ const step = endingScreenshot(test);
373
+ if (!step) {
374
+ return null;
375
+ }
376
+ return {
377
+ url: step.screenshotUrl,
378
+ caption: test.endingScreenshotCaption ?? defaultEndingCaption(test)
379
+ };
380
+ }
359
381
  function fullSizeImage(url, alt) {
360
382
  return `<a href="${url}"><img src="${url}" width="${DETAIL_IMAGE_WIDTH}" alt="${alt}"></a>`;
361
383
  }
362
384
  function safeInlineCode(s) {
363
385
  return s.replace(/`/g, "\u2018");
364
386
  }
387
+ function buildTestNumbering(report) {
388
+ const map = /* @__PURE__ */ new Map();
389
+ report.tests.forEach((t, i) => {
390
+ map.set(t.testCaseId, i + 1);
391
+ });
392
+ return map;
393
+ }
365
394
  function renderOverview(report) {
366
395
  const { total, passed, failed } = countTests(report);
367
396
  const lines = [
@@ -374,10 +403,11 @@ function renderOverview(report) {
374
403
  return lines.join("\n");
375
404
  }
376
405
  lines.push("", "**Tests run:**");
406
+ const numbering = buildTestNumbering(report);
377
407
  const anyGrouped = report.tests.some((t) => Boolean(t.useCaseName));
378
408
  if (!anyGrouped) {
379
409
  for (const t of report.tests) {
380
- lines.push(`- ${statusEmoji(t)} ${t.name}`);
410
+ lines.push(`- **${numbering.get(t.testCaseId)}.** ${statusEmoji(t)} ${t.name}`);
381
411
  }
382
412
  return lines.join("\n");
383
413
  }
@@ -402,23 +432,23 @@ function renderOverview(report) {
402
432
  }
403
433
  for (const entry of flat) {
404
434
  if (entry.type === "test") {
405
- lines.push(`- ${statusEmoji(entry.test)} ${entry.test.name}`);
435
+ lines.push(`- **${numbering.get(entry.test.testCaseId)}.** ${statusEmoji(entry.test)} ${entry.test.name}`);
406
436
  } else {
407
437
  lines.push(`- **${entry.key}**`);
408
438
  for (const t of groups.get(entry.key)) {
409
- lines.push(` - ${statusEmoji(t)} ${t.name}`);
439
+ lines.push(` - **${numbering.get(t.testCaseId)}.** ${statusEmoji(t)} ${t.name}`);
410
440
  }
411
441
  }
412
442
  }
413
443
  return lines.join("\n");
414
444
  }
415
- function renderTestDetails(test, projectId) {
416
- const summary = renderSummaryLine(test);
417
- const image = renderEndingImage(test);
445
+ function renderTestDetails(test, projectId, testNumber) {
446
+ const summary = renderSummaryLine(test, testNumber);
447
+ const frameBlock = renderEndingFrame(test);
418
448
  const resultLines = renderResultSummary(test, projectId);
419
449
  const body = ["", "<br>", ""];
420
- if (image) {
421
- body.push(image, "");
450
+ if (frameBlock) {
451
+ body.push(...frameBlock, "");
422
452
  }
423
453
  body.push(...resultLines);
424
454
  return `<details>
@@ -427,20 +457,24 @@ ${body.join("\n")}
427
457
 
428
458
  </details>`;
429
459
  }
430
- function renderSummaryLine(test) {
431
- const base = `${statusEmoji(test)} <b>${test.name}</b>`;
460
+ function renderSummaryLine(test, testNumber) {
461
+ const base = `<b>${testNumber}. ${test.name}</b> ${statusEmoji(test)}`;
432
462
  const tail = " <i>\u25B6 click to expand</i>";
433
463
  if (test.description) {
434
464
  return `${base} \u2014 ${test.description}${tail}`;
435
465
  }
436
466
  return `${base}${tail}`;
437
467
  }
438
- function renderEndingImage(test) {
439
- const step = endingScreenshot(test);
440
- if (!step) {
468
+ function renderEndingFrame(test) {
469
+ const frame = endingFrame(test);
470
+ if (!frame) {
441
471
  return null;
442
472
  }
443
- return fullSizeImage(step.screenshotUrl, test.name);
473
+ return [
474
+ `**\u{1F4F8} Ending screen \u2014 ${frame.caption}**`,
475
+ "",
476
+ fullSizeImage(frame.url, test.name)
477
+ ];
444
478
  }
445
479
  function renderResultSummary(test, projectId) {
446
480
  const dashboardUrl = `${DASHBOARD_URL_BASE}/${projectId}/scripts?modal=script-details&testCaseId=${encodeURIComponent(test.testCaseId)}`;
@@ -469,7 +503,7 @@ function renderBody(report, opts) {
469
503
  "_Full per-test details in the comment below \u2014 the PR description was too large to inline them._"
470
504
  ].join("\n");
471
505
  }
472
- const detailBlocks = report.tests.map((t) => renderTestDetails(t, report.projectId));
506
+ const detailBlocks = report.tests.map((t, i) => renderTestDetails(t, report.projectId, i + 1));
473
507
  return [
474
508
  overview,
475
509
  "",
@@ -482,7 +516,7 @@ function renderComment(report) {
482
516
  if (report.tests.length === 0) {
483
517
  return "";
484
518
  }
485
- const detailBlocks = report.tests.map((t) => renderTestDetails(t, report.projectId));
519
+ const detailBlocks = report.tests.map((t, i) => renderTestDetails(t, report.projectId, i + 1));
486
520
  return [
487
521
  "## E2E acceptance evidence (overflow)",
488
522
  "",
@@ -523,7 +557,9 @@ var PassedTestSchema = z.object({
523
557
  status: z.literal("passed"),
524
558
  steps: z.array(StepSchema),
525
559
  useCaseName: z.string().min(1).optional(),
526
- description: z.string().min(1).optional()
560
+ description: z.string().min(1).optional(),
561
+ endingScreenshotUrl: z.string().url().optional(),
562
+ endingScreenshotCaption: z.string().min(1).optional()
527
563
  });
528
564
  var FailedTestSchema = z.object({
529
565
  name: z.string().min(1),
@@ -537,7 +573,9 @@ var FailedTestSchema = z.object({
537
573
  error: z.string().min(1),
538
574
  artifactsDir: z.string().min(1).optional(),
539
575
  useCaseName: z.string().min(1).optional(),
540
- description: z.string().min(1).optional()
576
+ description: z.string().min(1).optional(),
577
+ endingScreenshotUrl: z.string().url().optional(),
578
+ endingScreenshotCaption: z.string().min(1).optional()
541
579
  });
542
580
  var TestResultSchema = z.discriminatedUnion("status", [
543
581
  PassedTestSchema,
@@ -558,6 +596,7 @@ var GS_SCHEME = "gs://";
558
596
  var PUBLIC_URL_PATH = "/v1/protected/storage/publicUrl";
559
597
  var RESOLVE_TIMEOUT_MS = 1e4;
560
598
  var LOG_PREFIX = "build-pr-section";
599
+ var IMAGE_PROXY_PREFIX = "https://images.weserv.nl/?url=";
561
600
 
562
601
  // src/cli/pr-section/resolve-urls.ts
563
602
  function errMsg(e) {
@@ -566,6 +605,12 @@ function errMsg(e) {
566
605
  function isGsUrl(url) {
567
606
  return url.startsWith(GS_SCHEME);
568
607
  }
608
+ function wrapInImageProxy(url) {
609
+ if (url.startsWith(IMAGE_PROXY_PREFIX)) {
610
+ return url;
611
+ }
612
+ return `${IMAGE_PROXY_PREFIX}${encodeURIComponent(url)}`;
613
+ }
569
614
  function buildAuthHeaders(credentials) {
570
615
  const headers = {};
571
616
  if (credentials.bearerToken) {
@@ -584,6 +629,9 @@ function collectGsUrls(report) {
584
629
  seen.add(step.screenshotUrl);
585
630
  }
586
631
  }
632
+ if (test.endingScreenshotUrl && isGsUrl(test.endingScreenshotUrl)) {
633
+ seen.add(test.endingScreenshotUrl);
634
+ }
587
635
  }
588
636
  return Array.from(seen);
589
637
  }
@@ -619,7 +667,17 @@ function remapStep(step, urlMap) {
619
667
  return { ...step, screenshotUrl: resolved };
620
668
  }
621
669
  function remapTest(test, urlMap) {
622
- return { ...test, steps: test.steps.map((s) => remapStep(s, urlMap)) };
670
+ const remapped = {
671
+ ...test,
672
+ steps: test.steps.map((s) => remapStep(s, urlMap))
673
+ };
674
+ if (test.endingScreenshotUrl) {
675
+ const resolved = urlMap.get(test.endingScreenshotUrl);
676
+ if (resolved) {
677
+ remapped.endingScreenshotUrl = resolved;
678
+ }
679
+ }
680
+ return remapped;
623
681
  }
624
682
  async function resolveGsScreenshotUrls(report, opts) {
625
683
  const { stderrWrite } = opts;
@@ -647,7 +705,7 @@ async function resolveGsScreenshotUrls(report, opts) {
647
705
  for (let i = 0; i < gsUrls.length; i++) {
648
706
  const https = resolved[i];
649
707
  if (https) {
650
- urlMap.set(gsUrls[i], https);
708
+ urlMap.set(gsUrls[i], wrapInImageProxy(https));
651
709
  } else {
652
710
  failureCount++;
653
711
  }
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-2FVSZ5LQ.js';
2
+ import { runCli } from './chunk-SMRMPHDD.js';
3
3
  import './chunk-HDEZDEM6.js';
4
4
 
5
5
  // src/cli/main.ts
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-2FVSZ5LQ.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-SMRMPHDD.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-HDEZDEM6.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": "4.6.1",
4
+ "version": "4.7.0",
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": "4.6.1",
5
+ "version": "4.7.0",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -1,7 +1,7 @@
1
1
  {
2
- "release": "4.6.1",
3
- "buildId": "run-16-1",
4
- "commitSha": "dd53168160e472e0916113dd838047c74aa46919",
5
- "buildTime": "2026-04-11T04:27:19Z",
2
+ "release": "4.7.0",
3
+ "buildId": "run-18-1",
4
+ "commitSha": "2497ce3c3bd536d511c5f1a8d6621e691fd8f5e3",
5
+ "buildTime": "2026-04-11T06:11:05Z",
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": "4.6.1",
4
+ "version": "4.7.0",
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": "4.6.1",
4
+ "version": "4.7.0",
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": "4.6.1",
5
+ "version": "4.7.0",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"