@crvy/rprtr 0.1.3 → 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 (45) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +15 -6
  3. package/dist/{chunk-X2TCDD54.js → chunk-HAFWYUNO.js} +204 -137
  4. package/dist/{chunk-F7NSIHCV.js → chunk-RFZGJSL3.js} +448 -128
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +6 -4
  7. package/dist/index.css +29 -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/path-utils.d.ts +3 -0
  12. package/dist/path-utils.d.ts.map +1 -0
  13. package/dist/report-state.d.ts +3 -1
  14. package/dist/report-state.d.ts.map +1 -1
  15. package/dist/report-utils.d.ts.map +1 -1
  16. package/dist/reporter-utils.d.ts +0 -1
  17. package/dist/reporter-utils.d.ts.map +1 -1
  18. package/dist/reporter.cjs +232 -170
  19. package/dist/reporter.d.ts +1 -1
  20. package/dist/reporter.d.ts.map +1 -1
  21. package/dist/reporter.js +8 -6
  22. package/dist/schemas/http.d.ts +44 -0
  23. package/dist/schemas/http.d.ts.map +1 -0
  24. package/dist/schemas.d.ts +25 -6
  25. package/dist/schemas.d.ts.map +1 -1
  26. package/dist/server/app.d.ts +6 -0
  27. package/dist/server/app.d.ts.map +1 -1
  28. package/dist/server/artifact-routes.d.ts.map +1 -1
  29. package/dist/server/handlers.d.ts +3 -0
  30. package/dist/server/handlers.d.ts.map +1 -1
  31. package/dist/server/playwright-config.d.ts +2 -0
  32. package/dist/server/playwright-config.d.ts.map +1 -0
  33. package/dist/server/routes-context.d.ts +13 -0
  34. package/dist/server/routes-context.d.ts.map +1 -0
  35. package/dist/server/routes.d.ts +6 -1
  36. package/dist/server/routes.d.ts.map +1 -1
  37. package/dist/server/run-controller.d.ts +85 -0
  38. package/dist/server/run-controller.d.ts.map +1 -0
  39. package/dist/server/run-routes.d.ts +3 -0
  40. package/dist/server/run-routes.d.ts.map +1 -0
  41. package/dist/server.cjs +668 -286
  42. package/dist/server.js +2 -2
  43. package/dist/types.d.ts +6 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -5,6 +5,90 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.2] - 2026-07-03
