@crvy/rprtr 0.1.4 → 0.2.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +8 -7
  3. package/dist/{chunk-WRMHUY5A.js → chunk-HAFWYUNO.js} +181 -130
  4. package/dist/{chunk-KLGJSEKV.js → chunk-RFZGJSL3.js} +439 -127
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +6 -4
  7. package/dist/index.css +26 -0
  8. package/dist/index.js +297 -76
  9. package/dist/offline-reports.d.ts +4 -0
  10. package/dist/offline-reports.d.ts.map +1 -1
  11. package/dist/report-state.d.ts +3 -1
  12. package/dist/report-state.d.ts.map +1 -1
  13. package/dist/reporter-utils.d.ts +0 -1
  14. package/dist/reporter-utils.d.ts.map +1 -1
  15. package/dist/reporter.cjs +186 -134
  16. package/dist/reporter.d.ts +1 -1
  17. package/dist/reporter.d.ts.map +1 -1
  18. package/dist/reporter.js +8 -6
  19. package/dist/schemas/http.d.ts +44 -0
  20. package/dist/schemas/http.d.ts.map +1 -0
  21. package/dist/schemas.d.ts +25 -6
  22. package/dist/schemas.d.ts.map +1 -1
  23. package/dist/server/app.d.ts +6 -0
  24. package/dist/server/app.d.ts.map +1 -1
  25. package/dist/server/handlers.d.ts +3 -0
  26. package/dist/server/handlers.d.ts.map +1 -1
  27. package/dist/server/playwright-config.d.ts +2 -0
  28. package/dist/server/playwright-config.d.ts.map +1 -0
  29. package/dist/server/routes-context.d.ts +13 -0
  30. package/dist/server/routes-context.d.ts.map +1 -0
  31. package/dist/server/routes.d.ts +6 -1
  32. package/dist/server/routes.d.ts.map +1 -1
  33. package/dist/server/run-controller.d.ts +85 -0
  34. package/dist/server/run-controller.d.ts.map +1 -0
  35. package/dist/server/run-routes.d.ts +3 -0
  36. package/dist/server/run-routes.d.ts.map +1 -0
  37. package/dist/server.cjs +633 -273
  38. package/dist/server.js +2 -2
  39. package/dist/types.d.ts +6 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -19322,10 +19322,42 @@ function date4(params) {
19322
19322
  // node_modules/zod/v4/classic/external.js
19323
19323
  config(en_default());
19324
19324
 
19325
+ // src/schemas/http.ts
19326
+ var ApproveRequestBodySchema = external_exports.object({
19327
+ id: external_exports.string(),
19328
+ retry: external_exports.number(),
19329
+ image: external_exports.string()
19330
+ });
19331
+ var RunTestDescriptorSchema = external_exports.object({
19332
+ file: external_exports.string(),
19333
+ line: external_exports.number(),
19334
+ column: external_exports.number().optional(),
19335
+ projectName: external_exports.string().optional(),
19336
+ titlePath: external_exports.array(external_exports.string())
19337
+ });
19338
+ var RunRequestBodySchema = external_exports.object({
19339
+ tests: external_exports.array(RunTestDescriptorSchema).optional()
19340
+ });
19341
+ var RunResponseSchema = external_exports.discriminatedUnion("ok", [
19342
+ external_exports.object({ ok: external_exports.literal(true) }),
19343
+ external_exports.object({
19344
+ ok: external_exports.literal(false),
19345
+ reason: external_exports.enum(["no-config", "already-running", "no-tests"])
19346
+ })
19347
+ ]);
19348
+ var StopResponseSchema = external_exports.discriminatedUnion("ok", [
19349
+ external_exports.object({ ok: external_exports.literal(true) }),
19350
+ external_exports.object({
19351
+ ok: external_exports.literal(false),
19352
+ reason: external_exports.literal("not-running")
19353
+ })
19354
+ ]);
19355
+
19325
19356
  // src/schemas.ts
19326
19357
  var LocationSchema = external_exports.object({
19327
19358
  file: external_exports.string(),
19328
- line: external_exports.number()
19359
+ line: external_exports.number(),
19360
+ column: external_exports.number().optional()
19329
19361
  });
19330
19362
  var VisualSourceSchema = external_exports.enum(["comparison", "baseline-only", "declared-only"]);
19331
19363
  var ImagesSchema = external_exports.object({
@@ -19413,7 +19445,13 @@ var WebSocketMessageSchema = external_exports.discriminatedUnion("type", [
19413
19445
  isUpdateMode: external_exports.boolean().optional()
19414
19446
  })
19415
19447
  }),
19416
- external_exports.object({ type: external_exports.literal("approve"), data: external_exports.unknown() })
19448
+ external_exports.object({ type: external_exports.literal("approve"), data: external_exports.unknown() }),
19449
+ external_exports.object({
19450
+ type: external_exports.literal("run-status"),
19451
+ data: external_exports.object({
19452
+ running: external_exports.boolean()
19453
+ })
19454
+ })
19417
19455
  ]);
