@crvy/rprtr 0.0.7 → 0.0.9

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":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,sCAAsC,CAAC,EAAE,MAAM,CAAA;CAChD;AAUD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClD,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAmMD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwCrF"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,8BAA8B,CAAC,EAAE,MAAM,CAAA;IACvC,sCAAsC,CAAC,EAAE,MAAM,CAAA;CAChD;AAUD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAChC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClD,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AA8LD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6BrF"}
@@ -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
@@ -35,7 +35,7 @@ __export(server_exports, {
35
35
  module.exports = __toCommonJS(server_exports);
36
36
 
37
37
  // src/server/app.ts
38
- var import_path6 = require("path");
38
+ var import_path5 = require("path");
39
39
  var import_url = require("url");
40
40
  var import_p_limit = __toESM(require("p-limit"), 1);
41
41
 
@@ -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);
@@ -560,121 +562,13 @@ function handleSync(ctx) {
560
562
  broadcastToBrowsers(ctx.wsClients, { type: "sync", data: ctx.reportData });
561
563
  }
562
564
 
563
- // src/server/report-watch.ts
564
- var import_promises3 = require("fs/promises");
565
- var import_path3 = require("path");
566
- var OFFLINE_REPORT_FILE_PATTERN2 = /^crvy-rprtr(?:-\d+)?\.json$/;
567
- function createDebouncedRefresh(reload, delayMs = 50) {
568
- let timer = null;
569
- return () => {
570
- if (timer !== null) {
571
- clearTimeout(timer);
572
- }
573
- timer = setTimeout(() => {
574
- timer = null;
575
- void reload();
576
- }, delayMs);
577
- };
578
- }
579
- function isFileNotFound2(error) {
580
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
581
- }
582
- async function describeFile(filePath, label) {
583
- try {
584
- const fileStats = await (0, import_promises3.stat)(filePath);
585
- return `${label}:${fileStats.size}:${fileStats.mtimeMs}`;
586
- } catch (error) {
587
- if (isFileNotFound2(error)) {
588
- return null;
589
- }
590
- throw error;
591
- }
592
- }
593
- async function createOfflineFingerprint(offlineReportDir) {
594
- if (!await isDirectory(offlineReportDir)) {
595
- return "offline:missing";
596
- }
597
- try {
598
- const entries = await (0, import_promises3.readdir)(offlineReportDir, { withFileTypes: true });
599
- const relevantEntries = entries.filter(
600
- (entry) => entry.isFile() && (entry.name === "report.json" || OFFLINE_REPORT_FILE_PATTERN2.test(entry.name))
601
- ).sort((left, right) => left.name.localeCompare(right.name));
602
- const parts = await Promise.all(
603
- relevantEntries.map((entry) => describeFile((0, import_path3.join)(offlineReportDir, entry.name), entry.name))
604
- );
605
- return `offline:${parts.filter((part) => part !== null).join("|")}`;
606
- } catch (error) {
607
- const errorMsg = error instanceof Error ? error.message : String(error);
608
- console.warn(`[Server] Unable to scan ${offlineReportDir}: ${errorMsg}`);
609
- return "offline:error";
610
- }
611
- }
612
- async function createScreenshotFingerprint(screenshotDir, relativePath = "") {
613
- const directoryPath = relativePath === "" ? screenshotDir : (0, import_path3.join)(screenshotDir, relativePath);
614
- if (!await isDirectory(directoryPath)) {
615
- return relativePath === "" ? ["screenshots:missing"] : [];
616
- }
617
- try {
618
- const entries = await (0, import_promises3.readdir)(directoryPath, { withFileTypes: true });
619
- const parts = await Promise.all(
620
- entries.sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
621
- const entryRelativePath = relativePath === "" ? entry.name : (0, import_path3.join)(relativePath, entry.name);
622
- if (entry.isDirectory()) {
623
- return createScreenshotFingerprint(screenshotDir, entryRelativePath);
624
- }
625
- return describeFile((0, import_path3.join)(screenshotDir, entryRelativePath), entryRelativePath);
626
- })
627
- );
628
- return parts.flat().filter((part) => part !== null);
629
- } catch (error) {
630
- if (isFileNotFound2(error)) {
631
- return relativePath === "" ? ["screenshots:missing"] : [];
632
- }
633
- const errorMsg = error instanceof Error ? error.message : String(error);
634
- console.warn(`[Server] Unable to scan ${directoryPath}: ${errorMsg}`);
635
- return relativePath === "" ? ["screenshots:error"] : [];
636
- }
637
- }
638
- async function createArtifactsFingerprint(options) {
639
- const [offlineFingerprint, screenshotFingerprint] = await Promise.all([
640
- createOfflineFingerprint(options.offlineReportDir),
641
- createScreenshotFingerprint(options.screenshotDir)
642
- ]);
643
- return `${offlineFingerprint}::${screenshotFingerprint.join("|")}`;
644
- }
645
- async function watchReportArtifacts(options) {
646
- let fingerprint = await createArtifactsFingerprint(options);
647
- let isPolling = false;
648
- const interval = setInterval(() => {
649
- if (isPolling) {
650
- return;
651
- }
652
- isPolling = true;
653
- void createArtifactsFingerprint(options).then((nextFingerprint) => {
654
- if (nextFingerprint === fingerprint) {
655
- return;
656
- }
657
- fingerprint = nextFingerprint;
658
- options.scheduleRefresh();
659
- }).catch((error) => {
660
- const errorMsg = error instanceof Error ? error.message : String(error);
661
- console.warn(`[Server] Artifact refresh poll failed: ${errorMsg}`);
662
- }).finally(() => {
663
- isPolling = false;
664
- });
665
- }, 100);
666
- return () => {
667
- clearInterval(interval);
668
- };
669
- }
670
-
671
565
  // src/server/routes.ts