9
+
10
+ ### Miscellaneous
11
+
12
+ - Update repository URLs to creevey/rprtr
13
+ ## [0.2.1] - 2026-07-03
14
+ ## [0.2.0] - 2026-07-03
15
+
16
+ ### Added
17
+
18
+ - **types:** Add run-status variant to ClientWebSocketMessage
19
+ - **reporter:** Include configFile and cwd in register message
20
+ - **server:** Add RunController for spawning playwright test
21
+ - **server:** Persist register payload configFile and cwd as runContext
22
+ - **server:** Instantiate RunController in createServerApp and dispose on close
23
+ - **server:** Add POST /api/run and /api/stop, surface runEnabled in /api/report
24
+ - **client:** Wire Start/Stop buttons to /api/run and /api/stop
25
+ - **client:** Add per-test and per-suite run buttons to sidebar tree
26
+ - **run-controller:** Resolve playwright via project package manager
27
+ - **run-controller:** Descriptor-based filter with positional translation
28
+ - **run-controller:** --test-list for suites with version fallback
29
+ - **client:** Descriptor filters, no-location feedback, isRunning guard
30
+ - **client:** Add captureTestStatuses/restoreTestStatuses helpers
31
+ - **client:** Revert orphaned pending on no-event run
32
+
33
+ ### Changed
34
+
35
+ - **schemas:** Extract HTTP request schemas to schemas/http.ts
36
+ - **schemas:** Use discriminatedUnion for Run/Stop responses
37
+ - **run-controller:** Document cwd param and test launch fallback
38
+ - **reporter:** Make onBegin synchronous and defer screenshotDir creation
39
+ - **client:** Reset runEventIds on rejected-start path
40
+
41
+ ### Documentation
42
+
43
+ - **spec:** Run tests from UI design
44
+ - **plan:** Run tests from UI implementation plan
45
+ - **spec:** Run-from-UI robustness design
46
+ - **plan:** Run-from-UI robustness implementation plan
47
+ - **spec:** Stuck-pending run recovery design
48
+ - **plan:** Stuck-pending run recovery implementation plan
49
+
50
+ ### Fixed
51
+
52
+ - **run-controller:** Guard against duplicate exit/error cleanup
53
+ - **reporter:** Honor CRVY_RPRTR_SERVER_URL env as fallback serverUrl
54
+ - **run-controller:** Override reporters and strip CI env for UI-triggered runs
55
+ - **run-from-ui:** Row height, re-run status, filtered-run scope, startup config
56
+ - **run-from-ui:** Mark tests pending on start, use file:line:column for precise re-runs
57
+ - **run-controller:** Resolve @crvy/rprtr from project cwd then server module
58
+ - **run-controller:** Clean up test-list temp file on spawn throw
59
+ - **knip:** Enable production-mode analysis in strict checks
60
+
61
+ ### Styling
62
+
63
+ - **client:** Match semicolon convention and destructure location
64
+ - **docs:** Reformat CLI options table
65
+
66
+ ### Testing
67
+
68
+ - **client:** Cover undefined-restore and empty/bare-root edge cases
69
+ ## [0.1.4] - 2026-06-19
70
+
71
+ ### Added
72
+
73
+ - **path-utils:** Add cross-platform absolute path helpers
74
+ - **server:** Log diagnostic when /file URL cannot resolve on host OS
75
+
76
+ ### Documentation
77
+
78
+ - **plans:** Cross-OS path handling and stale actual URL on passing rerun
79
+ - Document cross-OS artifact loading limitations
80
+
81
+ ### Fixed
82
+
83
+ - **report-utils:** Route Windows-style absolute paths to /file on any host
84
+ - **server:** Check pre-resolve path for foreign-OS diagnostic
85
+ - **report-state:** Drop stale comparison actual URL on passed reruns
86
+ - **report-state:** Strip stale actual/diff when preserving baseline-only images
87
+
88
+ ### Testing
89
+
90
+ - **path-utils:** Cover forward-slash UNC form
91
+ - **server-routes:** Cover baseline enrichment after failed→passed progression
8
92
  ## [0.1.3] - 2026-06-18
9
93
 
10
94
  ### Fixed
package/README.md CHANGED
@@ -74,13 +74,14 @@ If `artifact-dir` is provided, the CLI treats it as the directory containing:
74
74
 
75
75
  Explicit flags override the paths derived from `artifact-dir`.
76
76
 
77
- | Option | Short | Default | Description |
78
- | ------------------ | ----- | --------------- | ----------------------------------------------------------------------------------------- |
79
- | `--port` | `-p` | `3000` | Server port |
80
- | `--screenshot-dir` | `-s` | `./screenshots` | Screenshot directory path |
81
- | `--report-path` | `-r` | `./report.json` | Report JSON file path or directory containing `report.json` and `crvy-rprtr-*.json` files |
77
+ | Option | Short | Default | Description |
78
+ | ------------------ | ----- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79
+ | `--port` | `-p` | `3000` | Server port |
80
+ | `--screenshot-dir` | `-s` | `./screenshots` | Screenshot directory path |
81
+ | `--report-path` | `-r` | `./report.json` | Report JSON file path or directory containing `report.json` and `crvy-rprtr-*.json` files |
82
+ | `--config` | `-c` | auto-detect | Playwright config path used to enable the run buttons at startup. When omitted, the server discovers `playwright.config.*` in the working directory. A registering reporter always overrides this. |
82
83
 
83
- The CLI currently exposes only these flags. Approval-routing resolver overrides are available through the programmatic server API, not additional CLI flags.
84
+ When the server can resolve a Playwright config (via `--config` or auto-discovery, or once a reporter registers), the sidebar shows Start/Stop and per-test run buttons that trigger `playwright test` without leaving the browser. Approval-routing resolver overrides are available through the programmatic server API, not additional CLI flags.
84
85
 
85
86
  ## How It Works
86
87
 
@@ -113,6 +114,14 @@ Approval routing uses the same resolver, but it reads its resolver settings from
113
114
 
114
115
  When the server is running, Crvy Rprtr also refreshes the UI after report JSON or screenshot artifacts change on disk.
115
116
 
