@crvy/rprtr 0.0.7 → 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.
package/dist/reporter.js CHANGED
@@ -8,10 +8,24 @@ import {
8
8
  finalizeRunEvent,
9
9
  resolveBaselineTargets,
10
10
  safeParse
11
- } from "./chunk-JGS2VWQP.js";
11
+ } from "./chunk-EJRLZZ22.js";
12
12
 
13
13
  // src/reporter.ts
14
14
  import { existsSync } from "fs";
15
+ import { mkdir as mkdir3 } from "fs/promises";
16
+ import { dirname as dirname3, join as join2 } from "path";
17
+ import pLimit2 from "p-limit";
18
+
19
+ // src/debug-log.ts
20
+ var isDebug = () => process.env.DEBUG !== void 0 && process.env.DEBUG !== "";
21
+ var log = (...args) => {
22
+ if (isDebug()) console.log(...args);
23
+ };
24
+ var logError = (...args) => {
25
+ if (isDebug()) console.error(...args);
26
+ };
27
+
28
+ // src/reporter-artifact-ops.ts
15
29
  import { copyFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
16
30
  import { dirname as dirname2, join } from "path";
17
31
  import pLimit from "p-limit";
@@ -137,6 +151,98 @@ async function writeReportArtifact(options) {
137
151
  await writeFile(reportHtmlPath, html);
138
152
  }
139
153
 
154
+ // src/reporter-artifact-ops.ts
155
+ var CURRENT_DIRECTORY_ARTIFACT_SEGMENT = "+dot+";
156
+ var PARENT_DIRECTORY_ARTIFACT_SEGMENT = "+dotdot+";
157
+ var MAX_CONCURRENT_FILE_OPS = 5;
158
+ var SAFE_ARTIFACT_CHARACTER = /^[A-Za-z0-9._-]$/;
159
+ function encodeArtifactPathSegment(segment) {
160
+ if (segment === ".") return CURRENT_DIRECTORY_ARTIFACT_SEGMENT;
161
+ if (segment === "..") return PARENT_DIRECTORY_ARTIFACT_SEGMENT;
162
+ return Array.from(
163
+ segment,
164
+ (character) => SAFE_ARTIFACT_CHARACTER.test(character) ? character : encodeURIComponent(character)
165
+ ).join("");
166
+ }
167
+ function safeArtifactPath(name) {
168
+ return name.split("/").map(encodeArtifactPathSegment).join("/");
169
+ }
170
+ function sanitizeId(id) {
171
+ return id.replace(/[^a-zA-Z0-9-_]/g, "_");
172
+ }
173
+ async function copyResolvedBaseline(safeTestId, testScreenshotDir, target, savedAttachments) {
174
+ const attachmentName = `${target.attachmentBaseName}-expected.png`;
175
+ const artifactPath = safeArtifactPath(attachmentName);
176
+ const destPath = join(testScreenshotDir, artifactPath);
177
+ try {
178
+ await mkdir2(dirname2(destPath), { recursive: true });
179
+ await copyFile(target.snapshotPath, destPath);
180
+ savedAttachments.push({ name: attachmentName, path: `${safeTestId}/${artifactPath}`, contentType: "image/png" });
181
+ log(`[CrvyRprtr] Attached baseline: ${target.snapshotPath}`);
182
+ } catch {
183
+ }
184
+ }
185
+ async function saveAttachments(screenshotDir, testId, result) {
186
+ const savedAttachments = [];
187
+ const safeTestId = sanitizeId(testId);
188
+ const testScreenshotDir = join(screenshotDir, safeTestId);
189
+ const limit = pLimit(MAX_CONCURRENT_FILE_OPS);
190
+ await Promise.all(
191
+ result.attachments.filter(
192
+ (attachment) => attachment.contentType === "image/png" && attachment.path !== void 0
193
+ ).map(
194
+ (attachment) => limit(async () => {
195
+ try {
196
+ const artifactPath = safeArtifactPath(attachment.name);
197
+ const destPath = join(testScreenshotDir, artifactPath);
198
+ await mkdir2(dirname2(destPath), { recursive: true });
199
+ await copyFile(attachment.path, destPath);
200
+ savedAttachments.push({
201
+ name: attachment.name,
202
+ path: `${safeTestId}/${artifactPath}`,
203
+ contentType: attachment.contentType ?? "image/png"
204
+ });
205
+ log(`[CrvyRprtr] Saved screenshot: ${destPath}`);
206
+ } catch (error) {
207
+ logError(`[CrvyRprtr] Failed to save screenshot: ${attachment.path}`, error);
208
+ savedAttachments.push({
209
+ name: attachment.name,
210
+ path: attachment.path,
211
+ contentType: attachment.contentType ?? "image/png"
212
+ });
213
+ }
214
+ })
215
+ )
216
+ );
217
+ return savedAttachments;
218
+ }
219
+ async function writeOfflineReport(runEvents, offlineReportPath, workerIndex) {
220
+ if (runEvents.length === 0) {
221
+ log("[CrvyRprtr] No offline events to write");
222
+ return;
223
+ }
224
+ try {
225
+ const report = {
226
+ version: 1,
227
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
228
+ workers: workerIndex + 1,
229
+ events: runEvents.map((event) => ({ ...event, timestamp: Date.now(), workerIndex }))
230
+ };
231
+ await writeFile2(offlineReportPath, JSON.stringify(report, null, 2));
232
+ log(`[CrvyRprtr] Wrote offline report: ${offlineReportPath}`);
233
+ } catch (error) {
234
+ logError("[CrvyRprtr] Failed to write offline report:", error);
235
+ }
236
+ }
237
+ async function writeStaticArtifact(runEvents, screenshotDir, reportHtmlPath) {
238
+ try {
239
+ await writeReportArtifact({ events: runEvents, screenshotDir, reportHtmlPath });
240
+ log(`[CrvyRprtr] Wrote report artifact: ${reportHtmlPath}`);
241
+ } catch (error) {
242
+ logError("[CrvyRprtr] Failed to write report artifact:", error);
243
+ }
244
+ }
245
+
140
246
  // src/reporter-utils.ts
141
247
  var NAMED_SCREENSHOT_STEP_TITLE = /toHaveScreenshot\((.+?)\)/;
142
248
  var UNNAMED_SCREENSHOT_STEP_TITLE = /^Expect "toHaveScreenshot"(?:\s|$)/;
@@ -207,19 +313,6 @@ function extractScreenshotDeclarations(steps) {
207
313
  }
208
314
 
209
315
  // src/reporter.ts
210
- var CURRENT_DIRECTORY_ARTIFACT_SEGMENT = "+dot+";
211
- var PARENT_DIRECTORY_ARTIFACT_SEGMENT = "+dotdot+";
212
- var MAX_CONCURRENT_FILE_OPS = 5;
213
- var SAFE_ARTIFACT_CHARACTER = /^[A-Za-z0-9._-]$/;
214
- var encodeArtifactPathSegment = (segment) => {
215
- if (segment === ".") return CURRENT_DIRECTORY_ARTIFACT_SEGMENT;
216
- if (segment === "..") return PARENT_DIRECTORY_ARTIFACT_SEGMENT;
217
- return Array.from(
218
- segment,
219
- (character) => SAFE_ARTIFACT_CHARACTER.test(character) ? character : encodeURIComponent(character)
220
- ).join("");
221
- };
222
- var safeArtifactPath = (name) => name.split("/").map(encodeArtifactPathSegment).join("/");
223
316
  var CrvyRprtr = class {
224
317
  ws = null;
225
318
  serverUrl;
@@ -247,43 +340,43 @@ var CrvyRprtr = class {
247
340
  this.playwrightToHaveScreenshotPathTemplate = options.playwrightToHaveScreenshotPathTemplate;
248
341
  }
249
342
  async onBegin(config, suite) {
250
- this.configDir = config.configFile === void 0 ? config.rootDir : dirname2(config.configFile);
251
- console.log(`[CrvyRprtr] Starting run with ${suite.allTests().length} tests`);
252
- await mkdir2(this.screenshotDir, { recursive: true });
343
+ this.configDir = config.configFile === void 0 ? config.rootDir : dirname3(config.configFile);
344
+ log(`[CrvyRprtr] Starting run with ${suite.allTests().length} tests`);
345
+ await mkdir3(this.screenshotDir, { recursive: true });
253
346
  this.connect();
254
347
  }
255
348
  connect() {
256
349
  const WebSocketConstructor = globalThis.WebSocket;
257
350
  if (typeof WebSocketConstructor !== "function") {
258
- console.log("[CrvyRprtr] WebSocket unavailable in current runtime; offline mode enabled");
351
+ log("[CrvyRprtr] WebSocket unavailable in current runtime; offline mode enabled");
259
352
  this.enableOfflineMode();
260
353
  return;
261
354
  }
262
355
  try {
263
356
  this.ws = new WebSocketConstructor(this.serverUrl);
264
357
  this.ws.onopen = () => {
265
- console.log("[CrvyRprtr] Connected to Crvy Rprtr server");
358
+ log("[CrvyRprtr] Connected to Crvy Rprtr server");
266
359
  this.isOfflineMode = false;
267
360
  for (const message of this.queue) this.ws.send(message);
268
361
  this.queue = [];
269
362
  };
270
363
  this.ws.onerror = (error) => {
271
- console.error("[CrvyRprtr] WebSocket error:", error);
364
+ logError("[CrvyRprtr] WebSocket error:", error);
272
365
  this.enableOfflineMode();
273
366
  };
274
367
  this.ws.onclose = () => {
275
- console.log("[CrvyRprtr] Disconnected from Crvy Rprtr server");
368
+ log("[CrvyRprtr] Disconnected from Crvy Rprtr server");
276
369
  this.enableOfflineMode();
277
370
  };
278
371
  } catch (error) {
279
- console.error("[CrvyRprtr] Failed to connect:", error);
372
+ logError("[CrvyRprtr] Failed to connect:", error);
280
373
  this.enableOfflineMode();
281
374
  }
282
375
  }
283
376
  enableOfflineMode() {
284
377
  if (this.isOfflineMode) return;
285
378
  this.isOfflineMode = this.hadOfflineMode = true;
286
- console.log("[CrvyRprtr] Offline mode enabled - events will be queued to file");
379
+ log("[CrvyRprtr] Offline mode enabled - events will be queued to file");
287
380
  }
288
381
  describeTitlePath(test) {
289
382
  const titlePath = [];
@@ -309,7 +402,7 @@ var CrvyRprtr = class {
309
402
  }
310
403
  async onTestEnd(test, result) {
311
404
  const screenshotDeclarations = extractScreenshotDeclarations(result.steps);
312
- const savedAttachments = await this.saveAttachments(test.id, result);
405
+ const savedAttachments = await saveAttachments(this.screenshotDir, test.id, result);
313
406
  try {
314
407
  await this.copySnapshotBaselines(test, result.status, screenshotDeclarations, savedAttachments);
315
408
  this.send({
@@ -349,113 +442,38 @@ var CrvyRprtr = class {
349
442
  snapshotPathExists: existsSync
350
443
  };
351
444
  }
352
- async copyResolvedBaseline(safeTestId, testScreenshotDir, target, savedAttachments) {
353
- const attachmentName = `${target.attachmentBaseName}-expected.png`;
354
- const artifactPath = safeArtifactPath(attachmentName);
355
- const destPath = join(testScreenshotDir, artifactPath);
356
- try {
357
- await mkdir2(dirname2(destPath), { recursive: true });
358
- await copyFile(target.snapshotPath, destPath);
359
- savedAttachments.push({ name: attachmentName, path: `${safeTestId}/${artifactPath}`, contentType: "image/png" });
360
- console.log(`[CrvyRprtr] Attached baseline: ${target.snapshotPath}`);
361
- } catch {
362
- }
363
- }
364
445
  async copySnapshotBaselines(test, status, screenshotDeclarations, savedAttachments) {
365
446
  if (status !== "passed" || screenshotDeclarations.length === 0) return;
366
447
  const input = this.baselineInput(test, screenshotDeclarations);
367
448
  if (input === null) return;
368
449
  const targets = resolveBaselineTargets(input);
369
450
  if (targets.length === 0) return;
370
- const safeTestId = this.sanitizeId(test.id);
371
- const testScreenshotDir = join(this.screenshotDir, safeTestId);
372
- const limit = pLimit(MAX_CONCURRENT_FILE_OPS);
451
+ const safeTestId = sanitizeId(test.id);
452
+ const testScreenshotDir = join2(this.screenshotDir, safeTestId);
453
+ const limit = pLimit2(5);
373
454
  await Promise.all(
374
455
  targets.map(
375
- (target) => limit(() => this.copyResolvedBaseline(safeTestId, testScreenshotDir, target, savedAttachments))
376
- )
377
- );
378
- }
379
- async saveAttachments(testId, result) {
380
- const savedAttachments = [];
381
- const safeTestId = this.sanitizeId(testId);
382
- const testScreenshotDir = join(this.screenshotDir, safeTestId);
383
- const limit = pLimit(MAX_CONCURRENT_FILE_OPS);
384
- await Promise.all(
385
- result.attachments.filter(
386
- (attachment) => attachment.contentType === "image/png" && attachment.path !== void 0
387
- ).map(
388
- (attachment) => limit(async () => {
389
- try {
390
- const artifactPath = safeArtifactPath(attachment.name);
391
- const destPath = join(testScreenshotDir, artifactPath);
392
- await mkdir2(dirname2(destPath), { recursive: true });
393
- await copyFile(attachment.path, destPath);
394
- savedAttachments.push({
395
- name: attachment.name,
396
- path: `${safeTestId}/${artifactPath}`,
397
- contentType: attachment.contentType
398
- });
399
- console.log(`[CrvyRprtr] Saved screenshot: ${destPath}`);
400
- } catch (error) {
401
- console.error(`[CrvyRprtr] Failed to save screenshot: ${attachment.path}`, error);
402
- savedAttachments.push({
403
- name: attachment.name,
404
- path: attachment.path,
405
- contentType: attachment.contentType
406
- });
407
- }
408
- })
456
+ (target) => limit(() => copyResolvedBaseline(safeTestId, testScreenshotDir, target, savedAttachments))
409
457
  )
410
458
  );
411
- return savedAttachments;
412
- }
413
- sanitizeId(id) {
414
- return id.replace(/[^a-zA-Z0-9-_]/g, "_");
415
- }
416
- async writeOfflineReport() {
417
- if (this.runEvents.length === 0) console.log("[CrvyRprtr] No offline events to write");
418
- if (this.runEvents.length === 0) return;
419
- try {
420
- const report = {
421
- version: 1,
422
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
423
- workers: this.workerIndex + 1,
424
- events: this.runEvents.map((event) => ({ ...event, timestamp: Date.now(), workerIndex: this.workerIndex }))
425
- };
426
- await writeFile2(this.offlineReportPath, JSON.stringify(report, null, 2));
427
- console.log(`[CrvyRprtr] Wrote offline report: ${this.offlineReportPath}`);
428
- } catch (error) {
429
- console.error("[CrvyRprtr] Failed to write offline report:", error);
430
- }
431
- }
432
- async writeStaticArtifact() {
433
- try {
434
- await writeReportArtifact({
435
- events: this.runEvents,
436
- screenshotDir: this.screenshotDir,
437
- reportHtmlPath: this.reportHtmlPath
438
- });
439
- console.log(`[CrvyRprtr] Wrote report artifact: ${this.reportHtmlPath}`);
440
- } catch (error) {
441
- console.error("[CrvyRprtr] Failed to write report artifact:", error);
442
- }
443
459
  }
444
460
  async onEnd(result) {
445
461
  this.send({ type: "run-end", data: { status: result.status } });
446
- await this.writeStaticArtifact();
447
- if (this.hadOfflineMode) await this.writeOfflineReport();
462
+ await writeStaticArtifact(this.runEvents, this.screenshotDir, this.reportHtmlPath);
463
+ if (this.hadOfflineMode) await writeOfflineReport(this.runEvents, this.offlineReportPath, this.workerIndex);
448
464
  await new Promise((resolve2) => {
449
- if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
450
- this.ws.onclose = () => {
451
- resolve2();
452
- };
453
- setTimeout(() => {
454
- this.ws?.close();
455
- resolve2();
456
- }, 1e3);
457
- this.ws.close();
458
- } else resolve2();
465
+ if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
466
+ resolve2();
467
+ return;
468
+ }
469
+ this.ws.onclose = () => {
470
+ resolve2();
471
+ };
472
+ setTimeout(() => {
473
+ this.ws?.close();
474
+ resolve2();
475
+ }, 1e3);
476
+ this.ws.close();
459
477
  });
460
478
  }
461
479
  send(message) {
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE;QACV,SAAS,EAAE,OAAO,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,YAAY,EAAE,OAAO,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAG9E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAW1E;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrG;AAED,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAIpD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/server/handlers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE;QACV,SAAS,EAAE,OAAO,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAA;QAClB,YAAY,EAAE,OAAO,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAK9E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAW1E;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrG;AAED,wBAAgB,aAAa,IAAI,IAAI,CAIpC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAIpD"}
package/dist/server.cjs CHANGED
@@ -530,7 +530,9 @@ function broadcastToBrowsers(wsClients, msg) {
530
530
  // src/server/handlers.ts
531
531
  function handleTestBegin(ctx, data) {
532
532
  const test = applyTestBeginEvent(ctx, data);
533
+ ctx.reportData.isRunning = true;
533
534
  console.log(` \u25B6 [${test.browser ?? "?"}] ${test.title}`);
535
+ broadcastToBrowsers(ctx.wsClients, { type: "test-begin", data });
534
536
  }
535
537
  function handleTestEnd(ctx, data) {
536
538
  const result = applyTestEndEvent(ctx, data);
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startServer
3
- } from "./chunk-OMDYTNWY.js";
4
- import "./chunk-JGS2VWQP.js";
3
+ } from "./chunk-XU5VQ3FZ.js";
4
+ import "./chunk-EJRLZZ22.js";
5
5
  export {
6
6
  startServer
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crvy/rprtr",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Playwright reporter with visual regression UI for comparing and approving screenshot tests",
5
5
  "keywords": [
6
6
  "crvy",
@@ -154,6 +154,148 @@ function resolveBaselineTargets(input) {
154
154
  });
155
155
  }
156
156
 
157
+ // src/schemas.ts
158
+ import { z } from "zod";
159
+ var LocationSchema = z.object({
160
+ file: z.string(),
161
+ line: z.number()
162
+ });
163
+ var VisualSourceSchema = z.enum(["comparison", "baseline-only", "declared-only"]);
164
+ var ImagesSchema = z.object({
165
+ actual: z.string().optional(),
166
+ expect: z.string().optional(),
167
+ diff: z.string().optional(),
168
+ error: z.string().optional(),
169
+ source: VisualSourceSchema.optional()
170
+ });
171
+ var ScreenshotDeclarationSchema = z.discriminatedUnion("kind", [
172
+ z.object({
173
+ visualName: z.string(),
174
+ kind: z.literal("named"),
175
+ declaredName: z.string(),
176
+ snapshotBaseName: z.string(),
177
+ occurrenceIndex: z.number()
178
+ }),
179
+ z.object({
180
+ visualName: z.string(),
181
+ kind: z.literal("unnamed"),
182
+ occurrenceIndex: z.number()
183
+ })
184
+ ]);
185
+ var AttachmentSchema = z.object({
186
+ name: z.string(),
187
+ path: z.string(),
188
+ contentType: z.string()
189
+ });
190
+ var TestStatusSchema = z.enum(["unknown", "pending", "running", "failed", "approved", "success", "retrying"]);
191
+ var TestResultStatusSchema = z.enum(["failed", "success", "pending"]);
192
+ var TestResultSchema = z.object({
193
+ status: TestResultStatusSchema,
194
+ retries: z.number(),
195
+ images: z.record(z.string(), ImagesSchema).optional(),
196
+ visualDeclarations: z.array(ScreenshotDeclarationSchema).optional(),
197
+ error: z.string().optional(),
198
+ duration: z.number().optional()
199
+ });
200
+ var TestDataSchema = z.object({
201
+ id: z.string(),
202
+ titlePath: z.array(z.string()),
203
+ browser: z.string(),
204
+ title: z.string(),
205
+ skip: z.union([z.boolean(), z.string()]).optional(),
206
+ retries: z.number().optional(),
207
+ status: TestStatusSchema.optional(),
208
+ results: z.array(TestResultSchema).optional(),
209
+ approved: z.record(z.string(), z.number()).nullable().optional(),
210
+ attachments: z.array(AttachmentSchema).optional(),
211
+ location: LocationSchema.optional()
212
+ });
213
+ var CrvyRprtrTestSchema = TestDataSchema.extend({
214
+ checked: z.boolean()
215
+ });
216
+ var CrvyRprtrSuiteSchema = z.lazy(
217
+ () => z.object({
218
+ path: z.array(z.string()),
219
+ skip: z.boolean(),
220
+ status: TestStatusSchema.optional(),
221
+ opened: z.boolean(),
222
+ checked: z.boolean(),
223
+ indeterminate: z.boolean(),
224
+ children: z.record(z.string(), z.union([CrvyRprtrSuiteSchema, CrvyRprtrTestSchema])).optional()
225
+ })
226
+ );
227
+ var WebSocketMessageSchema = z.object({
228
+ type: z.enum(["test-begin", "test-end", "run-end", "approve", "sync"]),
229
+ data: z.unknown()
230
+ });
231
+ var TestBeginDataSchema = z.object({
232
+ id: z.string(),
233
+ title: z.string(),
234
+ titlePath: z.array(z.string()),
235
+ browser: z.string(),
236
+ location: LocationSchema
237
+ });
238
+ var TestEndDataSchema = z.object({
239
+ id: z.string(),
240
+ status: z.enum(["passed", "failed", "skipped"]),
241
+ attachments: z.array(AttachmentSchema),
242
+ visualNames: z.array(z.string()).default([]),
243
+ visualDeclarations: z.preprocess(
244
+ (value) => value === null ? void 0 : value,
245
+ z.array(ScreenshotDeclarationSchema).optional()
246
+ ),
247
+ error: z.string().optional(),
248
+ duration: z.number().optional()
249
+ });
250
+ var ReportDataSchema = z.object({
251
+ isRunning: z.boolean(),
252
+ tests: z.record(z.string(), TestDataSchema),
253
+ browsers: z.array(z.string()),
254
+ isUpdateMode: z.boolean(),
255
+ screenshotDir: z.string()
256
+ });
257
+ var LoadedReportDataSchema = z.object({
258
+ tests: z.record(z.string(), TestDataSchema).optional(),
259
+ isUpdateMode: z.boolean().optional()
260
+ });
261
+ var OfflineEventSchema = z.object({
262
+ type: z.enum(["test-begin", "test-end", "run-end"]),
263
+ data: z.unknown(),
264
+ timestamp: z.number(),
265
+ workerIndex: z.number()
266
+ });
267
+ var OfflineReportSchema = z.object({
268
+ version: z.number(),
269
+ generatedAt: z.string(),
270
+ workers: z.number(),
271
+ events: z.array(OfflineEventSchema)
272
+ });
273
+ var ApproveRequestBodySchema = z.object({
274
+ id: z.string(),
275
+ retry: z.number(),
276
+ image: z.string()
277
+ });
278
+ var ReportApiResponseSchema = z.object({
279
+ tests: z.record(z.string(), TestDataSchema),
280
+ isUpdateMode: z.boolean().optional()
281
+ });
282
+ var ClientBootstrapDataSchema = z.object({
283
+ report: ReportApiResponseSchema.extend({
284
+ isUpdateMode: z.boolean()
285
+ }),
286
+ liveUpdates: z.boolean(),
287
+ approvalEnabled: z.boolean(),
288
+ approvalMessage: z.string().optional()
289
+ });
290
+ var ImagesViewModeSchema = z.enum(["side-by-side", "swap", "slide", "blend"]);
291
+ function safeParse(schema, data) {
292
+ const result = schema.safeParse(data);
293
+ if (result.success) {
294
+ return result.data;
295
+ }
296
+ return null;
297
+ }
298
+
157
299
  // src/report-utils.ts
158
300
  function normalizeScreenshotsBaseUrl(baseUrl) {
159
301
  return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
@@ -341,148 +483,6 @@ function finalizeRunEvent(state) {
341
483
  };
342
484
  }
343
485
 
344
- // src/schemas.ts
345
- import { z } from "zod";
346
- var LocationSchema = z.object({
347
- file: z.string(),
348
- line: z.number()
349
- });
350
- var VisualSourceSchema = z.enum(["comparison", "baseline-only", "declared-only"]);
351
- var ImagesSchema = z.object({
352
- actual: z.string().optional(),
353
- expect: z.string().optional(),
354
- diff: z.string().optional(),
355
- error: z.string().optional(),
356
- source: VisualSourceSchema.optional()
357
- });
358
- var ScreenshotDeclarationSchema = z.discriminatedUnion("kind", [
359
- z.object({
360
- visualName: z.string(),
361
- kind: z.literal("named"),
362
- declaredName: z.string(),
363
- snapshotBaseName: z.string(),
364
- occurrenceIndex: z.number()
365
- }),
366
- z.object({
367
- visualName: z.string(),
368
- kind: z.literal("unnamed"),
369
- occurrenceIndex: z.number()
370
- })
371
- ]);
372
- var AttachmentSchema = z.object({
373
- name: z.string(),
374
- path: z.string(),
375
- contentType: z.string()
376
- });
377
- var TestStatusSchema = z.enum(["unknown", "pending", "running", "failed", "approved", "success", "retrying"]);
378
- var TestResultStatusSchema = z.enum(["failed", "success", "pending"]);
379
- var TestResultSchema = z.object({
380
- status: TestResultStatusSchema,
381
- retries: z.number(),
382
- images: z.record(z.string(), ImagesSchema).optional(),
383
- visualDeclarations: z.array(ScreenshotDeclarationSchema).optional(),
384
- error: z.string().optional(),
385
- duration: z.number().optional()
386
- });
387
- var TestDataSchema = z.object({
388
- id: z.string(),
389
- titlePath: z.array(z.string()),
390
- browser: z.string(),
391
- title: z.string(),
392
- skip: z.union([z.boolean(), z.string()]).optional(),
393
- retries: z.number().optional(),
394
- status: TestStatusSchema.optional(),
395
- results: z.array(TestResultSchema).optional(),
396
- approved: z.record(z.string(), z.number()).nullable().optional(),
397
- attachments: z.array(AttachmentSchema).optional(),
398
- location: LocationSchema.optional()
399
- });
400
- var CrvyRprtrTestSchema = TestDataSchema.extend({
401
- checked: z.boolean()
402
- });
403
- var CrvyRprtrSuiteSchema = z.lazy(
404
- () => z.object({
405
- path: z.array(z.string()),
406
- skip: z.boolean(),
407
- status: TestStatusSchema.optional(),
408
- opened: z.boolean(),
409
- checked: z.boolean(),
410
- indeterminate: z.boolean(),
411
- children: z.record(z.string(), z.union([CrvyRprtrSuiteSchema, CrvyRprtrTestSchema])).optional()
412
- })
413
- );
414
- var WebSocketMessageSchema = z.object({
415
- type: z.enum(["test-begin", "test-end", "run-end", "approve", "sync"]),
416
- data: z.unknown()
417
- });
418
- var TestBeginDataSchema = z.object({
419
- id: z.string(),
420
- title: z.string(),
421
- titlePath: z.array(z.string()),
422
- browser: z.string(),
423
- location: LocationSchema
424
- });
425
- var TestEndDataSchema = z.object({
426
- id: z.string(),
427
- status: z.enum(["passed", "failed", "skipped"]),
428
- attachments: z.array(AttachmentSchema),
429
- visualNames: z.array(z.string()).default([]),
430
- visualDeclarations: z.preprocess(
431
- (value) => value === null ? void 0 : value,
432
- z.array(ScreenshotDeclarationSchema).optional()
433
- ),
434
- error: z.string().optional(),
435
- duration: z.number().optional()
436
- });
437
- var ReportDataSchema = z.object({
438
- isRunning: z.boolean(),
439
- tests: z.record(z.string(), TestDataSchema),
440
- browsers: z.array(z.string()),
441
- isUpdateMode: z.boolean(),
442
- screenshotDir: z.string()
443
- });
444
- var LoadedReportDataSchema = z.object({
445
- tests: z.record(z.string(), TestDataSchema).optional(),
446
- isUpdateMode: z.boolean().optional()
447
- });
448
- var OfflineEventSchema = z.object({
449
- type: z.enum(["test-begin", "test-end", "run-end"]),
450
- data: z.unknown(),
451
- timestamp: z.number(),
452
- workerIndex: z.number()
453
- });
454
- var OfflineReportSchema = z.object({
455
- version: z.number(),
456
- generatedAt: z.string(),
457
- workers: z.number(),
458
- events: z.array(OfflineEventSchema)
459
- });
460
- var ApproveRequestBodySchema = z.object({
461
- id: z.string(),
462
- retry: z.number(),
463
- image: z.string()
464
- });
465
- var ReportApiResponseSchema = z.object({
466
- tests: z.record(z.string(), TestDataSchema),
467
- isUpdateMode: z.boolean().optional()
468
- });
469
- var ClientBootstrapDataSchema = z.object({
470
- report: ReportApiResponseSchema.extend({
471
- isUpdateMode: z.boolean()
472
- }),
473
- liveUpdates: z.boolean(),
474
- approvalEnabled: z.boolean(),
475
- approvalMessage: z.string().optional()
476
- });
477
- var ImagesViewModeSchema = z.enum(["side-by-side", "swap", "slide", "blend"]);
478
- function safeParse(schema, data) {
479
- const result = schema.safeParse(data);
480
- if (result.success) {
481
- return result.data;
482
- }
483
- return null;
484
- }
485
-
486
486
  export {
487
487
  createMutableReportState,
488
488
  applyTestBeginEvent,