672
566
  var import_fs = require("fs");
673
- var import_path5 = require("path");
567
+ var import_path4 = require("path");
674
568
 
675
569
  // src/snapshot-path-resolver.ts
676
570
  var import_crypto = require("crypto");
677
- var import_path4 = require("path");
571
+ var import_path3 = require("path");
678
572
  var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
679
573
  var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
680
574
  function isUnsafeFilePathCharacter(character) {
@@ -709,16 +603,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
709
603
  const end = length - middle.length - start;
710
604
  return value.slice(0, start) + middle + value.slice(-end);
711
605
  }
712
- function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path4.extname)(filePath)) {
606
+ function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
713
607
  const base = filePath.slice(0, filePath.length - extension.length);
714
608
  return sanitizeForFilePath(base) + extension;
715
609
  }
716
610
  function addSuffixToFilePath(filePath, suffix) {
717
- const extension = (0, import_path4.extname)(filePath);
611
+ const extension = (0, import_path3.extname)(filePath);
718
612
  return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
719
613
  }
720
614
  function normalizedSnapshotDir(config) {
721
- return (0, import_path4.resolve)(config.configDir, config.snapshotDir);
615
+ return (0, import_path3.resolve)(config.configDir, config.snapshotDir);
722
616
  }
723
617
  function templateValue(template, token, value) {
724
618
  return template.replace(
@@ -728,8 +622,8 @@ function templateValue(template, token, value) {
728
622
  }
729
623
  function applyTemplate(input, nameArgument, extension) {
730
624
  const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
731
- const relativeTestFilePath = (0, import_path4.relative)(input.config.testDir, input.testFile);
732
- const parsed = (0, import_path4.parse)(relativeTestFilePath);
625
+ const relativeTestFilePath = (0, import_path3.relative)(input.config.testDir, input.testFile);
626
+ const parsed = (0, import_path3.parse)(relativeTestFilePath);
733
627
  const tokens = [
734
628
  ["testDir", input.config.testDir],
735
629
  ["snapshotDir", normalizedSnapshotDir(input.config)],
@@ -747,16 +641,16 @@ function applyTemplate(input, nameArgument, extension) {
747
641
  (currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
748
642
  template
749
643
  );
750
- return (0, import_path4.resolve)(input.config.configDir, snapshotPath);
644
+ return (0, import_path3.resolve)(input.config.configDir, snapshotPath);
751
645
  }
752
- function removeExtension(filePath, extension = (0, import_path4.extname)(filePath)) {
646
+ function removeExtension(filePath, extension = (0, import_path3.extname)(filePath)) {
753
647
  return filePath.slice(0, filePath.length - extension.length);
754
648
  }
755
649
  function snapshotNameParts(declaredName) {
756
- const extension = (0, import_path4.extname)(declaredName) || ".png";
650
+ const extension = (0, import_path3.extname)(declaredName) || ".png";
757
651
  return {
758
652
  extension,
759
- filePath: (0, import_path4.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
653
+ filePath: (0, import_path3.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
760
654
  };
761
655
  }
762
656
  function filePathForOccurrence(filePath, occurrenceIndex) {
@@ -780,7 +674,7 @@ function resolveStringCallTarget(input, declaration) {
780
674
  function resolveArrayCallTarget(input, declaration) {
781
675
  const { extension, filePath } = snapshotNameParts(declaration.declaredName);
782
676
  const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
783
- const nameArgument = (0, import_path4.join)((0, import_path4.dirname)(occurrenceFilePath), (0, import_path4.basename)(occurrenceFilePath, extension));
677
+ const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(occurrenceFilePath), (0, import_path3.basename)(occurrenceFilePath, extension));
784
678
  return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
785
679
  }
786
680
  function resolveNamedTarget(input, declaration) {
@@ -816,7 +710,7 @@ function resolveTarget(input, declaration) {
816
710
  case "unnamed": {
817
711
  const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
818
712
  const extension = ".png";
819
- const nameArgument = (0, import_path4.join)((0, import_path4.dirname)(anonymousFileName), (0, import_path4.basename)(anonymousFileName, extension));
713
+ const nameArgument = (0, import_path3.join)((0, import_path3.dirname)(anonymousFileName), (0, import_path3.basename)(anonymousFileName, extension));
820
714
  return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
821
715
  }
822
716
  }
@@ -834,7 +728,7 @@ function isWebSocketUpgradeRequest(req) {
834
728
  return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
835
729
  }
836
730
  async function handleRoot(ctx) {
837
- const html = await respondWithFile((0, import_path5.join)(ctx.staticDir, "index.html"), "text/html");
731
+ const html = await respondWithFile((0, import_path4.join)(ctx.staticDir, "index.html"), "text/html");
838
732
  return html ?? new Response("Not Found", { status: 404 });
839
733
  }
840
734
  async function handleAppCss() {
@@ -853,7 +747,7 @@ function handleApiReport(ctx) {
853
747
  }
854
748
  var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
855
749
  function actualPathFromUrl(ctx, actualUrl) {
856
- return actualUrl.startsWith("/screenshots/") ? (0, import_path5.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length)) : actualUrl;
750
+ return actualUrl.startsWith("/screenshots/") ? (0, import_path4.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length)) : actualUrl;
857
751
  }
858
752
  function reporterTitlePath(test) {
859
753
  const testFile = test.location?.file;
@@ -871,8 +765,8 @@ function resolveApprovalTarget(ctx, test, retry, imageName) {
871
765
  declarations: [declaration],
872
766
  config: {
873
767
  configDir: ctx.approvalRouting.configDir,
874
- testDir: ctx.approvalRouting.playwrightTestDir ?? (0, import_path5.dirname)(testFile),
875
- snapshotDir: ctx.approvalRouting.playwrightSnapshotDir ?? (0, import_path5.dirname)(testFile),
768
+ testDir: ctx.approvalRouting.playwrightTestDir ?? (0, import_path4.dirname)(testFile),
769
+ snapshotDir: ctx.approvalRouting.playwrightSnapshotDir ?? (0, import_path4.dirname)(testFile),
876
770
  projectName: test.browser,
877
771
  snapshotSuffix: process.platform,
878
772
  snapshotPathTemplate: ctx.approvalRouting.playwrightSnapshotPathTemplate,
@@ -997,7 +891,7 @@ async function handleScreenshots(ctx, req) {
997
891
  }
998
892
  async function handleDist(ctx, req) {
999
893
  const path = new URL(req.url).pathname.slice("/dist/".length);
1000
- const filePath = (0, import_path5.join)(ctx.staticDir, path);
894
+ const filePath = (0, import_path4.join)(ctx.staticDir, path);
1001
895
  const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
1002
896
  const file = await respondWithFile(filePath, contentType);
1003
897
  return file ?? new Response("Not Found", { status: 404 });
@@ -1101,10 +995,6 @@ async function loadOfflineReports(offlineReportDir, reportData) {
1101
995
  screenshotsBaseUrl: "/screenshots/"
1102
996
  });
1103
997
  }
1104
- function resetReloadableReportData(reportData) {
1105
- reportData.tests = {};
1106
- reportData.isUpdateMode = false;
1107
- }
1108
998
  async function handleParsedWebSocketMessage(ctx, msg) {
1109
999
  switch (msg.type) {
1110
1000
  case "test-begin": {
@@ -1153,19 +1043,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
1153
1043
  };
1154
1044
  }
1155
1045
  async function resolveStaticDir(staticDir) {
1156
- const currentDir = (0, import_path6.dirname)((0, import_url.fileURLToPath)(import_meta.url));
1046
+ const currentDir = (0, import_path5.dirname)((0, import_url.fileURLToPath)(import_meta.url));
1157
1047
  const candidates = staticDir === void 0 ? [
1158
1048
  currentDir,
1159
- (0, import_path6.join)(currentDir, "dist"),
1160
- (0, import_path6.join)(currentDir, "..", "dist"),
1161
- (0, import_path6.join)(currentDir, "..", "..", "dist"),
1162
- (0, import_path6.join)(currentDir, ".."),
1163
- (0, import_path6.join)(currentDir, "..", "..")
1164
- ] : [staticDir, (0, import_path6.join)(staticDir, "dist")];
1049
+ (0, import_path5.join)(currentDir, "dist"),
1050
+ (0, import_path5.join)(currentDir, "..", "dist"),
1051
+ (0, import_path5.join)(currentDir, "..", "..", "dist"),
1052
+ (0, import_path5.join)(currentDir, ".."),
1053
+ (0, import_path5.join)(currentDir, "..", "..")
1054
+ ] : [staticDir, (0, import_path5.join)(staticDir, "dist")];
1165
1055
  const resolvedCandidates = await Promise.all(
1166
1056
  candidates.map(async (candidate) => ({
1167
1057
  candidate,
1168
- exists: await fileExists((0, import_path6.join)(candidate, "index.html"))
1058
+ exists: await fileExists((0, import_path5.join)(candidate, "index.html"))
1169
1059
  }))
1170
1060
  );
1171
1061
  const resolved = resolvedCandidates.find(({ exists }) => exists);
@@ -1176,9 +1066,9 @@ async function resolveStaticDir(staticDir) {
1176
1066
  }
1177
1067
  async function resolveReportPath(reportPath) {
1178
1068
  if (await isDirectory(reportPath)) {
1179
- return { reportFile: (0, import_path6.join)(reportPath, "report.json"), offlineReportDir: reportPath };
1069
+ return { reportFile: (0, import_path5.join)(reportPath, "report.json"), offlineReportDir: reportPath };
1180
1070
  }
1181
- return { reportFile: reportPath, offlineReportDir: (0, import_path6.dirname)(reportPath) };
1071
+ return { reportFile: reportPath, offlineReportDir: (0, import_path5.dirname)(reportPath) };
1182
1072
  }
1183
1073
  function createRoutesContext(reportData, staticDir, saveReport, options) {
1184
1074
  return {
@@ -1209,22 +1099,13 @@ async function createServerApp(options = {}) {
1209
1099
  const getHandlerContext = () => createHandlerContext(reportData, wsClients, currentRunIds, saveReport);
1210
1100
  const handleRequest = (req) => handleHttpRequest(routesContext, req);
1211
1101
  const handleWebSocketMessage = createWebSocketMessageHandler(getHandlerContext);
1212
- const reloadFromDisk = async () => {
1213
- resetReloadableReportData(reportData);
1214
- await loadReport(reportFile, reportData);
1215
- await loadOfflineReports(offlineReportDir, reportData);
1216
- broadcastToBrowsers(wsClients, { type: "sync", data: reportData });
1217
- };
1218
- await reloadFromDisk();
1219
- const close = await watchReportArtifacts({
1220
- offlineReportDir,
1221
- screenshotDir: reportData.screenshotDir,
1222
- scheduleRefresh: createDebouncedRefresh(reloadFromDisk)
1223
- });
1102
+ await loadReport(reportFile, reportData);
1103
+ await loadOfflineReports(offlineReportDir, reportData);
1224
1104
  return {
1225
1105
  port,
1226
1106
  wsClients,
1227
- close,
1107
+ close: () => {
1108
+ },
1228
1109
  handleRequest,
1229
1110
  handleWebSocketMessage
1230
1111
  };