@crvy/rprtr 0.0.5 → 0.0.8

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.
@@ -9,11 +9,12 @@ import {
9
9
  applyTestEndEvent,
10
10
  createMutableReportState,
11
11
  finalizeRunEvent,
12
+ resolveBaselineTargets,
12
13
  safeParse
13
- } from "./chunk-MWLM3HWS.js";
14
+ } from "./chunk-EJRLZZ22.js";
14
15
 
15
16
  // src/server/app.ts
16
- import { dirname as dirname2, join as join4 } from "path";
17
+ import { dirname as dirname3, join as join4 } from "path";
17
18
  import { fileURLToPath } from "url";
18
19
  import pLimit from "p-limit";
19
20
 
@@ -177,7 +178,9 @@ function broadcastToBrowsers(wsClients, msg) {
177
178
  // src/server/handlers.ts
178
179
  function handleTestBegin(ctx, data) {
179
180
  const test = applyTestBeginEvent(ctx, data);
181
+ ctx.reportData.isRunning = true;
180
182
  console.log(` \u25B6 [${test.browser ?? "?"}] ${test.title}`);
183
+ broadcastToBrowsers(ctx.wsClients, { type: "test-begin", data });
181
184
  }
182
185
  function handleTestEnd(ctx, data) {
183
186
  const result = applyTestEndEvent(ctx, data);
@@ -316,7 +319,8 @@ async function watchReportArtifacts(options) {
316
319
  }
317
320
 
318
321
  // src/server/routes.ts
319
- import { join as join3 } from "path";
322
+ import { existsSync } from "fs";
323
+ import { dirname as dirname2, join as join3 } from "path";
320
324
  var LIVE_UPDATES_WEBSOCKET_PATH = "/";
321
325
  function isWebSocketUpgradeRequest(req) {
322
326
  return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
@@ -339,6 +343,37 @@ async function handleSrcFiles(req) {
339
343
  function handleApiReport(ctx) {
340
344
  return Response.json(ctx.reportData);
341
345
  }
346
+ var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
347
+ function actualPathFromUrl(ctx, actualUrl) {
348
+ return actualUrl.startsWith("/screenshots/") ? join3(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length)) : actualUrl;
349
+ }
350
+ function reporterTitlePath(test) {
351
+ const testFile = test.location?.file;
352
+ return ["", test.browser, testFile ?? "", ...test.titlePath, test.title];
353
+ }
354
+ function resolveApprovalTarget(ctx, test, retry, imageName) {
355
+ const testFile = test.location?.file;
356
+ const declaration = test.results?.[retry]?.visualDeclarations?.find((candidate) => candidate.visualName === imageName);
357
+ if (ctx.approvalRouting === void 0 || testFile === void 0 || declaration === void 0) {
358
+ return null;
359
+ }
360
+ const targets = resolveBaselineTargets({
361
+ testFile,
362
+ reporterTitlePath: reporterTitlePath(test),
363
+ declarations: [declaration],
364
+ config: {
365
+ configDir: ctx.approvalRouting.configDir,
366
+ testDir: ctx.approvalRouting.playwrightTestDir ?? dirname2(testFile),
367
+ snapshotDir: ctx.approvalRouting.playwrightSnapshotDir ?? dirname2(testFile),
368
+ projectName: test.browser,
369
+ snapshotSuffix: process.platform,
370
+ snapshotPathTemplate: ctx.approvalRouting.playwrightSnapshotPathTemplate,
371
+ toHaveScreenshotPathTemplate: ctx.approvalRouting.playwrightToHaveScreenshotPathTemplate
372
+ },
373
+ snapshotPathExists: existsSync
374
+ });
375
+ return targets.length === 1 ? targets[0]?.snapshotPath ?? null : null;
376
+ }
342
377
  async function handleApiApprove(ctx, req) {
343
378
  try {
344
379
  const rawBody = await req.json();
@@ -349,61 +384,96 @@ async function handleApiApprove(ctx, req) {
349
384
  }
350
385
  const { id, retry, image } = parsed;
351
386
  const test = ctx.reportData.tests[id];
352
- if (test !== null && test !== void 0) {
353
- test.approved ??= {};
354
- test.approved[image] = retry;
387
+ if (test === void 0) {
388
+ return Response.json({ success: false, error: "Test not found" }, { status: 404 });
389
+ }
390
+ const actualUrl = test.results?.[retry]?.images?.[image]?.actual;
391
+ if (actualUrl === void 0) {
392
+ return Response.json({ success: false, error: "Actual image not found" }, { status: 409 });
393
+ }
394
+ const snapshotPath = resolveApprovalTarget(ctx, test, retry, image);
395
+ if (snapshotPath === null) {
396
+ return Response.json({ success: false, error: APPROVAL_TARGET_ERROR }, { status: 409 });
397
+ }
398
+ try {
399
+ await copyFilePortable(actualPathFromUrl(ctx, actualUrl), snapshotPath);
400
+ test.approved = { ...test.approved ?? {}, [image]: retry };
355
401
  await ctx.saveReport();
356
- const actualUrl = test.results?.[retry]?.images?.[image]?.actual;
357
- if (actualUrl !== null && actualUrl !== void 0 && test.location?.file !== null && test.location?.file !== void 0) {
358
- const actualPath = actualUrl.replace("/screenshots/", `${ctx.reportData.screenshotDir}/`);
359
- const snapshotPath = `${test.location.file}-snapshots/${image}-${test.browser}-${process.platform}.png`;
360
- try {
361
- await copyFilePortable(actualPath, snapshotPath);
362
- console.log(` \u2714 Updated baseline: ${snapshotPath}`);
363
- } catch (err) {
364
- const errorMsg = err instanceof Error ? err.message : String(err);
365
- console.error(` \u2717 Failed to update baseline: ${errorMsg}`);
366
- }
367
- }
402
+ console.log(` \u2714 Updated baseline: ${snapshotPath}`);
368
403
  console.log(` \u2714 Approved [${test.browser}] ${test.title} \u2014 ${image}`);
404
+ return Response.json({ success: true });
405
+ } catch (err) {
406
+ const errorMsg = err instanceof Error ? err.message : String(err);
407
+ console.error(` \u2717 Failed to update baseline: ${errorMsg}`);
408
+ return Response.json({ success: false, error: "Failed to update baseline" }, { status: 500 });
369
409
  }
370
- return Response.json({ success: true });
371
410
  } catch {
372
411
  return Response.json({ success: false, error: "Invalid request" }, { status: 400 });
373
412
  }
374
413
  }
375
- async function handleApiApproveAll(ctx) {
376
- let approvedCount = 0;
377
- const baselineUpdates = [];
378
- Object.values(ctx.reportData.tests).forEach((test) => {
379
- if (!test?.results) return;
380
- const approved = {};
414
+ function createBulkApprovalUpdates(ctx) {
415
+ return Object.values(ctx.reportData.tests).flatMap((test) => {
416
+ if (!test.results || test.results.length === 0) {
417
+ return [];
418
+ }
381
419
  const lastRetry = test.results.length - 1;
382
420
  const lastResult = test.results[lastRetry];
383
- if (!lastResult?.images) return;
384
- Object.keys(lastResult.images).forEach((imageName) => {
385
- approved[imageName] = lastRetry;
386
- approvedCount++;
421
+ if (!lastResult?.images) {
422
+ return [];
423
+ }
424
+ return Object.keys(lastResult.images).flatMap((imageName) => {
387
425
  const actualUrl = lastResult.images?.[imageName]?.actual;
388
- if (actualUrl !== null && actualUrl !== void 0 && test.location?.file !== null && test.location?.file !== void 0) {
389
- const actualPath = actualUrl.replace("/screenshots/", `${ctx.reportData.screenshotDir}/`);
390
- const snapshotPath = `${test.location.file}-snapshots/${imageName}-${test.browser}-${process.platform}.png`;
391
- baselineUpdates.push(
392
- copyFilePortable(actualPath, snapshotPath).then(() => {
393
- console.log(` \u2714 Updated baseline: ${snapshotPath}`);
394
- }).catch((err) => {
395
- const errorMsg = err instanceof Error ? err.message : String(err);
396
- console.error(` \u2717 Failed to update baseline: ${errorMsg}`);
397
- })
398
- );
426
+ if (actualUrl === void 0) {
427
+ return [Promise.resolve({ kind: "unresolved" })];
428
+ }
429
+ const snapshotPath = resolveApprovalTarget(ctx, test, lastRetry, imageName);
430
+ if (snapshotPath === null) {
431
+ return [Promise.resolve({ kind: "unresolved" })];
399
432
  }
433
+ return [
434
+ copyFilePortable(actualPathFromUrl(ctx, actualUrl), snapshotPath).then(
435
+ () => ({
436
+ kind: "approved",
437
+ imageName,
438
+ retry: lastRetry,
439
+ snapshotPath,
440
+ test
441
+ })
442
+ ).catch((err) => {
443
+ const errorMsg = err instanceof Error ? err.message : String(err);
444
+ console.error(` \u2717 Failed to update baseline: ${errorMsg}`);
445
+ return { kind: "failed" };
446
+ })
447
+ ];
400
448
  });
401
- test.approved = approved;
402
449
  });
450
+ }
451
+ function summarizeBulkApprovalOutcomes(outcomes) {
452
+ return outcomes.reduce(
453
+ (summary, outcome) => {
454
+ switch (outcome.kind) {
455
+ case "approved": {
456
+ outcome.test.approved = { ...outcome.test.approved ?? {}, [outcome.imageName]: outcome.retry };
457
+ console.log(` \u2714 Updated baseline: ${outcome.snapshotPath}`);
458
+ return { ...summary, approved: summary.approved + 1 };
459
+ }
460
+ case "unresolved":
461
+ return { ...summary, unresolved: summary.unresolved + 1 };
462
+ case "failed":
463
+ return { ...summary, failed: summary.failed + 1 };
464
+ }
465
+ },
466
+ { approved: 0, unresolved: 0, failed: 0 }
467
+ );
468
+ }
469
+ async function handleApiApproveAll(ctx) {
470
+ const outcomes = await Promise.all(createBulkApprovalUpdates(ctx));
471
+ const counts = summarizeBulkApprovalOutcomes(outcomes);
403
472
  await ctx.saveReport();
404
- await Promise.all(baselineUpdates);
405
- console.log(` \u2714 Approved all \u2014 ${approvedCount} image(s)`);
406
- return Response.json({ success: true });
473
+ console.log(
474
+ ` \u2714 Approved all \u2014 approved: ${counts.approved}, unresolved: ${counts.unresolved}, failed: ${counts.failed}`
475
+ );
476
+ return Response.json({ success: counts.failed === 0, ...counts });
407
477
  }
408
478
  async function handleApiImages(req) {
409
479
  const path = new URL(req.url).pathname.slice("/api/images/".length);
@@ -438,10 +508,10 @@ function handleHttpRequest(ctx, req) {
438
508
  if (pathname === "/api/report") {
439
509
  return Promise.resolve(handleApiReport(ctx));
440
510
  }
441
- if (pathname === "/api/approve") {
511
+ if (pathname === "/api/approve" && req.method === "POST") {
442
512
  return handleApiApprove(ctx, req);
443
513
  }
444
- if (pathname === "/api/approve-all") {
514
+ if (pathname === "/api/approve-all" && req.method === "POST") {
445
515
  return handleApiApproveAll(ctx);
446
516
  }
447
517
  if (pathname.startsWith("/api/images/")) {
@@ -574,7 +644,7 @@ function createWebSocketMessageHandler(getHandlerContext) {
574
644
  };
575
645
  }
576
646
  async function resolveStaticDir(staticDir) {
577
- const currentDir = dirname2(fileURLToPath(import.meta.url));
647
+ const currentDir = dirname3(fileURLToPath(import.meta.url));
578
648
  const candidates = staticDir === void 0 ? [
579
649
  currentDir,
580
650
  join4(currentDir, "dist"),
@@ -599,7 +669,21 @@ async function resolveReportPath(reportPath) {
599
669
  if (await isDirectory(reportPath)) {
600
670
  return { reportFile: join4(reportPath, "report.json"), offlineReportDir: reportPath };
601
671
  }
602
- return { reportFile: reportPath, offlineReportDir: dirname2(reportPath) };
672
+ return { reportFile: reportPath, offlineReportDir: dirname3(reportPath) };
673
+ }
674
+ function createRoutesContext(reportData, staticDir, saveReport, options) {
675
+ return {
676
+ reportData,
677
+ staticDir,
678
+ saveReport,
679
+ approvalRouting: {
680
+ configDir: options.configDir ?? process.cwd(),
681
+ playwrightTestDir: options.playwrightTestDir,
682
+ playwrightSnapshotDir: options.playwrightSnapshotDir,
683
+ playwrightSnapshotPathTemplate: options.playwrightSnapshotPathTemplate,
684
+ playwrightToHaveScreenshotPathTemplate: options.playwrightToHaveScreenshotPathTemplate
685
+ }
686
+ };
603
687
  }
604
688
  async function createServerApp(options = {}) {
605
689
  const port = options.port ?? 3e3;
@@ -612,11 +696,7 @@ async function createServerApp(options = {}) {
612
696
  async function saveReport() {
613
697
  await writeJsonFile(reportFile, reportData);
614
698
  }
615
- const routesContext = {
616
- reportData,
617
- staticDir,
618
- saveReport
619
- };
699
+ const routesContext = createRoutesContext(reportData, staticDir, saveReport, options);
620
700
  const getHandlerContext = () => createHandlerContext(reportData, wsClients, currentRunIds, saveReport);
621
701
  const handleRequest = (req) => handleHttpRequest(routesContext, req);
622
702
  const handleWebSocketMessage = createWebSocketMessageHandler(getHandlerContext);
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-IH44LYNM.js";
5
- import "./chunk-MWLM3HWS.js";
4
+ } from "./chunk-XU5VQ3FZ.js";
5
+ import "./chunk-EJRLZZ22.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { join } from "path";
@@ -0,0 +1,3 @@
1
+ export declare const log: (...args: unknown[]) => void;
2
+ export declare const logError: (...args: unknown[]) => void;
3
+ //# sourceMappingURL=debug-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-log.d.ts","sourceRoot":"","sources":["../src/debug-log.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,OAAO,EAAE,KAAG,IAExC,CAAA;AACD,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,OAAO,EAAE,KAAG,IAE7C,CAAA"}
package/dist/index.css CHANGED
@@ -24,7 +24,6 @@
24
24
  --color-white: #fff;
25
25
  --spacing: 0.25rem;
26
26
  --container-xl: 36rem;
27
- --container-2xl: 42rem;
28
27
  --text-xs: 0.75rem;
29
28
  --text-xs--line-height: calc(1 / 0.75);
30
29
  --text-sm: 0.875rem;
@@ -324,6 +323,9 @@
324
323
  .inline-block {
325
324
  display: inline-block;
326
325
  }
326
+ .table {
327
+ display: table;
328
+ }
327
329
  .size-2 {
328
330
  width: calc(var(--spacing) * 2);
329
331
  height: calc(var(--spacing) * 2);
@@ -388,9 +390,6 @@
388
390
  .w-full {
389
391
  width: 100%;
390
392
  }
391
- .max-w-2xl {
392
- max-width: var(--container-2xl);
393
- }
394
393
  .max-w-full {
395
394
  max-width: 100%;
396
395
  }
@@ -538,12 +537,6 @@
538
537
  .border-edge {
539
538
  border-color: var(--color-edge);
540
539
  }
541
- .border-edge\/70 {
542
- border-color: var(--color-edge);
543
- @supports (color: color-mix(in lab, red, red)) {
544
- border-color: color-mix(in oklab, var(--color-edge) 70%, transparent);
545
- }
546
- }
547
540
  .border-error {
548
541
  border-color: var(--color-error);
549
542
  }
@@ -556,12 +549,6 @@
556
549
  border-color: color-mix(in oklab, var(--color-green-500) 60%, transparent);
557
550
  }
558
551
  }
559
- .border-info\/40 {
560
- border-color: var(--color-info);
561
- @supports (color: color-mix(in lab, red, red)) {
562
- border-color: color-mix(in oklab, var(--color-info) 40%, transparent);
563
- }
564
- }
565
552
  .border-purple-500\/60 {
566
553
  border-color: color-mix(in srgb, oklch(62.7% 0.265 303.9) 60%, transparent);
567
554
  @supports (color: color-mix(in lab, red, red)) {
@@ -616,12 +603,6 @@
616
603
  .bg-info {
617
604
  background-color: var(--color-info);
618
605
  }
619
- .bg-info\/10 {
620
- background-color: var(--color-info);
621
- @supports (color: color-mix(in lab, red, red)) {
622
- background-color: color-mix(in oklab, var(--color-info) 10%, transparent);
623
- }
624
- }
625
606
  .bg-purple-500\/25 {
626
607
  background-color: color-mix(in srgb, oklch(62.7% 0.265 303.9) 25%, transparent);
627
608
  @supports (color: color-mix(in lab, red, red)) {