executable-stories-formatters 0.7.11 → 0.7.13
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/adapters.cjs +9 -2
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/adapters.js +9 -2
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +206 -20
- package/dist/cli.js.map +1 -1
- package/dist/{index-C0OOaaiK.d.cts → index-fqrm5-Xr.d.cts} +34 -24
- package/dist/{index-C0OOaaiK.d.ts → index-fqrm5-Xr.d.ts} +34 -24
- package/dist/index.cjs +215 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -9
- package/dist/index.d.ts +7 -9
- package/dist/index.js +215 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed CI provider and canonical CI info.
|
|
3
|
+
*
|
|
4
|
+
* RawCIInfo.name = legacy transport string (kept for backward compat + schema).
|
|
5
|
+
* CIInfo.displayName = canonical display name for downstream consumers.
|
|
6
|
+
*
|
|
7
|
+
* All downstream code (HTML meta, notifications, history) uses CIInfo via mappers.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type CIProvider = "github" | "gitlab" | "circleci" | "jenkins" | "azure" | "buildkite" | "travis" | "unknown";
|
|
11
|
+
interface CIInfo {
|
|
12
|
+
provider: CIProvider;
|
|
13
|
+
displayName: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
buildNumber?: string;
|
|
16
|
+
branch?: string;
|
|
17
|
+
commitSha?: string;
|
|
18
|
+
prNumber?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Convert RawCIInfo (legacy transport) to canonical CIInfo. */
|
|
21
|
+
declare function toCIInfo(raw?: RawCIInfo): CIInfo | undefined;
|
|
22
|
+
/** Convert canonical CIInfo back to RawCIInfo (for serialization). */
|
|
23
|
+
declare function toRawCIInfo(ci?: CIInfo): RawCIInfo | undefined;
|
|
24
|
+
|
|
1
25
|
/**
|
|
2
26
|
* OTel span types for trace waterfall rendering.
|
|
3
27
|
*
|
|
@@ -175,6 +199,7 @@ interface RawAttachment {
|
|
|
175
199
|
/** Raw step event from framework (if available) */
|
|
176
200
|
interface RawStepEvent {
|
|
177
201
|
index?: number;
|
|
202
|
+
stepId?: string;
|
|
178
203
|
title?: string;
|
|
179
204
|
status?: RawStatus;
|
|
180
205
|
durationMs?: number;
|
|
@@ -248,30 +273,6 @@ interface RawRun {
|
|
|
248
273
|
ci?: RawCIInfo;
|
|
249
274
|
}
|
|
250
275
|
|
|
251
|
-
/**
|
|
252
|
-
* Typed CI provider and canonical CI info.
|
|
253
|
-
*
|
|
254
|
-
* RawCIInfo.name = legacy transport string (kept for backward compat + schema).
|
|
255
|
-
* CIInfo.displayName = canonical display name for downstream consumers.
|
|
256
|
-
*
|
|
257
|
-
* All downstream code (HTML meta, notifications, history) uses CIInfo via mappers.
|
|
258
|
-
*/
|
|
259
|
-
|
|
260
|
-
type CIProvider = "github" | "gitlab" | "circleci" | "jenkins" | "azure" | "buildkite" | "travis" | "unknown";
|
|
261
|
-
interface CIInfo {
|
|
262
|
-
provider: CIProvider;
|
|
263
|
-
displayName: string;
|
|
264
|
-
url?: string;
|
|
265
|
-
buildNumber?: string;
|
|
266
|
-
branch?: string;
|
|
267
|
-
commitSha?: string;
|
|
268
|
-
prNumber?: string;
|
|
269
|
-
}
|
|
270
|
-
/** Convert RawCIInfo (legacy transport) to canonical CIInfo. */
|
|
271
|
-
declare function toCIInfo(raw?: RawCIInfo): CIInfo | undefined;
|
|
272
|
-
/** Convert canonical CIInfo back to RawCIInfo (for serialization). */
|
|
273
|
-
declare function toRawCIInfo(ci?: CIInfo): RawCIInfo | undefined;
|
|
274
|
-
|
|
275
276
|
/**
|
|
276
277
|
* Jest Adapter - Layer 1.
|
|
277
278
|
*
|
|
@@ -400,6 +401,15 @@ interface PlaywrightTestResult {
|
|
|
400
401
|
errors: PlaywrightError[];
|
|
401
402
|
attachments: PlaywrightAttachment[];
|
|
402
403
|
retry: number;
|
|
404
|
+
/** Optional step events if caller captures Playwright step timing. */
|
|
405
|
+
stepEvents?: Array<{
|
|
406
|
+
index?: number;
|
|
407
|
+
stepId?: string;
|
|
408
|
+
title?: string;
|
|
409
|
+
status?: RawStatus;
|
|
410
|
+
durationMs?: number;
|
|
411
|
+
errorMessage?: string;
|
|
412
|
+
}>;
|
|
403
413
|
}
|
|
404
414
|
/** Playwright test case annotation */
|
|
405
415
|
interface PlaywrightAnnotation {
|
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed CI provider and canonical CI info.
|
|
3
|
+
*
|
|
4
|
+
* RawCIInfo.name = legacy transport string (kept for backward compat + schema).
|
|
5
|
+
* CIInfo.displayName = canonical display name for downstream consumers.
|
|
6
|
+
*
|
|
7
|
+
* All downstream code (HTML meta, notifications, history) uses CIInfo via mappers.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type CIProvider = "github" | "gitlab" | "circleci" | "jenkins" | "azure" | "buildkite" | "travis" | "unknown";
|
|
11
|
+
interface CIInfo {
|
|
12
|
+
provider: CIProvider;
|
|
13
|
+
displayName: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
buildNumber?: string;
|
|
16
|
+
branch?: string;
|
|
17
|
+
commitSha?: string;
|
|
18
|
+
prNumber?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Convert RawCIInfo (legacy transport) to canonical CIInfo. */
|
|
21
|
+
declare function toCIInfo(raw?: RawCIInfo): CIInfo | undefined;
|
|
22
|
+
/** Convert canonical CIInfo back to RawCIInfo (for serialization). */
|
|
23
|
+
declare function toRawCIInfo(ci?: CIInfo): RawCIInfo | undefined;
|
|
24
|
+
|
|
1
25
|
/**
|
|
2
26
|
* OTel span types for trace waterfall rendering.
|
|
3
27
|
*
|
|
@@ -175,6 +199,7 @@ interface RawAttachment {
|
|
|
175
199
|
/** Raw step event from framework (if available) */
|
|
176
200
|
interface RawStepEvent {
|
|
177
201
|
index?: number;
|
|
202
|
+
stepId?: string;
|
|
178
203
|
title?: string;
|
|
179
204
|
status?: RawStatus;
|
|
180
205
|
durationMs?: number;
|
|
@@ -248,30 +273,6 @@ interface RawRun {
|
|
|
248
273
|
ci?: RawCIInfo;
|
|
249
274
|
}
|
|
250
275
|
|
|
251
|
-
/**
|
|
252
|
-
* Typed CI provider and canonical CI info.
|
|
253
|
-
*
|
|
254
|
-
* RawCIInfo.name = legacy transport string (kept for backward compat + schema).
|
|
255
|
-
* CIInfo.displayName = canonical display name for downstream consumers.
|
|
256
|
-
*
|
|
257
|
-
* All downstream code (HTML meta, notifications, history) uses CIInfo via mappers.
|
|
258
|
-
*/
|
|
259
|
-
|
|
260
|
-
type CIProvider = "github" | "gitlab" | "circleci" | "jenkins" | "azure" | "buildkite" | "travis" | "unknown";
|
|
261
|
-
interface CIInfo {
|
|
262
|
-
provider: CIProvider;
|
|
263
|
-
displayName: string;
|
|
264
|
-
url?: string;
|
|
265
|
-
buildNumber?: string;
|
|
266
|
-
branch?: string;
|
|
267
|
-
commitSha?: string;
|
|
268
|
-
prNumber?: string;
|
|
269
|
-
}
|
|
270
|
-
/** Convert RawCIInfo (legacy transport) to canonical CIInfo. */
|
|
271
|
-
declare function toCIInfo(raw?: RawCIInfo): CIInfo | undefined;
|
|
272
|
-
/** Convert canonical CIInfo back to RawCIInfo (for serialization). */
|
|
273
|
-
declare function toRawCIInfo(ci?: CIInfo): RawCIInfo | undefined;
|
|
274
|
-
|
|
275
276
|
/**
|
|
276
277
|
* Jest Adapter - Layer 1.
|
|
277
278
|
*
|
|
@@ -400,6 +401,15 @@ interface PlaywrightTestResult {
|
|
|
400
401
|
errors: PlaywrightError[];
|
|
401
402
|
attachments: PlaywrightAttachment[];
|
|
402
403
|
retry: number;
|
|
404
|
+
/** Optional step events if caller captures Playwright step timing. */
|
|
405
|
+
stepEvents?: Array<{
|
|
406
|
+
index?: number;
|
|
407
|
+
stepId?: string;
|
|
408
|
+
title?: string;
|
|
409
|
+
status?: RawStatus;
|
|
410
|
+
durationMs?: number;
|
|
411
|
+
errorMessage?: string;
|
|
412
|
+
}>;
|
|
403
413
|
}
|
|
404
414
|
/** Playwright test case annotation */
|
|
405
415
|
interface PlaywrightAnnotation {
|
package/dist/index.cjs
CHANGED
|
@@ -158,6 +158,7 @@ function deriveStepResults(steps, scenarioStatus, error) {
|
|
|
158
158
|
if (scenarioStatus === "passed") {
|
|
159
159
|
return steps.map((_, index) => ({
|
|
160
160
|
index,
|
|
161
|
+
stepId: steps[index].id,
|
|
161
162
|
status: "passed",
|
|
162
163
|
durationMs: 0
|
|
163
164
|
}));
|
|
@@ -165,6 +166,7 @@ function deriveStepResults(steps, scenarioStatus, error) {
|
|
|
165
166
|
if (scenarioStatus === "skipped" || scenarioStatus === "pending") {
|
|
166
167
|
return steps.map((_, index) => ({
|
|
167
168
|
index,
|
|
169
|
+
stepId: steps[index].id,
|
|
168
170
|
status: scenarioStatus,
|
|
169
171
|
durationMs: 0
|
|
170
172
|
}));
|
|
@@ -172,16 +174,17 @@ function deriveStepResults(steps, scenarioStatus, error) {
|
|
|
172
174
|
const failingIndex = findFailingStepIndex(steps, error);
|
|
173
175
|
return steps.map((_, index) => {
|
|
174
176
|
if (index < failingIndex) {
|
|
175
|
-
return { index, status: "passed", durationMs: 0 };
|
|
177
|
+
return { index, stepId: steps[index].id, status: "passed", durationMs: 0 };
|
|
176
178
|
} else if (index === failingIndex) {
|
|
177
179
|
return {
|
|
178
180
|
index,
|
|
181
|
+
stepId: steps[index].id,
|
|
179
182
|
status: "failed",
|
|
180
183
|
durationMs: 0,
|
|
181
184
|
errorMessage: error?.message
|
|
182
185
|
};
|
|
183
186
|
} else {
|
|
184
|
-
return { index, status: "skipped", durationMs: 0 };
|
|
187
|
+
return { index, stepId: steps[index].id, status: "skipped", durationMs: 0 };
|
|
185
188
|
}
|
|
186
189
|
});
|
|
187
190
|
}
|
|
@@ -203,18 +206,23 @@ function mergeStepResults(derived, events) {
|
|
|
203
206
|
return derived;
|
|
204
207
|
}
|
|
205
208
|
const actualByIndex = /* @__PURE__ */ new Map();
|
|
209
|
+
const actualByStepId = /* @__PURE__ */ new Map();
|
|
206
210
|
for (const event of events) {
|
|
207
211
|
if (event.index !== void 0) {
|
|
208
212
|
actualByIndex.set(event.index, event);
|
|
209
213
|
}
|
|
214
|
+
if (event.stepId) {
|
|
215
|
+
actualByStepId.set(event.stepId, event);
|
|
216
|
+
}
|
|
210
217
|
}
|
|
211
218
|
return derived.map((step) => {
|
|
212
|
-
const actual = actualByIndex.get(step.index);
|
|
219
|
+
const actual = (step.stepId ? actualByStepId.get(step.stepId) : void 0) ?? actualByIndex.get(step.index);
|
|
213
220
|
if (!actual) {
|
|
214
221
|
return step;
|
|
215
222
|
}
|
|
216
223
|
return {
|
|
217
224
|
index: step.index,
|
|
225
|
+
stepId: step.stepId ?? actual.stepId,
|
|
218
226
|
status: normalizeStepStatus(actual.status) ?? step.status,
|
|
219
227
|
durationMs: actual.durationMs ?? step.durationMs,
|
|
220
228
|
errorMessage: actual.errorMessage ?? step.errorMessage
|
|
@@ -348,6 +356,7 @@ function canonicalizeTestCase(raw, options, projectRoot) {
|
|
|
348
356
|
derivedSteps,
|
|
349
357
|
raw.stepEvents?.map((e) => ({
|
|
350
358
|
index: e.index,
|
|
359
|
+
stepId: e.stepId,
|
|
351
360
|
status: e.status,
|
|
352
361
|
durationMs: e.durationMs,
|
|
353
362
|
errorMessage: e.errorMessage
|
|
@@ -369,6 +378,7 @@ function canonicalizeTestCase(raw, options, projectRoot) {
|
|
|
369
378
|
sourceFile,
|
|
370
379
|
sourceLine: raw.sourceLine ?? 1,
|
|
371
380
|
status,
|
|
381
|
+
rawStatus: raw.status,
|
|
372
382
|
durationMs: raw.durationMs ?? 0,
|
|
373
383
|
errorMessage: raw.error?.message,
|
|
374
384
|
errorStack: raw.error?.stack,
|
|
@@ -736,19 +746,34 @@ ${doc.markdown}`,
|
|
|
736
746
|
}
|
|
737
747
|
};
|
|
738
748
|
}
|
|
739
|
-
case "
|
|
749
|
+
case "custom":
|
|
750
|
+
if (doc.type === "visual" && doc.data && typeof doc.data === "object") {
|
|
751
|
+
const data = doc.data;
|
|
752
|
+
const status = typeof data.status === "string" ? data.status : "unknown";
|
|
753
|
+
const lines = [`Visual Check (${status})`];
|
|
754
|
+
if (typeof data.baseline === "string") lines.push(`Baseline: ${data.baseline}`);
|
|
755
|
+
if (typeof data.actual === "string") lines.push(`Actual: ${data.actual}`);
|
|
756
|
+
if (typeof data.diff === "string") lines.push(`Diff: ${data.diff}`);
|
|
757
|
+
return {
|
|
758
|
+
doc_string: {
|
|
759
|
+
content: lines.join("\n"),
|
|
760
|
+
content_type: "text/plain",
|
|
761
|
+
line: 0
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
740
765
|
return {
|
|
741
766
|
doc_string: {
|
|
742
|
-
content: doc.
|
|
743
|
-
content_type: "
|
|
767
|
+
content: JSON.stringify(doc.data, null, 2),
|
|
768
|
+
content_type: "application/json",
|
|
744
769
|
line: 0
|
|
745
770
|
}
|
|
746
771
|
};
|
|
747
|
-
case "
|
|
772
|
+
case "tag":
|
|
748
773
|
return {
|
|
749
774
|
doc_string: {
|
|
750
|
-
content:
|
|
751
|
-
content_type: "
|
|
775
|
+
content: doc.names.map((n) => `@${n}`).join(" "),
|
|
776
|
+
content_type: "text/plain",
|
|
752
777
|
line: 0
|
|
753
778
|
}
|
|
754
779
|
};
|
|
@@ -2266,6 +2291,14 @@ body {
|
|
|
2266
2291
|
font-family: var(--font-mono);
|
|
2267
2292
|
}
|
|
2268
2293
|
|
|
2294
|
+
.outcome-tag {
|
|
2295
|
+
background: var(--status-fail-bg, color-mix(in srgb, var(--destructive, #ef4444) 12%, transparent));
|
|
2296
|
+
color: var(--destructive, #b91c1c);
|
|
2297
|
+
border-color: color-mix(in srgb, var(--destructive, #ef4444) 35%, transparent);
|
|
2298
|
+
text-transform: uppercase;
|
|
2299
|
+
letter-spacing: 0.04em;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2269
2302
|
.scenario-duration {
|
|
2270
2303
|
font-size: 0.75rem;
|
|
2271
2304
|
color: var(--muted-foreground);
|
|
@@ -2422,6 +2455,27 @@ body {
|
|
|
2422
2455
|
border: 1px solid var(--border);
|
|
2423
2456
|
}
|
|
2424
2457
|
|
|
2458
|
+
.attachment-unavailable {
|
|
2459
|
+
padding: 0.75rem 1rem;
|
|
2460
|
+
border: 1px dashed var(--border);
|
|
2461
|
+
border-radius: calc(var(--radius) - 2px);
|
|
2462
|
+
background: var(--muted, transparent);
|
|
2463
|
+
color: var(--muted-foreground);
|
|
2464
|
+
font-size: 0.8125rem;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
.attachment-unavailable-label {
|
|
2468
|
+
font-weight: 600;
|
|
2469
|
+
margin-bottom: 0.25rem;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
.attachment-unavailable-path {
|
|
2473
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
2474
|
+
font-size: 0.75rem;
|
|
2475
|
+
word-break: break-all;
|
|
2476
|
+
opacity: 0.8;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2425
2479
|
/* ============================================================================
|
|
2426
2480
|
Chevron Icon - smooth rotation
|
|
2427
2481
|
============================================================================ */
|
|
@@ -2987,6 +3041,84 @@ body {
|
|
|
2987
3041
|
font-style: italic;
|
|
2988
3042
|
}
|
|
2989
3043
|
|
|
3044
|
+
.doc-screenshot-missing {
|
|
3045
|
+
padding: 0.75rem 1rem;
|
|
3046
|
+
border: 1px dashed var(--border);
|
|
3047
|
+
border-radius: calc(var(--radius) - 2px);
|
|
3048
|
+
background: var(--muted, transparent);
|
|
3049
|
+
color: var(--muted-foreground);
|
|
3050
|
+
font-size: 0.8125rem;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
.doc-screenshot-missing-label {
|
|
3054
|
+
font-weight: 600;
|
|
3055
|
+
margin-bottom: 0.25rem;
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
.doc-screenshot-missing-path {
|
|
3059
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
3060
|
+
font-size: 0.75rem;
|
|
3061
|
+
word-break: break-all;
|
|
3062
|
+
opacity: 0.8;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
/* ============================================================================
|
|
3066
|
+
Documentation Entries - Visual Check
|
|
3067
|
+
============================================================================ */
|
|
3068
|
+
.doc-visual {
|
|
3069
|
+
margin-bottom: 0.5rem;
|
|
3070
|
+
padding: 0.75rem;
|
|
3071
|
+
border: 1px solid var(--border);
|
|
3072
|
+
border-radius: calc(var(--radius) - 2px);
|
|
3073
|
+
background: var(--muted, transparent);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
.doc-visual:last-child {
|
|
3077
|
+
margin-bottom: 0;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
.doc-visual-header {
|
|
3081
|
+
font-size: 0.8125rem;
|
|
3082
|
+
font-weight: 600;
|
|
3083
|
+
margin-bottom: 0.5rem;
|
|
3084
|
+
display: flex;
|
|
3085
|
+
align-items: center;
|
|
3086
|
+
gap: 0.5rem;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
.doc-visual-status {
|
|
3090
|
+
font-size: 0.6875rem;
|
|
3091
|
+
font-family: var(--font-mono);
|
|
3092
|
+
font-weight: 500;
|
|
3093
|
+
padding: 0.125rem 0.5rem;
|
|
3094
|
+
border-radius: 9999px;
|
|
3095
|
+
background: var(--tag-bg);
|
|
3096
|
+
color: var(--tag-color);
|
|
3097
|
+
border: 1px solid var(--tag-border);
|
|
3098
|
+
text-transform: uppercase;
|
|
3099
|
+
letter-spacing: 0.04em;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
.doc-visual-grid {
|
|
3103
|
+
display: grid;
|
|
3104
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
3105
|
+
gap: 0.5rem;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
.doc-visual-item {
|
|
3109
|
+
display: flex;
|
|
3110
|
+
flex-direction: column;
|
|
3111
|
+
gap: 0.25rem;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
.doc-visual-label {
|
|
3115
|
+
font-size: 0.6875rem;
|
|
3116
|
+
font-weight: 600;
|
|
3117
|
+
color: var(--muted-foreground);
|
|
3118
|
+
text-transform: uppercase;
|
|
3119
|
+
letter-spacing: 0.04em;
|
|
3120
|
+
}
|
|
3121
|
+
|
|
2990
3122
|
/* ============================================================================
|
|
2991
3123
|
Documentation Entries - Custom
|
|
2992
3124
|
============================================================================ */
|
|
@@ -12964,11 +13096,18 @@ function renderTagBar(args, deps) {
|
|
|
12964
13096
|
</div>`;
|
|
12965
13097
|
}
|
|
12966
13098
|
|
|
13099
|
+
// src/notifiers/ansi-strip.ts
|
|
13100
|
+
function stripAnsi(text2) {
|
|
13101
|
+
return text2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
13102
|
+
}
|
|
13103
|
+
|
|
12967
13104
|
// src/formatters/html/renderers/error-box.ts
|
|
12968
13105
|
function renderErrorBox(args, deps) {
|
|
12969
|
-
const
|
|
13106
|
+
const message = stripAnsi(args.message);
|
|
13107
|
+
const stack = args.stack != null ? stripAnsi(args.stack) : void 0;
|
|
13108
|
+
const body = stack != null ? `${deps.escapeHtml(message)}
|
|
12970
13109
|
|
|
12971
|
-
${deps.escapeHtml(
|
|
13110
|
+
${deps.escapeHtml(stack)}` : deps.escapeHtml(message);
|
|
12972
13111
|
return `<div class="error-box">${body}</div>`;
|
|
12973
13112
|
}
|
|
12974
13113
|
|
|
@@ -12981,6 +13120,7 @@ function renderAttachments(args, deps) {
|
|
|
12981
13120
|
const isImage = att.mediaType.startsWith("image/");
|
|
12982
13121
|
const isVideo = att.mediaType.startsWith("video/");
|
|
12983
13122
|
const isBase64 = att.contentEncoding === "BASE64";
|
|
13123
|
+
const isUnreachableFsPath = deps.embedScreenshots && !isBase64 && typeof att.body === "string" && /^(?:[/\\]|[A-Za-z]:[/\\])/.test(att.body);
|
|
12984
13124
|
if (isImage && deps.embedScreenshots && isBase64) {
|
|
12985
13125
|
return `
|
|
12986
13126
|
<div class="attachment">
|
|
@@ -12988,12 +13128,19 @@ function renderAttachments(args, deps) {
|
|
|
12988
13128
|
<img class="attachment-image" src="data:${att.mediaType};base64,${att.body}" alt="${deps.escapeHtml(att.name)}" />
|
|
12989
13129
|
</div>`;
|
|
12990
13130
|
}
|
|
12991
|
-
if (isVideo && deps.embedScreenshots) {
|
|
13131
|
+
if (isVideo && deps.embedScreenshots && !isUnreachableFsPath) {
|
|
12992
13132
|
const src = isBase64 ? `data:${att.mediaType};base64,${att.body}` : att.body;
|
|
12993
13133
|
return `
|
|
12994
13134
|
<div class="attachment">
|
|
12995
13135
|
${deps.escapeHtml(att.name)}
|
|
12996
13136
|
<video class="attachment-video" controls src="${deps.escapeHtml(src)}"></video>
|
|
13137
|
+
</div>`;
|
|
13138
|
+
}
|
|
13139
|
+
if (isUnreachableFsPath) {
|
|
13140
|
+
return `
|
|
13141
|
+
<div class="attachment attachment-unavailable">
|
|
13142
|
+
<div class="attachment-unavailable-label">${deps.escapeHtml(att.name)} unavailable</div>
|
|
13143
|
+
<div class="attachment-unavailable-path">${deps.escapeHtml(att.body)}</div>
|
|
12997
13144
|
</div>`;
|
|
12998
13145
|
}
|
|
12999
13146
|
const href = isBase64 ? `data:${att.mediaType};base64,${att.body}` : att.body;
|
|
@@ -13076,13 +13223,40 @@ function renderDocScreenshot(entry, deps) {
|
|
|
13076
13223
|
const alt = entry.alt ?? "Screenshot";
|
|
13077
13224
|
const embedEnabled = deps.embedScreenshots ?? true;
|
|
13078
13225
|
const isRemote = /^(?:https?:|data:)/i.test(entry.path);
|
|
13079
|
-
const
|
|
13226
|
+
const embedAttempted = !isRemote && embedEnabled && !!deps.readScreenshot;
|
|
13227
|
+
const inlined = embedAttempted ? deps.readScreenshot(entry.path) : void 0;
|
|
13228
|
+
const isAbsoluteFsPath = !isRemote && /^(?:[/\\]|[A-Za-z]:[/\\])/.test(entry.path);
|
|
13229
|
+
if (embedAttempted && inlined === void 0 && isAbsoluteFsPath) {
|
|
13230
|
+
const captionHtml = entry.alt ? `<div class="doc-screenshot-caption">${deps.escapeHtml(entry.alt)}</div>` : "";
|
|
13231
|
+
return `<div class="doc-screenshot doc-screenshot-missing">
|
|
13232
|
+
<div class="doc-screenshot-missing-label">Screenshot unavailable</div>
|
|
13233
|
+
<div class="doc-screenshot-missing-path">${deps.escapeHtml(entry.path)}</div>
|
|
13234
|
+
${captionHtml}
|
|
13235
|
+
</div>`;
|
|
13236
|
+
}
|
|
13237
|
+
const src = inlined ?? entry.path;
|
|
13080
13238
|
return `<div class="doc-screenshot">
|
|
13081
13239
|
<img src="${deps.escapeHtml(src)}" alt="${deps.escapeHtml(alt)}" class="doc-screenshot-img" />
|
|
13082
13240
|
${entry.alt ? `<div class="doc-screenshot-caption">${deps.escapeHtml(entry.alt)}</div>` : ""}
|
|
13083
13241
|
</div>`;
|
|
13084
13242
|
}
|
|
13085
13243
|
function renderDocCustom(entry, deps) {
|
|
13244
|
+
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
13245
|
+
const data = entry.data;
|
|
13246
|
+
const status = typeof data.status === "string" ? data.status : "unknown";
|
|
13247
|
+
const baseline = typeof data.baseline === "string" ? data.baseline : void 0;
|
|
13248
|
+
const actual = typeof data.actual === "string" ? data.actual : void 0;
|
|
13249
|
+
const diff = typeof data.diff === "string" ? data.diff : void 0;
|
|
13250
|
+
const maybeImg = (src, label) => src ? `<div class="doc-visual-item"><div class="doc-visual-label">${deps.escapeHtml(label ?? "")}</div><img src="${deps.escapeHtml(src)}" alt="${deps.escapeHtml(label ?? "visual image")}" class="doc-screenshot-img" /></div>` : "";
|
|
13251
|
+
return `<div class="doc-visual">
|
|
13252
|
+
<div class="doc-visual-header">Visual Check <span class="doc-visual-status">${deps.escapeHtml(status)}</span></div>
|
|
13253
|
+
<div class="doc-visual-grid">
|
|
13254
|
+
${maybeImg(baseline, "Baseline")}
|
|
13255
|
+
${maybeImg(actual, "Actual")}
|
|
13256
|
+
${maybeImg(diff, "Diff")}
|
|
13257
|
+
</div>
|
|
13258
|
+
</div>`;
|
|
13259
|
+
}
|
|
13086
13260
|
const dataStr = JSON.stringify(entry.data, null, 2);
|
|
13087
13261
|
return `<div class="doc-custom">
|
|
13088
13262
|
<div class="doc-custom-type">${deps.escapeHtml(entry.type)}</div>
|
|
@@ -13151,8 +13325,11 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
13151
13325
|
</div>${stepDocs}`;
|
|
13152
13326
|
}
|
|
13153
13327
|
function renderSteps(args, deps) {
|
|
13328
|
+
const byStepId = new Map(
|
|
13329
|
+
args.stepResults.filter((sr) => typeof sr.stepId === "string" && sr.stepId.length > 0).map((sr) => [sr.stepId, sr])
|
|
13330
|
+
);
|
|
13154
13331
|
const stepsHtml = args.steps.map((step, index) => {
|
|
13155
|
-
const stepResult = args.stepResults.find((sr) => sr.index === index);
|
|
13332
|
+
const stepResult = (step.id ? byStepId.get(step.id) : void 0) ?? args.stepResults.find((sr) => sr.index === index);
|
|
13156
13333
|
return renderStep(step, stepResult, index, deps);
|
|
13157
13334
|
}).join("");
|
|
13158
13335
|
return `<div class="steps">${stepsHtml}</div>`;
|
|
@@ -13205,6 +13382,7 @@ function renderScenario(args, deps) {
|
|
|
13205
13382
|
const duration = tc.durationMs > 0 ? `${(tc.durationMs / 1e3).toFixed(2)}s` : "";
|
|
13206
13383
|
const tags = tc.tags.map((t) => `<span class="tag">${deps.escapeHtml(t)}</span>`).join("");
|
|
13207
13384
|
const tickets = (tc.story.tickets ?? []).map((t) => renderTicket(t, deps.ticketUrlTemplate, deps.escapeHtml)).join("");
|
|
13385
|
+
const outcomeBadge = tc.rawStatus === "timeout" || tc.rawStatus === "interrupted" ? `<span class="tag outcome-tag">${deps.escapeHtml(tc.rawStatus)}</span>` : "";
|
|
13208
13386
|
const otelMeta = tc.story.meta?.otel;
|
|
13209
13387
|
let traceBadge = "";
|
|
13210
13388
|
if (otelMeta?.traceId) {
|
|
@@ -13272,7 +13450,7 @@ function renderScenario(args, deps) {
|
|
|
13272
13450
|
<span class="status-icon ${statusClass}">${statusIcon2}</span>
|
|
13273
13451
|
<span class="scenario-name">${deps.escapeHtml(tc.story.scenario)}</span>
|
|
13274
13452
|
</div>
|
|
13275
|
-
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13453
|
+
<div class="scenario-meta">${tags}${tickets}${outcomeBadge}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13276
13454
|
</div>
|
|
13277
13455
|
<div class="scenario-actions">
|
|
13278
13456
|
<button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
|
@@ -14276,6 +14454,9 @@ var MarkdownFormatter = class {
|
|
|
14276
14454
|
});
|
|
14277
14455
|
meta.push(`Tickets: ${ticketLinks.join(", ")}`);
|
|
14278
14456
|
}
|
|
14457
|
+
if (tc.rawStatus === "timeout" || tc.rawStatus === "interrupted") {
|
|
14458
|
+
meta.push(`Outcome: \`${tc.rawStatus}\``);
|
|
14459
|
+
}
|
|
14279
14460
|
const otelMeta = tc.story.meta?.otel;
|
|
14280
14461
|
if (otelMeta?.traceId) {
|
|
14281
14462
|
const traceTemplate = this.options.traceUrlTemplate;
|
|
@@ -14438,6 +14619,16 @@ var MarkdownFormatter = class {
|
|
|
14438
14619
|
lines.push(`${indent}`);
|
|
14439
14620
|
break;
|
|
14440
14621
|
case "custom":
|
|
14622
|
+
if (entry.type === "visual" && entry.data && typeof entry.data === "object") {
|
|
14623
|
+
const data = entry.data;
|
|
14624
|
+
const status = typeof data.status === "string" ? data.status : "unknown";
|
|
14625
|
+
lines.push(`${indent}**Visual Check** (${status})`);
|
|
14626
|
+
if (typeof data.baseline === "string") lines.push(`${indent}- Baseline: ${data.baseline}`);
|
|
14627
|
+
if (typeof data.actual === "string") lines.push(`${indent}- Actual: ${data.actual}`);
|
|
14628
|
+
if (typeof data.diff === "string") lines.push(`${indent}- Diff: ${data.diff}`);
|
|
14629
|
+
lines.push(`${indent}`);
|
|
14630
|
+
break;
|
|
14631
|
+
}
|
|
14441
14632
|
lines.push(`${indent}**[${entry.type}]**`);
|
|
14442
14633
|
lines.push(`${indent}`);
|
|
14443
14634
|
lines.push(`${indent}\`\`\`json`);
|
|
@@ -17089,6 +17280,14 @@ function adaptPlaywrightRun(testResults, options = {}) {
|
|
|
17089
17280
|
message: result.errors[0].message,
|
|
17090
17281
|
stack: result.errors[0].stack
|
|
17091
17282
|
} : void 0;
|
|
17283
|
+
const stepEvents = result.stepEvents?.length ? result.stepEvents : story.steps.map(
|
|
17284
|
+
(step, index) => step.durationMs !== void 0 ? {
|
|
17285
|
+
index,
|
|
17286
|
+
stepId: step.id,
|
|
17287
|
+
title: step.text,
|
|
17288
|
+
durationMs: step.durationMs
|
|
17289
|
+
} : void 0
|
|
17290
|
+
).filter((e) => e !== void 0);
|
|
17092
17291
|
testCases.push({
|
|
17093
17292
|
externalId: test.titlePath().join(" > "),
|
|
17094
17293
|
title: story.scenario,
|
|
@@ -17099,8 +17298,7 @@ function adaptPlaywrightRun(testResults, options = {}) {
|
|
|
17099
17298
|
status: mapPlaywrightStatus(result.status),
|
|
17100
17299
|
durationMs: result.duration,
|
|
17101
17300
|
error,
|
|
17102
|
-
stepEvents: void 0,
|
|
17103
|
-
// Playwright could provide step info, but we don't capture it yet
|
|
17301
|
+
stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
|
|
17104
17302
|
attachments,
|
|
17105
17303
|
meta: {
|
|
17106
17304
|
playwrightStatus: result.status,
|
|
@@ -18121,11 +18319,6 @@ function resolveTraceUrl(template, traceId) {
|
|
|
18121
18319
|
return template.replace(/\{traceId\}/g, traceId);
|
|
18122
18320
|
}
|
|
18123
18321
|
|
|
18124
|
-
// src/notifiers/ansi-strip.ts
|
|
18125
|
-
function stripAnsi(text2) {
|
|
18126
|
-
return text2.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
18127
|
-
}
|
|
18128
|
-
|
|
18129
18322
|
// src/notifiers/slack.ts
|
|
18130
18323
|
function truncate(text2, maxLen) {
|
|
18131
18324
|
if (text2.length <= maxLen) return text2;
|