@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/CHANGELOG.md +5 -0
- package/dist/{chunk-OMDYTNWY.js → chunk-XU5VQ3FZ.js} +3 -1
- package/dist/cli.js +2 -2
- package/dist/debug-log.d.ts +3 -0
- package/dist/debug-log.d.ts.map +1 -0
- package/dist/index.css +0 -22
- package/dist/index.js +83 -127
- package/dist/reporter-artifact-ops.d.ts +22 -0
- package/dist/reporter-artifact-ops.d.ts.map +1 -0
- package/dist/reporter.cjs +194 -176
- package/dist/reporter.d.ts +0 -5
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +135 -117
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server.cjs +2 -0
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/{chunk-JGS2VWQP.js → chunk-EJRLZZ22.js} +142 -142
package/dist/reporter.js
CHANGED
|
@@ -8,10 +8,24 @@ import {
|
|
|
8
8
|
finalizeRunEvent,
|
|
9
9
|
resolveBaselineTargets,
|
|
10
10
|
safeParse
|
|
11
|
-
} from "./chunk-
|
|
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 :
|
|
251
|
-
|
|
252
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
364
|
+
logError("[CrvyRprtr] WebSocket error:", error);
|
|
272
365
|
this.enableOfflineMode();
|
|
273
366
|
};
|
|
274
367
|
this.ws.onclose = () => {
|
|
275
|
-
|
|
368
|
+
log("[CrvyRprtr] Disconnected from Crvy Rprtr server");
|
|
276
369
|
this.enableOfflineMode();
|
|
277
370
|
};
|
|
278
371
|
} catch (error) {
|
|
279
|
-
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
371
|
-
const testScreenshotDir =
|
|
372
|
-
const limit =
|
|
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(() =>
|
|
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.
|
|
447
|
-
if (this.hadOfflineMode) await this.
|
|
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
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.ws
|
|
458
|
-
|
|
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,
|
|
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
package/package.json
CHANGED
|
@@ -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,
|