19418
19456
  var TestBeginDataSchema = external_exports.object({
19419
19457
  id: external_exports.string(),
@@ -19442,7 +19480,9 @@ var RegisterDataSchema = external_exports.object({
19442
19480
  playwrightSnapshotDir: external_exports.string().optional(),
19443
19481
  playwrightTestDir: external_exports.string().optional(),
19444
19482
  playwrightSnapshotPathTemplate: external_exports.string().optional(),
19445
- playwrightToHaveScreenshotPathTemplate: external_exports.string().optional()
19483
+ playwrightToHaveScreenshotPathTemplate: external_exports.string().optional(),
19484
+ configFile: external_exports.string().optional(),
19485
+ cwd: external_exports.string().optional()
19446
19486
  });
19447
19487
  var ReportDataSchema = external_exports.object({
19448
19488
  isRunning: external_exports.boolean(),
@@ -19467,14 +19507,11 @@ var OfflineReportSchema = external_exports.object({
19467
19507
  workers: external_exports.number(),
19468
19508
  events: external_exports.array(OfflineEventSchema)
19469
19509
  });
19470
- var ApproveRequestBodySchema = external_exports.object({
19471
- id: external_exports.string(),
19472
- retry: external_exports.number(),
19473
- image: external_exports.string()
19474
- });
19475
19510
  var ReportApiResponseSchema = external_exports.object({
19476
19511
  tests: external_exports.record(external_exports.string(), TestDataSchema),
19477
- isUpdateMode: external_exports.boolean().optional()
19512
+ isUpdateMode: external_exports.boolean().optional(),
19513
+ isRunning: external_exports.boolean().optional(),
19514
+ runEnabled: external_exports.boolean().optional()
19478
19515
  });
19479
19516
  var ClientBootstrapDataSchema = external_exports.object({
19480
19517
  report: ReportApiResponseSchema.extend({
@@ -19482,7 +19519,9 @@ var ClientBootstrapDataSchema = external_exports.object({
19482
19519
  }),
19483
19520
  liveUpdates: external_exports.boolean(),
19484
19521
  approvalEnabled: external_exports.boolean(),
19485
- approvalMessage: external_exports.string().optional()
19522
+ approvalMessage: external_exports.string().optional(),
19523
+ runEnabled: external_exports.boolean().optional(),
19524
+ isRunning: external_exports.boolean().optional()
19486
19525
  });
19487
19526
  var ImagesViewModeSchema = external_exports.enum(["side-by-side", "swap", "slide", "blend"]);
19488
19527
  function safeParse3(schema, data) {
@@ -20066,6 +20105,42 @@ function recalcAllSuiteStatuses(suite) {
20066
20105
  }
20067
20106
  suite.status = getChildrenArray(suite.children).map(({ status }) => status).reduce(calcStatus);
20068
20107
  }
20108
+ function markTestsPending(node) {
20109
+ if (isTest(node)) {
20110
+ node.status = "pending";
20111
+ return;
20112
+ }
20113
+ getChildrenArray(node.children).forEach(markTestsPending);
20114
+ }
20115
+
20116
+ // src/client/helpers/run-snapshot.ts
20117
+ function captureTestStatuses(node) {
20118
+ const snapshot2 = /* @__PURE__ */ new Map();
20119
+ const walk = (n) => {
20120
+ if (isTest(n)) {
20121
+ snapshot2.set(n.id, n.status);
20122
+ return;
20123
+ }
20124
+ for (const child2 of getChildrenArray(n.children)) walk(child2);
20125
+ };
20126
+ walk(node);
20127
+ return snapshot2;
20128
+ }
20129
+ function restoreTestStatuses(root10, snapshot2, receivedIds) {
20130
+ let restored = 0;
20131
+ const walk = (n) => {
20132
+ if (isTest(n)) {
20133
+ if (snapshot2.has(n.id) && !receivedIds.has(n.id)) {
20134
+ n.status = snapshot2.get(n.id);
20135
+ restored++;
20136
+ }
20137
+ return;
20138
+ }
20139
+ for (const child2 of getChildrenArray(n.children)) walk(child2);
20140
+ };
20141
+ walk(root10);
20142
+ return restored;
20143
+ }
20069
20144
 
20070
20145
  // src/client/viewMode.ts
20071
20146
  var VIEW_MODE_KEY = "crvy-rprtr-view-mode";
@@ -20105,8 +20180,9 @@ function statusDotClass(status) {
20105
20180
 
20106
20181
  // src/client/components/TreeItem.svelte
20107
20182
  var root_1 = from_html(`<span aria-hidden="true">\u25B6</span>`);
20108
- var root_2 = from_html(`<span></span>`);
20109
- var root = from_html(`<div role="treeitem" tabindex="-1"><!> <span class="flex-1 text-ui whitespace-nowrap overflow-hidden text-ellipsis"> </span> <!></div> <!>`, 1);
20183
+ var root_2 = from_html(`<button>&#9654;</button>`);
20184
+ var root_3 = from_html(`<span></span>`);
20185
+ var root = from_html(`<div role="treeitem" tabindex="-1"><!> <span class="flex-1 text-ui whitespace-nowrap overflow-hidden text-ellipsis"> </span> <!> <!></div> <!>`, 1);
20110
20186
  function TreeItem_1($$anchor, $$props) {
20111
20187
  push($$props, true);
20112
20188
  let itemIsTest = user_derived(() => isTest($$props.item));
@@ -20149,24 +20225,50 @@ function TreeItem_1($$anchor, $$props) {
20149
20225
  var node_1 = sibling(span_1, 2);
20150
20226
  {
20151
20227
  var consequent_1 = ($$anchor2) => {
20152
- var span_2 = root_2();
20228
+ var button = root_2();
20229
+ template_effect(
20230
+ ($0) => {
20231
+ set_class(button, 1, $0);
20232
+ set_attribute2(button, "aria-label", `Run ${get2(itemIsTest) ? "this test" : "this suite"}`);
20233
+ set_attribute2(button, "title", `Run ${get2(itemIsTest) ? "this test" : "this suite"}`);
20234
+ button.disabled = $$props.isRunning;
20235
+ },
20236
+ [
20237
+ () => clsx2(cn("size-5 flex items-center justify-center text-success text-[10px] rounded transition-opacity", $$props.isRunning ? "opacity-0 pointer-events-none" : "opacity-0 group-hover:opacity-100 focus:opacity-100 hover:bg-surface-hover focus-visible:ring-2 focus-visible:ring-accent"))
20238
+ ]
20239
+ );
20240
+ delegated("click", button, (e) => {
20241
+ e.stopPropagation();
20242
+ $$props.onRun($$props.item);
20243
+ });
20244
+ delegated("keydown", button, (e) => e.stopPropagation());
20245
+ append($$anchor2, button);
20246
+ };
20247
+ if_block(node_1, ($$render) => {
20248
+ if ($$props.runEnabled) $$render(consequent_1);
20249
+ });
20250
+ }
20251
+ var node_2 = sibling(node_1, 2);
20252
+ {
20253
+ var consequent_2 = ($$anchor2) => {
20254
+ var span_2 = root_3();
20153
20255
  template_effect(($0) => set_class(span_2, 1, $0), [
20154
20256
  () => clsx2(cn("size-2 rounded-full inline-block shrink-0", statusDotClass($$props.item.status)))
20155
20257
  ]);
20156
20258
  append($$anchor2, span_2);
20157
20259
  };
20158
- if_block(node_1, ($$render) => {
20159
- if ($$props.item.status) $$render(consequent_1);
20260
+ if_block(node_2, ($$render) => {
20261
+ if ($$props.item.status) $$render(consequent_2);
20160
20262
  });
20161
20263
  }
20162
20264
  reset(div);
20163
20265
  bind_this(div, ($$value) => set(rowEl, $$value), () => get2(rowEl));
20164
- var node_2 = sibling(div, 2);
20266
+ var node_3 = sibling(div, 2);
20165
20267
  {
20166
- var consequent_2 = ($$anchor2) => {
20268
+ var consequent_3 = ($$anchor2) => {
20167
20269
  var fragment_1 = comment();
20168
- var node_3 = first_child(fragment_1);
20169
- each(node_3, 17, () => Object.values(get2(suiteItem).children).filter(isDefined).filter((c) => hasScreenshots(c)), index, ($$anchor3, child2) => {
20270
+ var node_4 = first_child(fragment_1);
20271
+ each(node_4, 17, () => Object.values(get2(suiteItem).children).filter(isDefined).filter((c) => hasScreenshots(c)), index, ($$anchor3, child2) => {
20170
20272
  {
20171
20273
  let $0 = user_derived(() => $$props.level + 1);
20172
20274
  TreeItem_1($$anchor3, {
@@ -20185,6 +20287,12 @@ function TreeItem_1($$anchor, $$props) {
20185
20287
  get isUpdateMode() {
20186
20288
  return $$props.isUpdateMode;
20187
20289
  },
20290
+ get runEnabled() {
20291
+ return $$props.runEnabled;
20292
+ },
20293
+ get isRunning() {
20294
+ return $$props.isRunning;
20295
+ },
20188
20296
  get onSelect() {
20189
20297
  return $$props.onSelect;
20190
20298
  },
@@ -20193,14 +20301,17 @@ function TreeItem_1($$anchor, $$props) {
20193
20301
  },
20194
20302
  get onToggle() {
20195
20303
  return $$props.onToggle;
20304
+ },
20305
+ get onRun() {
20306
+ return $$props.onRun;
20196
20307
  }
20197
20308
  });
20198
20309
  }
20199
20310
  });
20200
20311
  append($$anchor2, fragment_1);
20201
20312
  };
20202
- if_block(node_2, ($$render) => {
20203
- if (get2(isOpen)) $$render(consequent_2);
20313
+ if_block(node_3, ($$render) => {
20314
+ if (get2(isOpen)) $$render(consequent_3);
20204
20315
  });
20205
20316
  }
20206
20317
  template_effect(
@@ -20214,7 +20325,7 @@ function TreeItem_1($$anchor, $$props) {
20214
20325
  set_text(text2, get2(itemIsTest) ? get2(testItem).browser ?? get2(testItem).title : get2(suiteItem).path[get2(suiteItem).path.length - 1] ?? "Tests");
20215
20326
  },
20216
20327
  [
20217
- () => clsx2(cn("flex items-center py-1 cursor-pointer transition-colors select-none gap-1 hover:bg-surface-hover", get2(isSelected) && "bg-surface-selected", get2(isFocused) && "outline outline-1 outline-accent -outline-offset-1"))
20328
+ () => clsx2(cn("group flex items-center py-1 cursor-pointer transition-colors select-none gap-1 hover:bg-surface-hover", get2(isSelected) && "bg-surface-selected", get2(isFocused) && "outline outline-1 outline-accent -outline-offset-1"))
20218
20329
  ]
20219
20330
  );
20220
20331
  delegated("click", div, handleClick);
@@ -20229,15 +20340,16 @@ delegate(["click", "keydown"]);
20229
20340
  // src/client/components/Sidebar.svelte
20230
20341
  var root_12 = from_html(`<div class="text-xs mb-2 px-2 py-1 text-success bg-success/10 rounded-sm">Review and approve screenshots</div>`);
20231
20342
  var root_22 = from_html(`<button><span></span> <span> </span></button>`);
20232
- var root_3 = from_html(`<button><span class="size-2 rounded-full inline-block shrink-0 bg-info"></span> <span> </span></button>`);
20343
+ var root_32 = from_html(`<button><span class="size-2 rounded-full inline-block shrink-0 bg-info"></span> <span> </span></button>`);
20233
20344
  var root_5 = from_html(`<button class="size-9 flex items-center justify-center bg-surface-input border border-edge rounded text-error cursor-pointer text-sm transition-colors hover:bg-surface-hover focus-visible:ring-2 focus-visible:ring-accent" aria-label="Stop tests">&#9632;</button>`);
20234
20345
  var root_6 = from_html(`<button class="size-9 flex items-center justify-center bg-surface-input border border-edge rounded text-success cursor-pointer text-sm transition-colors hover:bg-surface-hover focus-visible:ring-2 focus-visible:ring-accent" aria-label="Start tests">&#9654;</button>`);
20235
20346
  var root_4 = from_html(`<div class="mt-1"><!></div>`);
20236
- var root_8 = from_html(`<div class="py-6 px-4 text-center text-fg-muted text-ui">No tests match filter</div>`);
20237
- var root_9 = from_html(`<button class="flex-1 px-4 py-1.5 border border-edge rounded text-ui cursor-pointer transition-colors text-center bg-transparent text-fg hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Next &#8250;</button>`);
20238
- var root_10 = from_html(`<button class="flex-1 px-4 py-1.5 border border-success rounded text-ui cursor-pointer transition-colors text-center bg-success text-white font-semibold hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Approve &#8250;</button>`);
20239
- var root_11 = from_html(`<div class="mt-2 text-center text-xs text-fg-muted"> </div>`);
20240
- var root2 = from_html(`<div class="w-[300px] min-w-[300px] max-md:w-full max-md:min-w-0 max-md:max-h-[40vh] max-md:border-b max-md:border-r-0 bg-surface-alt border-r border-edge flex flex-col overflow-hidden"><div class="p-4 border-b border-edge bg-surface-alt shrink-0"><div class="flex justify-between items-start"><div><h1 class="m-0 mb-2 text-base font-normal text-fg-bright">Crvy Rprtr</h1> <!> <div class="flex gap-2 text-xs mb-3 tabular-nums"><!> <!></div></div> <!></div> <input type="text" class="w-full px-2.5 py-1.5 bg-surface-input border border-edge rounded text-fg text-ui outline-none focus-visible:ring-2 focus-visible:ring-accent transition-colors focus:border-accent placeholder:text-fg-muted" placeholder="Filter tests\u2026 (status:failed)" aria-label="Filter tests" autocomplete="off"/></div> <div class="flex-1 overflow-y-auto overscroll-y-contain py-1" role="tree"><!> <!></div> <div class="py-3 px-4 border-t border-edge bg-surface-alt shrink-0"><div class="flex gap-2"><!> <button class="flex-1 px-4 py-1.5 border border-edge rounded text-ui cursor-pointer transition-colors text-center bg-transparent text-fg hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Approve All</button></div> <!></div></div>`);
20347
+ var root_7 = from_html(`<div class="mt-2 text-center text-xs text-fg-muted"> </div>`);
20348
+ var root_9 = from_html(`<div class="py-6 px-4 text-center text-fg-muted text-ui">No tests match filter</div>`);
20349
+ var root_10 = from_html(`<button class="flex-1 px-4 py-1.5 border border-edge rounded text-ui cursor-pointer transition-colors text-center bg-transparent text-fg hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Next &#8250;</button>`);
20350
+ var root_11 = from_html(`<button class="flex-1 px-4 py-1.5 border border-success rounded text-ui cursor-pointer transition-colors text-center bg-success text-white font-semibold hover:brightness-110 disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Approve &#8250;</button>`);
20351
+ var root_122 = from_html(`<div class="mt-2 text-center text-xs text-fg-muted"> </div>`);
20352
+ var root2 = from_html(`<div class="w-[300px] min-w-[300px] max-md:w-full max-md:min-w-0 max-md:max-h-[40vh] max-md:border-b max-md:border-r-0 bg-surface-alt border-r border-edge flex flex-col overflow-hidden"><div class="p-4 border-b border-edge bg-surface-alt shrink-0"><div class="flex justify-between items-start"><div><h1 class="m-0 mb-2 text-base font-normal text-fg-bright">Crvy Rprtr</h1> <!> <div class="flex gap-2 text-xs mb-3 tabular-nums"><!> <!></div></div> <!></div> <input type="text" class="w-full px-2.5 py-1.5 bg-surface-input border border-edge rounded text-fg text-ui outline-none focus-visible:ring-2 focus-visible:ring-accent transition-colors focus:border-accent placeholder:text-fg-muted" placeholder="Filter tests\u2026 (status:failed)" aria-label="Filter tests" autocomplete="off"/> <!></div> <div class="flex-1 overflow-y-auto overscroll-y-contain py-1" role="tree"><!> <!></div> <div class="py-3 px-4 border-t border-edge bg-surface-alt shrink-0"><div class="flex gap-2"><!> <button class="flex-1 px-4 py-1.5 border border-edge rounded text-ui cursor-pointer transition-colors text-center bg-transparent text-fg hover:bg-surface-hover disabled:opacity-40 disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-accent">Approve All</button></div> <!></div></div>`);
20241
20353
  function Sidebar($$anchor, $$props) {
20242
20354
  push($$props, true);
20243
20355
  let filterInput = state("");
@@ -20332,7 +20444,7 @@ function Sidebar($$anchor, $$props) {
20332
20444
  var node_2 = sibling(node_1, 2);
20333
20445
  {
20334
20446
  var consequent_1 = ($$anchor2) => {
20335
- var button_1 = root_3();
20447
+ var button_1 = root_32();
20336
20448
  var span_2 = sibling(child(button_1), 2);
20337
20449
  var text_1 = child(span_2, true);
20338
20450
  reset(span_2);
@@ -20384,16 +20496,29 @@ function Sidebar($$anchor, $$props) {
20384
20496
  append($$anchor2, div_6);
20385
20497
  };
20386
20498
  if_block(node_3, ($$render) => {
20387
- if (!$$props.isReport && !$$props.isUpdateMode) $$render(consequent_3);
20499
+ if ($$props.runEnabled) $$render(consequent_3);
20388
20500
  });
20389
20501
  }
20390
20502
  reset(div_2);
20391
20503
  var input = sibling(div_2, 2);
20392
20504
  remove_input_defaults(input);
20505
+ var node_5 = sibling(input, 2);
20506
+ {
20507
+ var consequent_4 = ($$anchor2) => {
20508
+ var div_7 = root_7();
20509
+ var text_2 = child(div_7, true);
20510
+ reset(div_7);
20511
+ template_effect(() => set_text(text_2, $$props.runMessage));
20512
+ append($$anchor2, div_7);
20513
+ };
20514
+ if_block(node_5, ($$render) => {
20515
+ if ($$props.runMessage) $$render(consequent_4);
20516
+ });
20517
+ }
20393
20518
  reset(div_1);
20394
- var div_7 = sibling(div_1, 2);
20395
- var node_5 = child(div_7);
20396
- each(node_5, 17, () => get2(visibleChildren), index, ($$anchor2, child2) => {
20519
+ var div_8 = sibling(div_1, 2);
20520
+ var node_6 = child(div_8);
20521
+ each(node_6, 17, () => get2(visibleChildren), index, ($$anchor2, child2) => {
20397
20522
  TreeItem_1($$anchor2, {
20398
20523
  get item() {
20399
20524
  return get2(child2);
@@ -20408,6 +20533,12 @@ function Sidebar($$anchor, $$props) {
20408
20533
  get isUpdateMode() {
20409
20534
  return $$props.isUpdateMode;
20410
20535
  },
20536
+ get runEnabled() {
20537
+ return $$props.runEnabled;
20538
+ },
20539
+ get isRunning() {
20540
+ return $$props.isRunning;
20541
+ },
20411
20542
  get onSelect() {
20412
20543
  return $$props.onSelect;
20413
20544
  },
@@ -20416,26 +20547,29 @@ function Sidebar($$anchor, $$props) {
20416
20547
  },
20417
20548
  get onToggle() {
20418
20549
  return $$props.onToggle;
20550
+ },
20551
+ get onRun() {
20552
+ return $$props.onRun;
20419
20553
  }
20420
20554
  });
20421
20555
  });
20422
- var node_6 = sibling(node_5, 2);
20556
+ var node_7 = sibling(node_6, 2);
20423
20557
  {
20424
- var consequent_4 = ($$anchor2) => {
20425
- var div_8 = root_8();
20426
- append($$anchor2, div_8);
20558
+ var consequent_5 = ($$anchor2) => {
20559
+ var div_9 = root_9();
20560
+ append($$anchor2, div_9);
20427
20561
  };
20428
- if_block(node_6, ($$render) => {
20429
- if (get2(visibleChildren).length === 0) $$render(consequent_4);
20562
+ if_block(node_7, ($$render) => {
20563
+ if (get2(visibleChildren).length === 0) $$render(consequent_5);
20430
20564
  });
20431
20565
  }
20432
- reset(div_7);
20433
- var div_9 = sibling(div_7, 2);
20434
- var div_10 = child(div_9);
20435
- var node_7 = child(div_10);
20566
+ reset(div_8);
20567
+ var div_10 = sibling(div_8, 2);
20568
+ var div_11 = child(div_10);
20569
+ var node_8 = child(div_11);
20436
20570
  {
20437
- var consequent_5 = ($$anchor2) => {
20438
- var button_4 = root_9();
20571
+ var consequent_6 = ($$anchor2) => {
20572
+ var button_4 = root_10();
20439
20573
  template_effect(() => button_4.disabled = !$$props.canApprove);
20440
20574
  delegated("click", button_4, function(...$$args) {
20441
20575
  $$props.onNext?.apply(this, $$args);
@@ -20443,34 +20577,34 @@ function Sidebar($$anchor, $$props) {
20443
20577
  append($$anchor2, button_4);
20444
20578
  };
20445
20579
  var alternate_1 = ($$anchor2) => {
20446
- var button_5 = root_10();
20580
+ var button_5 = root_11();
20447
20581
  template_effect(() => button_5.disabled = !$$props.canApprove);
20448
20582
  delegated("click", button_5, function(...$$args) {
20449
20583
  $$props.onApprove?.apply(this, $$args);
20450
20584
  });
20451
20585
  append($$anchor2, button_5);
20452
20586
  };
20453
- if_block(node_7, ($$render) => {
20454
- if (get2(isAlt)) $$render(consequent_5);
20587
+ if_block(node_8, ($$render) => {
20588
+ if (get2(isAlt)) $$render(consequent_6);
20455
20589
  else $$render(alternate_1, -1);
20456
20590
  });
20457
20591
  }
20458
- var button_6 = sibling(node_7, 2);
20459
- reset(div_10);
20460
- var node_8 = sibling(div_10, 2);
20592
+ var button_6 = sibling(node_8, 2);
20593
+ reset(div_11);
20594
+ var node_9 = sibling(div_11, 2);
20461
20595
  {
20462
- var consequent_6 = ($$anchor2) => {
20463
- var div_11 = root_11();
20464
- var text_2 = child(div_11, true);
20465
- reset(div_11);
20466
- template_effect(() => set_text(text_2, $$props.approvalMessage));
20467
- append($$anchor2, div_11);
20596
+ var consequent_7 = ($$anchor2) => {
20597
+ var div_12 = root_122();
20598
+ var text_3 = child(div_12, true);
20599
+ reset(div_12);
20600
+ template_effect(() => set_text(text_3, $$props.approvalMessage));
20601
+ append($$anchor2, div_12);
20468
20602
  };
20469
- if_block(node_8, ($$render) => {
20470
- if (!$$props.approvalEnabled && $$props.approvalMessage) $$render(consequent_6);
20603
+ if_block(node_9, ($$render) => {
20604
+ if (!$$props.approvalEnabled && $$props.approvalMessage) $$render(consequent_7);
20471
20605
  });
20472
20606
  }
20473
- reset(div_9);
20607
+ reset(div_10);
20474
20608
  reset(div);
20475
20609
  template_effect(() => {
20476
20610
  set_value(input, get2(filterInput));
@@ -20569,7 +20703,7 @@ delegate(["input"]);
20569
20703
  // src/client/components/SideBySideView.svelte
20570
20704
  var root_14 = from_html(`<div class="flex-1 flex flex-col bg-surface-panel rounded-md overflow-hidden min-w-0 border-2 border-green-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-green-500/25 text-green-800 dark:text-green-200 uppercase tracking-wider"> </h3> <div class="flex items-center justify-center p-2"><img alt="Expected" class="w-auto max-w-full object-contain mx-auto" loading="lazy"/></div></div>`);
20571
20705
  var root_24 = from_html(`<div class="flex-1 flex flex-col bg-surface-panel rounded-md overflow-hidden min-w-0 border-2 border-yellow-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-yellow-500/25 text-yellow-800 dark:text-yellow-200 uppercase tracking-wider">Diff</h3> <div class="flex items-center justify-center p-2"><img alt="Diff" class="w-auto max-w-full object-contain mx-auto" loading="lazy"/></div></div>`);
20572
- var root_32 = from_html(`<div class="flex-1 flex flex-col bg-surface-panel rounded-md overflow-hidden min-w-0 border-2 border-red-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-red-500/25 text-red-800 dark:text-red-200 uppercase tracking-wider">Actual</h3> <div class="flex items-center justify-center p-2"><img alt="Actual" class="w-auto max-w-full object-contain mx-auto" loading="lazy"/></div></div>`);
20706
+ var root_33 = from_html(`<div class="flex-1 flex flex-col bg-surface-panel rounded-md overflow-hidden min-w-0 border-2 border-red-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-red-500/25 text-red-800 dark:text-red-200 uppercase tracking-wider">Actual</h3> <div class="flex items-center justify-center p-2"><img alt="Actual" class="w-auto max-w-full object-contain mx-auto" loading="lazy"/></div></div>`);
20573
20707
  var root3 = from_html(`<div><!> <!> <!></div>`);
20574
20708
  function SideBySideView($$anchor, $$props) {
20575
20709
  push($$props, true);
@@ -20627,7 +20761,7 @@ function SideBySideView($$anchor, $$props) {
20627
20761
  var node_2 = sibling(node_1, 2);
20628
20762
  {
20629
20763
  var consequent_2 = ($$anchor2) => {
20630
- var div_5 = root_32();
20764
+ var div_5 = root_33();
20631
20765
  var div_6 = sibling(child(div_5), 2);
20632
20766
  var img_3 = child(div_6);
20633
20767
  reset(div_6);
@@ -20728,7 +20862,7 @@ delegate(["click", "keydown"]);
20728
20862
  // src/client/components/BlendView.svelte
20729
20863
  var root_16 = from_html(`<div class="absolute w-full h-full flex"><img alt="Expected" class="max-w-full border border-green-500 invert"/></div>`);
20730
20864
  var root_26 = from_html(`<img alt="Diff" class="border border-transparent max-w-full invert opacity-0"/>`);
20731
- var root_33 = from_html(`<div class="absolute w-full h-full flex"><img alt="Actual" class="max-w-full border border-red-500 mix-blend-difference invert"/></div>`);
20865
+ var root_34 = from_html(`<div class="absolute w-full h-full flex"><img alt="Actual" class="max-w-full border border-red-500 mix-blend-difference invert"/></div>`);
20732
20866
  var root5 = from_html(`<div class="flex flex-col gap-3 items-center"><div class="flex flex-col bg-surface-panel rounded-md overflow-hidden w-fit max-w-full border-2 border-purple-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-purple-500/25 text-purple-800 dark:text-purple-400 uppercase tracking-wider">Blend (Difference)</h3> <div class="p-2"><div class="relative flex invert"><!> <!> <!></div></div></div></div>`);
20733
20867
  function BlendView($$anchor, $$props) {
20734
20868
  push($$props, true);
@@ -20763,7 +20897,7 @@ function BlendView($$anchor, $$props) {
20763
20897
  var node_2 = sibling(node_1, 2);
20764
20898
  {
20765
20899
  var consequent_2 = ($$anchor2) => {
20766
- var div_5 = root_33();
20900
+ var div_5 = root_34();
20767
20901
  var img_2 = child(div_5);
20768
20902
  reset(div_5);
20769
20903
  template_effect(() => set_attribute2(img_2, "src", $$props.image.actual));
@@ -20784,13 +20918,13 @@ function BlendView($$anchor, $$props) {
20784
20918
  // src/client/components/ResultsPage.svelte
20785
20919
  var root_27 = from_html(`<button> </button>`);
20786
20920
  var root_42 = from_html(`<button> </button>`);
20787
- var root_34 = from_html(`<div class="flex gap-1 flex-wrap justify-center mt-1"></div>`);
20921
+ var root_35 = from_html(`<div class="flex gap-1 flex-wrap justify-center mt-1"></div>`);
20788
20922
  var root_52 = from_html(`<div class="flex-1 flex items-center justify-center text-fg-muted text-base">No image to display</div>`);
20789
20923
  var root_62 = from_html(`<div class="max-w-xl w-full rounded-md border border-edge bg-surface-panel px-5 py-6 text-center"><h3 class="m-0 text-sm font-semibold text-fg-bright uppercase tracking-wide">Passed Visual Assertion</h3> <p class="mt-3 text-sm leading-6 text-fg-muted">Playwright reported this screenshot assertion, but did not emit an actual, expected, or diff artifact for the passing comparison.</p></div>`);
20790
- var root_7 = from_html(`<div class="w-full flex flex-col gap-3"><!></div>`);
20924
+ var root_72 = from_html(`<div class="w-full flex flex-col gap-3"><!></div>`);
20791
20925
  var root_112 = from_html(`<div class="flex flex-col bg-surface-panel rounded-md overflow-hidden min-w-0 border-2 border-red-500/60"><h3 class="m-0 px-3 py-2 text-xs font-bold bg-red-500/25 text-red-800 dark:text-red-200 uppercase tracking-wider">Actual</h3> <div class="flex items-center justify-center p-2"><img alt="Actual" class="w-auto max-w-full object-contain mx-auto" loading="lazy"/></div></div>`);
20792
20926
  var root_92 = from_html(`<div class="flex flex-col gap-3"><!></div>`);
20793
- var root_122 = from_html(`<div class="w-full flex flex-col gap-3"><!></div>`);
20927
+ var root_123 = from_html(`<div class="w-full flex flex-col gap-3"><!></div>`);
20794
20928
  var root_152 = from_html(`<button> </button>`);
20795
20929
  var root_162 = from_html(`<span class="px-1 text-fg-muted text-xs"></span>`);
20796
20930
  var root_132 = from_html(`<div class="py-2 px-5 bg-surface-alt border-t border-edge flex items-center justify-center gap-1 shrink-0"><button aria-label="Previous retry">&#8249;</button> <div class="flex gap-0.5 items-center"></div> <button aria-label="Next retry">&#8250;</button></div>`);
@@ -20861,7 +20995,7 @@ function ResultsPage($$anchor, $$props) {
20861
20995
  var node_2 = sibling(h2, 2);
20862
20996
  {
20863
20997
  var consequent_1 = ($$anchor2) => {
20864
- var div_4 = root_34();
20998
+ var div_4 = root_35();
20865
20999
  each(div_4, 21, () => get2(imageNames), index, ($$anchor3, name) => {
20866
21000
  var button_1 = root_42();
20867
21001
  var text_2 = child(button_1, true);
@@ -20900,7 +21034,7 @@ function ResultsPage($$anchor, $$props) {
20900
21034
  append($$anchor2, div_8);
20901
21035
  };
20902
21036
  var consequent_4 = ($$anchor2) => {
20903
- var div_9 = root_7();
21037
+ var div_9 = root_72();
20904
21038
  var node_4 = child(div_9);
20905
21039
  SideBySideView(node_4, {
20906
21040
  get image() {
@@ -20952,7 +21086,7 @@ function ResultsPage($$anchor, $$props) {
20952
21086
  append($$anchor2, div_10);
20953
21087
  };
20954
21088
  var consequent_9 = ($$anchor2) => {
20955
- var div_13 = root_122();
21089
+ var div_13 = root_123();
20956
21090
  var node_6 = child(div_13);
20957
21091
  BlendView(node_6, {
20958
21092
  get image() {
@@ -21087,7 +21221,10 @@ var root8 = from_html(`<div class="flex h-dvh relative max-md:flex-col overflow-
21087
21221
  function App($$anchor, $$props) {
21088
21222
  push($$props, true);
21089
21223
  let tests = proxy($$props.initialTests);
21090
- let isRunning = false;
21224
+ let isRunning = state(proxy($$props.isRunning));
21225
+ let runMessage = state(null);
21226
+ let runSnapshot = null;
21227
+ let runEventIds = /* @__PURE__ */ new Set();
21091
21228
  let openedTestPath = state(proxy([]));
21092
21229
  let filter = state(proxy({ status: null, subStrings: [] }));
21093
21230
  let viewMode = state(proxy(getViewMode()));
@@ -21275,9 +21412,67 @@ function App($$anchor, $$props) {
21275
21412
  });
21276
21413
  recalcAllSuiteStatuses(tests);
21277
21414
  }
21278
- function handleStart() {
21415
+ async function handleStart(filters, target) {
21416
+ if (get2(isRunning) || runSnapshot !== null) {
21417
+ set(runMessage, "A run is already in progress");
21418
+ return;
21419
+ }
21420
+ const markTarget = target ?? tests;
21421
+ runSnapshot = captureTestStatuses(markTarget);
21422
+ runEventIds = /* @__PURE__ */ new Set();
21423
+ markTestsPending(markTarget);
21424
+ recalcAllSuiteStatuses(tests);
21425
+ const res = await fetch("/api/run", {
21426
+ method: "POST",
21427
+ headers: { "Content-Type": "application/json" },
21428
+ body: JSON.stringify(filters ?? {})
21429
+ });
21430
+ if (res.status === 409 || res.status === 400) {
21431
+ if (runSnapshot !== null) {
21432
+ restoreTestStatuses(tests, runSnapshot, runEventIds);
21433
+ recalcAllSuiteStatuses(tests);
21434
+ runSnapshot = null;
21435
+ runEventIds = /* @__PURE__ */ new Set();
21436
+ }
21437
+ const body = await res.json().catch(() => null);
21438
+ set(
21439
+ runMessage,
21440
+ body?.reason === "already-running" ? "A run is already in progress" : body?.reason === "no-tests" ? "No tests selected" : "Connect a Playwright reporter to enable running",
21441
+ true
21442
+ );
21443
+ }
21279
21444
  }
21280
- function handleStop() {
21445
+ function finalizeRunSnapshot() {
21446
+ if (runSnapshot === null) return;
21447
+ restoreTestStatuses(tests, runSnapshot, runEventIds);
21448
+ recalcAllSuiteStatuses(tests);
21449
+ if (runEventIds.size === 0) {
21450
+ set(runMessage, "Run produced no results \u2014 see the server terminal for details.");
21451
+ }
21452
+ runSnapshot = null;
21453
+ runEventIds = /* @__PURE__ */ new Set();
21454
+ }
21455
+ function toDescriptor(test) {
21456
+ const { file: file2, line, column } = test.location ?? {};
21457
+ if (file2 === void 0 || line === void 0) return null;
21458
+ return {
21459
+ file: file2,
21460
+ line,
21461
+ column,
21462
+ projectName: test.projectName,
21463
+ titlePath: [...test.titlePath ?? [], test.title]
21464
+ };
21465
+ }
21466
+ function handleRunItem(item) {
21467
+ const descriptors = (isTest(item) ? [item] : getAllTests(item)).map(toDescriptor).filter((d) => d !== null);
21468
+ if (descriptors.length === 0) {
21469
+ set(runMessage, "Cannot run: no source location for the selected test(s)");
21470
+ return;
21471
+ }
21472
+ handleStart({ tests: descriptors }, item);
21473
+ }
21474
+ async function handleStop() {
21475
+ await fetch("/api/stop", { method: "POST" });
21281
21476
  }
21282
21477
  function handleImageChange(name) {
21283
21478
  set(imageName, name, true);
@@ -21320,6 +21515,7 @@ function App($$anchor, $$props) {
21320
21515
  const current = collectTestsById(tests);
21321
21516
  current[msg.data.id] = msg.data;
21322
21517
  syncTreeState(tests, current);
21518
+ if (runSnapshot !== null) runEventIds.add(msg.data.id);
21323
21519
  break;
21324
21520
  }
21325
21521
  case "run-end": {
@@ -21339,6 +21535,16 @@ function App($$anchor, $$props) {
21339
21535
  }
21340
21536
  case "approve":
21341
21537
  break;
21538
+ case "run-status": {
21539
+ if (msg.data.running) {
21540
+ set(isRunning, true);
21541
+ set(runMessage, null);
21542
+ } else {
21543
+ set(isRunning, false);
21544
+ finalizeRunSnapshot();
21545
+ }
21546
+ break;
21547
+ }
21342
21548
  }
21343
21549
  };
21344
21550
  ws.onmessage = handleMessage;
@@ -21363,7 +21569,9 @@ function App($$anchor, $$props) {
21363
21569
  get isReport() {
21364
21570
  return $$props.isReport;
21365
21571
  },
21366
- isRunning,
21572
+ get isRunning() {
21573
+ return get2(isRunning);
21574
+ },
21367
21575
  get isUpdateMode() {
21368
21576
  return $$props.isUpdateMode;
21369
21577
  },
@@ -21373,6 +21581,12 @@ function App($$anchor, $$props) {
21373
21581
  get approvalMessage() {
21374
21582
  return $$props.approvalMessage;
21375
21583
  },
21584
+ get runEnabled() {
21585
+ return $$props.runEnabled;
21586
+ },
21587
+ get runMessage() {
21588
+ return get2(runMessage);
21589
+ },
21376
21590
  get filter() {
21377
21591
  return get2(filter);
21378
21592
  },
@@ -21385,6 +21599,7 @@ function App($$anchor, $$props) {
21385
21599
  onToggle: handleSuiteToggle,
21386
21600
  onStart: handleStart,
21387
21601
  onStop: handleStop,
21602
+ onRun: handleRunItem,
21388
21603
  onApprove: handleApproveAndGoNext,
21389
21604
  onNext: handleGoToNextFailed,
21390
21605
  onApproveAll: handleApproveAllTests
@@ -21460,7 +21675,9 @@ function loadBootstrapData() {
21460
21675
  isUpdateMode: parsed.report.isUpdateMode,
21461
21676
  liveUpdates: parsed.liveUpdates,
21462
21677
  approvalEnabled: parsed.approvalEnabled,
21463
- approvalMessage: parsed.approvalMessage
21678
+ approvalMessage: parsed.approvalMessage,
21679
+ runEnabled: parsed.runEnabled ?? false,
21680
+ isRunning: parsed.report.isRunning ?? false
21464
21681
  };
21465
21682
  }
21466
21683
  async function loadReportData() {
@@ -21475,7 +21692,9 @@ async function loadReportData() {
21475
21692
  isReport: true,
21476
21693
  isUpdateMode: parsed.isUpdateMode ?? false,
21477
21694
  liveUpdates: true,
21478
- approvalEnabled: true
21695
+ approvalEnabled: true,
21696
+ runEnabled: parsed.runEnabled ?? false,
21697
+ isRunning: parsed.isRunning ?? false
21479
21698
  };
21480
21699
  }
21481
21700
  var handleApprove = async (id, retry, image) => {
@@ -21503,6 +21722,8 @@ mount(App, {
21503
21722
  liveUpdates: initialState.liveUpdates,
21504
21723
  approvalEnabled: initialState.approvalEnabled,
21505
21724
  approvalMessage: initialState.approvalMessage,
21725
+ runEnabled: initialState.runEnabled,
21726
+ isRunning: initialState.isRunning,
21506
21727
  onApprove: handleApprove,
21507
21728
  onApproveAll: handleApproveAll
21508
21729
  }