117
+ ## Cross-OS Artifact Loading
118
+
119
+ Crvy Rprtr stores image URLs exactly as the reporter that produced them saw them. In live mode (server running during the test run), failure artifacts are referenced by absolute path under `/file/<encoded>`; in CI/offline mode, attachments are copied into `screenshots/` and referenced by relative path under `/screenshots/`.
120
+
121
+ If you generate a report on one operating system and then open it on another (for example, downloading a Windows CI runner's `report.json` onto a macOS laptop), absolute-path `/file/...` URLs cannot resolve: the file is not on your filesystem. Crvy Rprtr logs a single diagnostic line per such request and returns 404. The `/screenshots/...` and `/baseline/...` URLs remain portable because they resolve through the server's `screenshotDir` or snapshot resolver.
122
+
123
+ For fully portable artifact loading across operating systems, run the reporter in CI mode (`ci: true`) and ship the `screenshots/` directory alongside the report JSON.
124
+
116
125
  ## Programmatic API
117
126
 
118
127
  ```ts
@@ -170,169 +170,211 @@ function resolveBaselineTargets(input) {
170
170
  }
171
171
 
172
172
  // src/schemas.ts
173
+ import { z as z2 } from "zod";
174
+
175
+ // src/schemas/http.ts
173
176
  import { z } from "zod";
174
- var LocationSchema = z.object({
177
+ var ApproveRequestBodySchema = z.object({
178
+ id: z.string(),
179
+ retry: z.number(),
180
+ image: z.string()
181
+ });
182
+ var RunTestDescriptorSchema = z.object({
175
183
  file: z.string(),
176
- line: z.number()
184
+ line: z.number(),
185
+ column: z.number().optional(),
186
+ projectName: z.string().optional(),
187
+ titlePath: z.array(z.string())
177
188
  });
178
- var VisualSourceSchema = z.enum(["comparison", "baseline-only", "declared-only"]);
179
- var ImagesSchema = z.object({
180
- actual: z.string().optional(),
181
- expect: z.string().optional(),
182
- diff: z.string().optional(),
183
- error: z.string().optional(),
184
- source: VisualSourceSchema.optional()
189
+ var RunRequestBodySchema = z.object({
190
+ tests: z.array(RunTestDescriptorSchema).optional()
185
191
  });
186
- var ScreenshotDeclarationSchema = z.discriminatedUnion("kind", [
192
+ var RunResponseSchema = z.discriminatedUnion("ok", [
193
+ z.object({ ok: z.literal(true) }),
187
194
  z.object({
188
- visualName: z.string(),
189
- kind: z.literal("named"),
190
- declaredName: z.string(),
191
- snapshotBaseName: z.string(),
192
- occurrenceIndex: z.number()
193
- }),
195
+ ok: z.literal(false),
196
+ reason: z.enum(["no-config", "already-running", "no-tests"])
197
+ })
198
+ ]);
199
+ var StopResponseSchema = z.discriminatedUnion("ok", [
200
+ z.object({ ok: z.literal(true) }),
194
201
  z.object({
195
- visualName: z.string(),
196
- kind: z.literal("unnamed"),
197
- occurrenceIndex: z.number()
202
+ ok: z.literal(false),
203
+ reason: z.literal("not-running")
198
204
  })
199
205
  ]);
200
- var AttachmentSchema = z.object({
201
- name: z.string(),
202
- path: z.string(),
203
- contentType: z.string()
206
+
207
+ // src/schemas.ts
208
+ var LocationSchema = z2.object({
209
+ file: z2.string(),
210
+ line: z2.number(),
211
+ column: z2.number().optional()
212
+ });
213
+ var VisualSourceSchema = z2.enum(["comparison", "baseline-only", "declared-only"]);
214
+ var ImagesSchema = z2.object({
215
+ actual: z2.string().optional(),
216
+ expect: z2.string().optional(),
217
+ diff: z2.string().optional(),
218
+ error: z2.string().optional(),
219
+ source: VisualSourceSchema.optional()
204
220
  });
205
- var TestStatusSchema = z.enum(["unknown", "pending", "running", "failed", "approved", "success", "retrying"]);
206
- var TestResultStatusSchema = z.enum(["failed", "success", "pending"]);
207
- var TestResultSchema = z.object({
221
+ var ScreenshotDeclarationSchema = z2.discriminatedUnion("kind", [
222
+ z2.object({
223
+ visualName: z2.string(),
224
+ kind: z2.literal("named"),
225
+ declaredName: z2.string(),
226
+ snapshotBaseName: z2.string(),
227
+ occurrenceIndex: z2.number()
228
+ }),
229
+ z2.object({
230
+ visualName: z2.string(),
231
+ kind: z2.literal("unnamed"),
232
+ occurrenceIndex: z2.number()
233
+ })
234
+ ]);
235
+ var AttachmentSchema = z2.object({
236
+ name: z2.string(),
237
+ path: z2.string(),
238
+ contentType: z2.string()
239
+ });
240
+ var TestStatusSchema = z2.enum(["unknown", "pending", "running", "failed", "approved", "success", "retrying"]);
241
+ var TestResultStatusSchema = z2.enum(["failed", "success", "pending"]);
242
+ var TestResultSchema = z2.object({
208
243
  status: TestResultStatusSchema,
209
- retries: z.number(),
210
- images: z.record(z.string(), ImagesSchema).optional(),
211
- visualDeclarations: z.array(ScreenshotDeclarationSchema).optional(),
212
- error: z.string().optional(),
213
- duration: z.number().optional()
244
+ retries: z2.number(),
245
+ images: z2.record(z2.string(), ImagesSchema).optional(),
246
+ visualDeclarations: z2.array(ScreenshotDeclarationSchema).optional(),
247
+ error: z2.string().optional(),
248
+ duration: z2.number().optional()
214
249
  });
215
- var TestDataSchema = z.object({
216
- id: z.string(),
217
- titlePath: z.array(z.string()),
218
- browser: z.string(),
219
- projectName: z.string().optional(),
220
- title: z.string(),
221
- skip: z.union([z.boolean(), z.string()]).optional(),
222
- retries: z.number().optional(),
250
+ var TestDataSchema = z2.object({
251
+ id: z2.string(),
252
+ titlePath: z2.array(z2.string()),
253
+ browser: z2.string(),
254
+ projectName: z2.string().optional(),
255
+ title: z2.string(),
256
+ skip: z2.union([z2.boolean(), z2.string()]).optional(),
257
+ retries: z2.number().optional(),
223
258
  status: TestStatusSchema.optional(),
224
- results: z.array(TestResultSchema).optional(),
225
- approved: z.record(z.string(), z.number()).nullable().optional(),
226
- attachments: z.array(AttachmentSchema).optional(),
259
+ results: z2.array(TestResultSchema).optional(),
260
+ approved: z2.record(z2.string(), z2.number()).nullable().optional(),
261
+ attachments: z2.array(AttachmentSchema).optional(),
227
262
  location: LocationSchema.optional()
228
263
  });
229
264
  var CrvyRprtrTestSchema = TestDataSchema.extend({
230
- checked: z.boolean()
265
+ checked: z2.boolean()
231
266
  });
232
- var CrvyRprtrSuiteSchema = z.lazy(
233
- () => z.object({
234
- path: z.array(z.string()),
235
- skip: z.boolean(),
267
+ var CrvyRprtrSuiteSchema = z2.lazy(
268
+ () => z2.object({
269
+ path: z2.array(z2.string()),
270
+ skip: z2.boolean(),
236
271
  status: TestStatusSchema.optional(),
237
- opened: z.boolean(),
238
- checked: z.boolean(),
239
- indeterminate: z.boolean(),
240
- children: z.record(z.string(), z.union([CrvyRprtrSuiteSchema, CrvyRprtrTestSchema])).optional()
272
+ opened: z2.boolean(),
273
+ checked: z2.boolean(),
274
+ indeterminate: z2.boolean(),
275
+ children: z2.record(z2.string(), z2.union([CrvyRprtrSuiteSchema, CrvyRprtrTestSchema])).optional()
241
276
  })
242
277
  );
243
- var IncomingWebSocketMessageSchema = z.object({
244
- type: z.enum(["test-begin", "test-end", "run-end", "approve", "sync", "register"]),
245
- data: z.unknown()
278
+ var IncomingWebSocketMessageSchema = z2.object({
279
+ type: z2.enum(["test-begin", "test-end", "run-end", "approve", "sync", "register"]),
280
+ data: z2.unknown()
246
281
  });
247
- var WebSocketMessageSchema = z.discriminatedUnion("type", [
248
- z.object({ type: z.literal("test-begin"), data: TestDataSchema }),
249
- z.object({ type: z.literal("test-update"), data: TestDataSchema }),
250
- z.object({
251
- type: z.literal("run-end"),
252
- data: z.object({
253
- status: z.enum(["passed", "failed", "skipped"]),
254
- removedTestIds: z.array(z.string())
282
+ var WebSocketMessageSchema = z2.discriminatedUnion("type", [
283
+ z2.object({ type: z2.literal("test-begin"), data: TestDataSchema }),
284
+ z2.object({ type: z2.literal("test-update"), data: TestDataSchema }),
285
+ z2.object({
286
+ type: z2.literal("run-end"),
287
+ data: z2.object({
288
+ status: z2.enum(["passed", "failed", "skipped"]),
289
+ removedTestIds: z2.array(z2.string())
255
290
  })
256
291
  }),
257
- z.object({
258
- type: z.literal("sync"),
259
- data: z.object({
260
- tests: z.record(z.string(), TestDataSchema),
261
- isUpdateMode: z.boolean().optional()
292
+ z2.object({
293
+ type: z2.literal("sync"),
294
+ data: z2.object({
295
+ tests: z2.record(z2.string(), TestDataSchema),
296
+ isUpdateMode: z2.boolean().optional()
262
297
  })
263
298
  }),
264
- z.object({ type: z.literal("approve"), data: z.unknown() })
299
+ z2.object({ type: z2.literal("approve"), data: z2.unknown() }),
300
+ z2.object({
301
+ type: z2.literal("run-status"),
302
+ data: z2.object({
303
+ running: z2.boolean()
304
+ })
305
+ })
265
306
  ]);
266
- var TestBeginDataSchema = z.object({
267
- id: z.string(),
268
- title: z.string(),
269
- titlePath: z.array(z.string()),
270
- browser: z.string(),
271
- projectName: z.string().optional(),
307
+ var TestBeginDataSchema = z2.object({
308
+ id: z2.string(),
309
+ title: z2.string(),
310
+ titlePath: z2.array(z2.string()),
311
+ browser: z2.string(),
312
+ projectName: z2.string().optional(),
272
313
  location: LocationSchema
273
314
  });
274
- var TestEndDataSchema = z.object({
275
- id: z.string(),
276
- status: z.enum(["passed", "failed", "skipped"]),
277
- attachments: z.array(AttachmentSchema),
278
- visualNames: z.array(z.string()).default([]),
279
- visualDeclarations: z.preprocess(
315
+ var TestEndDataSchema = z2.object({
316
+ id: z2.string(),
317
+ status: z2.enum(["passed", "failed", "skipped"]),
318
+ attachments: z2.array(AttachmentSchema),
319
+ visualNames: z2.array(z2.string()).default([]),
320
+ visualDeclarations: z2.preprocess(
280
321
  (value) => value === null ? void 0 : value,
281
- z.array(ScreenshotDeclarationSchema).optional()
322
+ z2.array(ScreenshotDeclarationSchema).optional()
282
323
  ),
283
- error: z.string().optional(),
284
- duration: z.number().optional()
324
+ error: z2.string().optional(),
325
+ duration: z2.number().optional()
285
326
  });
286
- var RunEndDataSchema = z.object({
287
- status: z.enum(["passed", "failed", "skipped"])
327
+ var RunEndDataSchema = z2.object({
328
+ status: z2.enum(["passed", "failed", "skipped"])
288
329
  });
289
- var RegisterDataSchema = z.object({
290
- playwrightSnapshotDir: z.string().optional(),
291
- playwrightTestDir: z.string().optional(),
292
- playwrightSnapshotPathTemplate: z.string().optional(),
293
- playwrightToHaveScreenshotPathTemplate: z.string().optional()
330
+ var RegisterDataSchema = z2.object({
331
+ playwrightSnapshotDir: z2.string().optional(),
332
+ playwrightTestDir: z2.string().optional(),
333
+ playwrightSnapshotPathTemplate: z2.string().optional(),
334
+ playwrightToHaveScreenshotPathTemplate: z2.string().optional(),
335
+ configFile: z2.string().optional(),
336
+ cwd: z2.string().optional()
294
337
  });
295
- var ReportDataSchema = z.object({
296
- isRunning: z.boolean(),
297
- tests: z.record(z.string(), TestDataSchema),
298
- browsers: z.array(z.string()),
299
- isUpdateMode: z.boolean(),
300
- screenshotDir: z.string()
338
+ var ReportDataSchema = z2.object({
339
+ isRunning: z2.boolean(),
340
+ tests: z2.record(z2.string(), TestDataSchema),
341
+ browsers: z2.array(z2.string()),
342
+ isUpdateMode: z2.boolean(),
343
+ screenshotDir: z2.string()
301
344
  });
302
- var LoadedReportDataSchema = z.object({
303
- tests: z.record(z.string(), TestDataSchema).optional(),
304
- isUpdateMode: z.boolean().optional()
345
+ var LoadedReportDataSchema = z2.object({
346
+ tests: z2.record(z2.string(), TestDataSchema).optional(),
347
+ isUpdateMode: z2.boolean().optional()
305
348
  });
306
- var OfflineEventSchema = z.object({
307
- type: z.enum(["test-begin", "test-end", "run-end"]),
308
- data: z.unknown(),
309
- timestamp: z.number(),
310
- workerIndex: z.number()
349
+ var OfflineEventSchema = z2.object({
350
+ type: z2.enum(["test-begin", "test-end", "run-end"]),
351
+ data: z2.unknown(),
352
+ timestamp: z2.number(),
353
+ workerIndex: z2.number()
311
354
  });
312
- var OfflineReportSchema = z.object({
313
- version: z.number(),
314
- generatedAt: z.string(),
315
- workers: z.number(),
316
- events: z.array(OfflineEventSchema)
317
- });
318
- var ApproveRequestBodySchema = z.object({
319
- id: z.string(),
320
- retry: z.number(),
321
- image: z.string()
355
+ var OfflineReportSchema = z2.object({
356
+ version: z2.number(),
357
+ generatedAt: z2.string(),
358
+ workers: z2.number(),
359
+ events: z2.array(OfflineEventSchema)
322
360
  });
323
- var ReportApiResponseSchema = z.object({
324
- tests: z.record(z.string(), TestDataSchema),
325
- isUpdateMode: z.boolean().optional()
361
+ var ReportApiResponseSchema = z2.object({
362
+ tests: z2.record(z2.string(), TestDataSchema),
363
+ isUpdateMode: z2.boolean().optional(),
364
+ isRunning: z2.boolean().optional(),
365
+ runEnabled: z2.boolean().optional()
326
366
  });
327
- var ClientBootstrapDataSchema = z.object({
367
+ var ClientBootstrapDataSchema = z2.object({
328
368
  report: ReportApiResponseSchema.extend({
329
- isUpdateMode: z.boolean()
369
+ isUpdateMode: z2.boolean()
330
370
  }),
331
- liveUpdates: z.boolean(),
332
- approvalEnabled: z.boolean(),
333
- approvalMessage: z.string().optional()
371
+ liveUpdates: z2.boolean(),
372
+ approvalEnabled: z2.boolean(),
373
+ approvalMessage: z2.string().optional(),
374
+ runEnabled: z2.boolean().optional(),
375
+ isRunning: z2.boolean().optional()
334
376
  });
335
- var ImagesViewModeSchema = z.enum(["side-by-side", "swap", "slide", "blend"]);
377
+ var ImagesViewModeSchema = z2.enum(["side-by-side", "swap", "slide", "blend"]);
336
378
  function safeParse(schema, data) {
337
379
  const result = schema.safeParse(data);
338
380
  if (result.success) {
@@ -341,8 +383,26 @@ function safeParse(schema, data) {
341
383
  return null;
342
384
  }
343
385
 
386
+ // src/path-utils.ts
387
+ var posixAbsolutePattern = /^\//u;
388
+ var windowsDrivePattern = /^[A-Za-z]:[\\/]/u;
389
+ var windowsUncPattern = /^[\\/][\\/]/u;
390
+ function isPosixAbsolutePath(p) {
391
+ return posixAbsolutePattern.test(p);
392
+ }
393
+ function isWindowsAbsolutePath(p) {
394
+ return windowsDrivePattern.test(p) || windowsUncPattern.test(p);
395
+ }
396
+ function isAnyAbsolutePath(p) {
397
+ return isPosixAbsolutePath(p) || isWindowsAbsolutePath(p);
398
+ }
399
+ function isForeignAbsolutePath(p, hostPlatform) {
400
+ if (!isAnyAbsolutePath(p)) return false;
401
+ if (hostPlatform === "win32") return !isWindowsAbsolutePath(p);
402
+ return !isPosixAbsolutePath(p);
403
+ }
404
+
344
405
  // src/report-utils.ts
345
- import { isAbsolute } from "path";
346
406
  function normalizeScreenshotsBaseUrl(baseUrl) {
347
407
  return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
348
408
  }
@@ -387,7 +447,7 @@ function attachmentsToImages(attachments, screenshotsBaseUrl = "/screenshots/")
387
447
  const role = match[2];
388
448
  if (baseName === null || baseName === void 0 || role === null || role === void 0) continue;
389
449
  images[baseName] ??= {};
390
- const url = isAbsolute(attachment.path) ? `/file/${encodeURIComponent(attachment.path)}` : `${baseUrl}${attachment.path}`;
450
+ const url = isAnyAbsolutePath(attachment.path) ? `/file/${encodeURIComponent(attachment.path)}` : `${baseUrl}${attachment.path}`;
391
451
  const img = images[baseName];
392
452
  if (img !== null && img !== void 0) {
393
453
  if (role === "actual") img.actual = url;
@@ -422,9 +482,6 @@ function isCurrentArtifact(image) {
422
482
  }
423
483
  function isReusablePassingImage(image) {
424
484
  const source = image.source ?? classifyImage(image);
425
- if (source === "comparison") {
426
- return image.actual !== void 0 && image.diff === void 0;
427
- }
428
485
  return source === "baseline-only";
429
486
  }
430
487
  function hasReusablePassingImages(images) {
@@ -445,8 +502,8 @@ function preservePreviousPassingImages(test, status, images) {
445
502
  return {
446
503
  ...currentImages,
447
504
  [name]: {
448
- ...previousImage,
449
- source: previousImage.source ?? classifyImage(previousImage)
505
+ expect: previousImage.expect,
506
+ source: "baseline-only"
450
507
  }
451
508
  };
452
509
  }, images);
@@ -469,7 +526,12 @@ function createMutableReportState(screenshotDir = "./screenshots") {
469
526
  function applyTestBeginEvent(state, data) {
470
527
  const { id, title, titlePath, browser, projectName, location } = data;
471
528
  state.currentRunIds.add(id);
472
- state.reportData.tests[id] ??= {
529
+ const existing = state.reportData.tests[id];
530
+ if (existing !== void 0) {
531
+ existing.status = "running";
532
+ return existing;
533
+ }
534
+ const created = {
473
535
  id,
474
536
  titlePath: titlePath ?? [],
475
537
  browser: browser ?? "",
@@ -481,7 +543,8 @@ function applyTestBeginEvent(state, data) {
481
543
  location,
482
544
  status: "running"
483
545
  };
484
- return state.reportData.tests[id];
546
+ state.reportData.tests[id] = created;
547
+ return created;
485
548
  }
486
549
  function applyTestEndEvent(state, data, options = {}) {
487
550
  const test = state.reportData.tests[data.id];
@@ -519,11 +582,13 @@ function applyTestEndEvent(state, data, options = {}) {
519
582
  diffCount
520
583
  };
521
584
  }
522
- function finalizeRunEvent(state) {
585
+ function finalizeRunEvent(state, options = {}) {
523
586
  state.reportData.isRunning = false;
524
- state.reportData.tests = Object.fromEntries(
525
- Object.entries(state.reportData.tests).filter(([id]) => state.currentRunIds.has(id))
526
- );
587
+ if (options.preserveNonCurrent !== true) {
588
+ state.reportData.tests = Object.fromEntries(
589
+ Object.entries(state.reportData.tests).filter(([id]) => state.currentRunIds.has(id))
590
+ );
591
+ }
527
592
  state.currentRunIds.clear();
528
593
  const runTests = Object.values(state.reportData.tests).filter((test) => test !== void 0);
529
594
  return {
@@ -534,10 +599,13 @@ function finalizeRunEvent(state) {
534
599
  }
535
600
 
536
601
  export {
602
+ isForeignAbsolutePath,
537
603
  createMutableReportState,
538
604
  applyTestBeginEvent,
539
605
  applyTestEndEvent,
540
606
  finalizeRunEvent,
607
+ ApproveRequestBodySchema,
608
+ RunRequestBodySchema,
541
609
  IncomingWebSocketMessageSchema,
542
610
  TestBeginDataSchema,
543
611
  TestEndDataSchema,
@@ -545,7 +613,6 @@ export {
545
613
  RegisterDataSchema,
546
614
  LoadedReportDataSchema,
547
615
  OfflineReportSchema,
548
- ApproveRequestBodySchema,
549
616
  ClientBootstrapDataSchema,
550
617
  safeParse,
551
618
  withResolvedVisualNames,