executable-stories-playwright 2.0.0
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/index.d.ts +152 -0
- package/dist/index.js +330 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +27 -0
- package/dist/reporter.js +144 -0
- package/dist/reporter.js.map +1 -0
- package/package.json +48 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { TestInfo } from '@playwright/test';
|
|
2
|
+
import { StepKeyword } from 'executable-stories-formatters';
|
|
3
|
+
export { DocEntry, StepKeyword, StoryMeta, StoryStep } from 'executable-stories-formatters';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type definitions for executable-stories-playwright.
|
|
7
|
+
*
|
|
8
|
+
* Shared story types are re-exported from the formatters package.
|
|
9
|
+
* Playwright-specific types are defined here.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface KvOptions {
|
|
13
|
+
label: string;
|
|
14
|
+
value: unknown;
|
|
15
|
+
}
|
|
16
|
+
interface JsonOptions {
|
|
17
|
+
label: string;
|
|
18
|
+
value: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface CodeOptions {
|
|
21
|
+
label: string;
|
|
22
|
+
content: string;
|
|
23
|
+
lang?: string;
|
|
24
|
+
}
|
|
25
|
+
interface TableOptions {
|
|
26
|
+
label: string;
|
|
27
|
+
columns: string[];
|
|
28
|
+
rows: string[][];
|
|
29
|
+
}
|
|
30
|
+
interface LinkOptions {
|
|
31
|
+
label: string;
|
|
32
|
+
url: string;
|
|
33
|
+
}
|
|
34
|
+
interface SectionOptions {
|
|
35
|
+
title: string;
|
|
36
|
+
markdown: string;
|
|
37
|
+
}
|
|
38
|
+
interface MermaidOptions {
|
|
39
|
+
code: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
}
|
|
42
|
+
interface ScreenshotOptions {
|
|
43
|
+
path: string;
|
|
44
|
+
alt?: string;
|
|
45
|
+
}
|
|
46
|
+
interface CustomOptions {
|
|
47
|
+
type: string;
|
|
48
|
+
data: unknown;
|
|
49
|
+
}
|
|
50
|
+
/** All inline doc options that can be passed to step markers. */
|
|
51
|
+
interface StoryDocs {
|
|
52
|
+
note?: string;
|
|
53
|
+
tag?: string | string[];
|
|
54
|
+
kv?: Record<string, unknown>;
|
|
55
|
+
json?: JsonOptions;
|
|
56
|
+
table?: TableOptions;
|
|
57
|
+
link?: LinkOptions;
|
|
58
|
+
code?: CodeOptions;
|
|
59
|
+
section?: SectionOptions;
|
|
60
|
+
mermaid?: MermaidOptions;
|
|
61
|
+
screenshot?: ScreenshotOptions;
|
|
62
|
+
custom?: CustomOptions;
|
|
63
|
+
}
|
|
64
|
+
/** Options for story.init(). */
|
|
65
|
+
interface StoryOptions {
|
|
66
|
+
tags?: string[];
|
|
67
|
+
ticket?: string | string[];
|
|
68
|
+
meta?: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
/** Options for story.attach(). */
|
|
71
|
+
interface AttachmentOptions {
|
|
72
|
+
name: string;
|
|
73
|
+
mediaType: string;
|
|
74
|
+
path?: string;
|
|
75
|
+
body?: string | Buffer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Playwright story.* API for executable-stories.
|
|
80
|
+
*
|
|
81
|
+
* Uses native Playwright test() with opt-in documentation:
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { test, expect } from '@playwright/test';
|
|
86
|
+
* import { story } from 'executable-stories-playwright';
|
|
87
|
+
*
|
|
88
|
+
* test.describe('Calculator', () => {
|
|
89
|
+
* test('adds two numbers', async ({ page }, testInfo) => {
|
|
90
|
+
* story.init(testInfo);
|
|
91
|
+
*
|
|
92
|
+
* story.given('two numbers 5 and 3');
|
|
93
|
+
* const a = 5;
|
|
94
|
+
* const b = 3;
|
|
95
|
+
*
|
|
96
|
+
* story.when('I add them together');
|
|
97
|
+
* const result = a + b;
|
|
98
|
+
*
|
|
99
|
+
* story.then('the result is 8');
|
|
100
|
+
* expect(result).toBe(8);
|
|
101
|
+
* });
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
declare function init(testInfo: TestInfo, options?: StoryOptions): void;
|
|
107
|
+
/**
|
|
108
|
+
* Wrap a function as a step with timing and error capture.
|
|
109
|
+
* Records the step with `wrapped: true` and `durationMs`.
|
|
110
|
+
*/
|
|
111
|
+
declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
|
|
112
|
+
/**
|
|
113
|
+
* Wrap an assertion as a Then step with timing and error capture.
|
|
114
|
+
* Shorthand for `story.fn('Then', text, body)`.
|
|
115
|
+
*/
|
|
116
|
+
declare function storyExpect<T>(text: string, body: () => T): T;
|
|
117
|
+
declare function playwrightAttach(options: AttachmentOptions): void;
|
|
118
|
+
declare const story: {
|
|
119
|
+
init: typeof init;
|
|
120
|
+
given: (text: string, docs?: StoryDocs) => void;
|
|
121
|
+
when: (text: string, docs?: StoryDocs) => void;
|
|
122
|
+
then: (text: string, docs?: StoryDocs) => void;
|
|
123
|
+
and: (text: string, docs?: StoryDocs) => void;
|
|
124
|
+
but: (text: string, docs?: StoryDocs) => void;
|
|
125
|
+
arrange: (text: string, docs?: StoryDocs) => void;
|
|
126
|
+
act: (text: string, docs?: StoryDocs) => void;
|
|
127
|
+
assert: (text: string, docs?: StoryDocs) => void;
|
|
128
|
+
setup: (text: string, docs?: StoryDocs) => void;
|
|
129
|
+
context: (text: string, docs?: StoryDocs) => void;
|
|
130
|
+
execute: (text: string, docs?: StoryDocs) => void;
|
|
131
|
+
action: (text: string, docs?: StoryDocs) => void;
|
|
132
|
+
verify: (text: string, docs?: StoryDocs) => void;
|
|
133
|
+
note(text: string): void;
|
|
134
|
+
tag(name: string | string[]): void;
|
|
135
|
+
kv(options: KvOptions): void;
|
|
136
|
+
json(options: JsonOptions): void;
|
|
137
|
+
code(options: CodeOptions): void;
|
|
138
|
+
table(options: TableOptions): void;
|
|
139
|
+
link(options: LinkOptions): void;
|
|
140
|
+
section(options: SectionOptions): void;
|
|
141
|
+
mermaid(options: MermaidOptions): void;
|
|
142
|
+
screenshot(options: ScreenshotOptions): void;
|
|
143
|
+
custom(options: CustomOptions): void;
|
|
144
|
+
attach: typeof playwrightAttach;
|
|
145
|
+
startTimer(): number;
|
|
146
|
+
endTimer(token: number): void;
|
|
147
|
+
fn: typeof fn;
|
|
148
|
+
expect: typeof storyExpect;
|
|
149
|
+
};
|
|
150
|
+
type Story = typeof story;
|
|
151
|
+
|
|
152
|
+
export { type CodeOptions, type CustomOptions, type JsonOptions, type KvOptions, type LinkOptions, type MermaidOptions, type ScreenshotOptions, type SectionOptions, type Story, type StoryDocs, type StoryOptions, type TableOptions, story };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// src/story-api.ts
|
|
2
|
+
var activeContext = null;
|
|
3
|
+
var activeTestInfo = null;
|
|
4
|
+
var sourceOrderCounter = 0;
|
|
5
|
+
function getContext() {
|
|
6
|
+
if (!activeContext) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"story.init(testInfo) must be called first. Use: test('name', async ({ page }, testInfo) => { story.init(testInfo); ... });"
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
return activeContext;
|
|
12
|
+
}
|
|
13
|
+
function normalizeTickets(ticket) {
|
|
14
|
+
if (!ticket) return void 0;
|
|
15
|
+
return Array.isArray(ticket) ? ticket : [ticket];
|
|
16
|
+
}
|
|
17
|
+
function convertStoryDocsToEntries(docs) {
|
|
18
|
+
const entries = [];
|
|
19
|
+
if (docs.note) {
|
|
20
|
+
entries.push({ kind: "note", text: docs.note, phase: "runtime" });
|
|
21
|
+
}
|
|
22
|
+
if (docs.tag) {
|
|
23
|
+
const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];
|
|
24
|
+
entries.push({ kind: "tag", names, phase: "runtime" });
|
|
25
|
+
}
|
|
26
|
+
if (docs.kv) {
|
|
27
|
+
for (const [label, value] of Object.entries(docs.kv)) {
|
|
28
|
+
entries.push({ kind: "kv", label, value, phase: "runtime" });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (docs.code) {
|
|
32
|
+
entries.push({
|
|
33
|
+
kind: "code",
|
|
34
|
+
label: docs.code.label,
|
|
35
|
+
content: docs.code.content,
|
|
36
|
+
lang: docs.code.lang,
|
|
37
|
+
phase: "runtime"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (docs.json) {
|
|
41
|
+
entries.push({
|
|
42
|
+
kind: "code",
|
|
43
|
+
label: docs.json.label,
|
|
44
|
+
content: JSON.stringify(docs.json.value, null, 2),
|
|
45
|
+
lang: "json",
|
|
46
|
+
phase: "runtime"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (docs.table) {
|
|
50
|
+
entries.push({
|
|
51
|
+
kind: "table",
|
|
52
|
+
label: docs.table.label,
|
|
53
|
+
columns: docs.table.columns,
|
|
54
|
+
rows: docs.table.rows,
|
|
55
|
+
phase: "runtime"
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (docs.link) {
|
|
59
|
+
entries.push({
|
|
60
|
+
kind: "link",
|
|
61
|
+
label: docs.link.label,
|
|
62
|
+
url: docs.link.url,
|
|
63
|
+
phase: "runtime"
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (docs.section) {
|
|
67
|
+
entries.push({
|
|
68
|
+
kind: "section",
|
|
69
|
+
title: docs.section.title,
|
|
70
|
+
markdown: docs.section.markdown,
|
|
71
|
+
phase: "runtime"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (docs.mermaid) {
|
|
75
|
+
entries.push({
|
|
76
|
+
kind: "mermaid",
|
|
77
|
+
code: docs.mermaid.code,
|
|
78
|
+
title: docs.mermaid.title,
|
|
79
|
+
phase: "runtime"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (docs.screenshot) {
|
|
83
|
+
entries.push({
|
|
84
|
+
kind: "screenshot",
|
|
85
|
+
path: docs.screenshot.path,
|
|
86
|
+
alt: docs.screenshot.alt,
|
|
87
|
+
phase: "runtime"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (docs.custom) {
|
|
91
|
+
entries.push({
|
|
92
|
+
kind: "custom",
|
|
93
|
+
type: docs.custom.type,
|
|
94
|
+
data: docs.custom.data,
|
|
95
|
+
phase: "runtime"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
99
|
+
}
|
|
100
|
+
function attachDoc(entry) {
|
|
101
|
+
const ctx = getContext();
|
|
102
|
+
if (ctx.currentStep) {
|
|
103
|
+
ctx.currentStep.docs ??= [];
|
|
104
|
+
ctx.currentStep.docs.push(entry);
|
|
105
|
+
} else {
|
|
106
|
+
ctx.meta.docs ??= [];
|
|
107
|
+
ctx.meta.docs.push(entry);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function extractSuitePath(testInfo) {
|
|
111
|
+
const titlePath = testInfo.titlePath;
|
|
112
|
+
if (titlePath.length <= 2) {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
const suitePath = titlePath.slice(1, -1);
|
|
116
|
+
return suitePath.length > 0 ? suitePath : void 0;
|
|
117
|
+
}
|
|
118
|
+
function createStepMarker(keyword) {
|
|
119
|
+
return function stepMarker(text, docs) {
|
|
120
|
+
const ctx = getContext();
|
|
121
|
+
const step = {
|
|
122
|
+
id: `step-${ctx.stepCounter++}`,
|
|
123
|
+
keyword,
|
|
124
|
+
text,
|
|
125
|
+
docs: docs ? convertStoryDocsToEntries(docs) : []
|
|
126
|
+
};
|
|
127
|
+
ctx.meta.steps.push(step);
|
|
128
|
+
ctx.currentStep = step;
|
|
129
|
+
syncAnnotationToTest();
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function init(testInfo, options) {
|
|
133
|
+
const meta = {
|
|
134
|
+
scenario: testInfo.title,
|
|
135
|
+
steps: [],
|
|
136
|
+
suitePath: extractSuitePath(testInfo),
|
|
137
|
+
tags: options?.tags,
|
|
138
|
+
tickets: normalizeTickets(options?.ticket),
|
|
139
|
+
meta: options?.meta,
|
|
140
|
+
sourceOrder: sourceOrderCounter++
|
|
141
|
+
};
|
|
142
|
+
testInfo.annotations.push({
|
|
143
|
+
type: "story-meta",
|
|
144
|
+
description: JSON.stringify(meta)
|
|
145
|
+
});
|
|
146
|
+
activeContext = {
|
|
147
|
+
meta,
|
|
148
|
+
currentStep: null,
|
|
149
|
+
stepCounter: 0,
|
|
150
|
+
attachments: [],
|
|
151
|
+
activeTimers: /* @__PURE__ */ new Map(),
|
|
152
|
+
timerCounter: 0
|
|
153
|
+
};
|
|
154
|
+
activeTestInfo = testInfo;
|
|
155
|
+
}
|
|
156
|
+
function syncAnnotationToTest() {
|
|
157
|
+
if (!activeTestInfo || !activeContext) return;
|
|
158
|
+
const annotation = activeTestInfo.annotations.find(
|
|
159
|
+
(a) => a.type === "story-meta"
|
|
160
|
+
);
|
|
161
|
+
if (annotation) {
|
|
162
|
+
annotation.description = JSON.stringify(activeContext.meta);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function fn(keyword, text, body) {
|
|
166
|
+
const ctx = getContext();
|
|
167
|
+
const step = {
|
|
168
|
+
id: `step-${ctx.stepCounter++}`,
|
|
169
|
+
keyword,
|
|
170
|
+
text,
|
|
171
|
+
docs: [],
|
|
172
|
+
wrapped: true
|
|
173
|
+
};
|
|
174
|
+
ctx.meta.steps.push(step);
|
|
175
|
+
ctx.currentStep = step;
|
|
176
|
+
syncAnnotationToTest();
|
|
177
|
+
const start = performance.now();
|
|
178
|
+
try {
|
|
179
|
+
const result = body();
|
|
180
|
+
if (result instanceof Promise) {
|
|
181
|
+
return result.then(
|
|
182
|
+
(val) => {
|
|
183
|
+
step.durationMs = performance.now() - start;
|
|
184
|
+
syncAnnotationToTest();
|
|
185
|
+
return val;
|
|
186
|
+
},
|
|
187
|
+
(err) => {
|
|
188
|
+
step.durationMs = performance.now() - start;
|
|
189
|
+
syncAnnotationToTest();
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
step.durationMs = performance.now() - start;
|
|
195
|
+
syncAnnotationToTest();
|
|
196
|
+
return result;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
step.durationMs = performance.now() - start;
|
|
199
|
+
syncAnnotationToTest();
|
|
200
|
+
throw err;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function storyExpect(text, body) {
|
|
204
|
+
return fn("Then", text, body);
|
|
205
|
+
}
|
|
206
|
+
function playwrightAttach(options) {
|
|
207
|
+
const ctx = getContext();
|
|
208
|
+
const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
|
|
209
|
+
ctx.attachments.push({
|
|
210
|
+
...options,
|
|
211
|
+
stepId: ctx.currentStep?.id
|
|
212
|
+
});
|
|
213
|
+
syncAnnotationToTest();
|
|
214
|
+
if (activeTestInfo) {
|
|
215
|
+
const attachOptions = {
|
|
216
|
+
name: options.name,
|
|
217
|
+
contentType: options.mediaType
|
|
218
|
+
};
|
|
219
|
+
if (options.path) attachOptions.path = options.path;
|
|
220
|
+
if (options.body) attachOptions.body = options.body;
|
|
221
|
+
activeTestInfo.attach(options.name, attachOptions);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
var story = {
|
|
225
|
+
init,
|
|
226
|
+
// BDD step markers
|
|
227
|
+
given: createStepMarker("Given"),
|
|
228
|
+
when: createStepMarker("When"),
|
|
229
|
+
then: createStepMarker("Then"),
|
|
230
|
+
and: createStepMarker("And"),
|
|
231
|
+
but: createStepMarker("But"),
|
|
232
|
+
// AAA pattern aliases
|
|
233
|
+
arrange: createStepMarker("Given"),
|
|
234
|
+
act: createStepMarker("When"),
|
|
235
|
+
assert: createStepMarker("Then"),
|
|
236
|
+
// Additional aliases
|
|
237
|
+
setup: createStepMarker("Given"),
|
|
238
|
+
context: createStepMarker("Given"),
|
|
239
|
+
execute: createStepMarker("When"),
|
|
240
|
+
action: createStepMarker("When"),
|
|
241
|
+
verify: createStepMarker("Then"),
|
|
242
|
+
// Standalone doc methods
|
|
243
|
+
note(text) {
|
|
244
|
+
attachDoc({ kind: "note", text, phase: "runtime" });
|
|
245
|
+
syncAnnotationToTest();
|
|
246
|
+
},
|
|
247
|
+
tag(name) {
|
|
248
|
+
const names = Array.isArray(name) ? name : [name];
|
|
249
|
+
attachDoc({ kind: "tag", names, phase: "runtime" });
|
|
250
|
+
syncAnnotationToTest();
|
|
251
|
+
},
|
|
252
|
+
kv(options) {
|
|
253
|
+
attachDoc({ kind: "kv", label: options.label, value: options.value, phase: "runtime" });
|
|
254
|
+
syncAnnotationToTest();
|
|
255
|
+
},
|
|
256
|
+
json(options) {
|
|
257
|
+
const content = JSON.stringify(options.value, null, 2);
|
|
258
|
+
attachDoc({ kind: "code", label: options.label, content, lang: "json", phase: "runtime" });
|
|
259
|
+
syncAnnotationToTest();
|
|
260
|
+
},
|
|
261
|
+
code(options) {
|
|
262
|
+
attachDoc({ kind: "code", label: options.label, content: options.content, lang: options.lang, phase: "runtime" });
|
|
263
|
+
syncAnnotationToTest();
|
|
264
|
+
},
|
|
265
|
+
table(options) {
|
|
266
|
+
attachDoc({ kind: "table", label: options.label, columns: options.columns, rows: options.rows, phase: "runtime" });
|
|
267
|
+
syncAnnotationToTest();
|
|
268
|
+
},
|
|
269
|
+
link(options) {
|
|
270
|
+
attachDoc({ kind: "link", label: options.label, url: options.url, phase: "runtime" });
|
|
271
|
+
syncAnnotationToTest();
|
|
272
|
+
},
|
|
273
|
+
section(options) {
|
|
274
|
+
attachDoc({ kind: "section", title: options.title, markdown: options.markdown, phase: "runtime" });
|
|
275
|
+
syncAnnotationToTest();
|
|
276
|
+
},
|
|
277
|
+
mermaid(options) {
|
|
278
|
+
attachDoc({ kind: "mermaid", code: options.code, title: options.title, phase: "runtime" });
|
|
279
|
+
syncAnnotationToTest();
|
|
280
|
+
},
|
|
281
|
+
screenshot(options) {
|
|
282
|
+
attachDoc({ kind: "screenshot", path: options.path, alt: options.alt, phase: "runtime" });
|
|
283
|
+
syncAnnotationToTest();
|
|
284
|
+
},
|
|
285
|
+
custom(options) {
|
|
286
|
+
attachDoc({ kind: "custom", type: options.type, data: options.data, phase: "runtime" });
|
|
287
|
+
syncAnnotationToTest();
|
|
288
|
+
},
|
|
289
|
+
// Attachments
|
|
290
|
+
attach: playwrightAttach,
|
|
291
|
+
// Step timing
|
|
292
|
+
startTimer() {
|
|
293
|
+
const ctx = getContext();
|
|
294
|
+
const token = ctx.timerCounter++;
|
|
295
|
+
const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
|
|
296
|
+
ctx.activeTimers.set(token, {
|
|
297
|
+
start: performance.now(),
|
|
298
|
+
stepIndex: stepIndex !== void 0 && stepIndex >= 0 ? stepIndex : void 0,
|
|
299
|
+
stepId: ctx.currentStep?.id,
|
|
300
|
+
consumed: false
|
|
301
|
+
});
|
|
302
|
+
syncAnnotationToTest();
|
|
303
|
+
return token;
|
|
304
|
+
},
|
|
305
|
+
endTimer(token) {
|
|
306
|
+
const ctx = getContext();
|
|
307
|
+
const entry = ctx.activeTimers.get(token);
|
|
308
|
+
if (!entry || entry.consumed) return;
|
|
309
|
+
entry.consumed = true;
|
|
310
|
+
const durationMs = performance.now() - entry.start;
|
|
311
|
+
let step;
|
|
312
|
+
if (entry.stepId) {
|
|
313
|
+
step = ctx.meta.steps.find((s) => s.id === entry.stepId);
|
|
314
|
+
}
|
|
315
|
+
if (!step && entry.stepIndex !== void 0) {
|
|
316
|
+
step = ctx.meta.steps[entry.stepIndex];
|
|
317
|
+
}
|
|
318
|
+
if (step) {
|
|
319
|
+
step.durationMs = durationMs;
|
|
320
|
+
}
|
|
321
|
+
syncAnnotationToTest();
|
|
322
|
+
},
|
|
323
|
+
// Step wrappers
|
|
324
|
+
fn,
|
|
325
|
+
expect: storyExpect
|
|
326
|
+
};
|
|
327
|
+
export {
|
|
328
|
+
story
|
|
329
|
+
};
|
|
330
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/story-api.ts"],"sourcesContent":["/**\n * Playwright story.* API for executable-stories.\n *\n * Uses native Playwright test() with opt-in documentation:\n *\n * @example\n * ```ts\n * import { test, expect } from '@playwright/test';\n * import { story } from 'executable-stories-playwright';\n *\n * test.describe('Calculator', () => {\n * test('adds two numbers', async ({ page }, testInfo) => {\n * story.init(testInfo);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport type { TestInfo } from '@playwright/test';\nimport type {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n} from './types';\nimport type {\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n ScopedAttachment,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n} from './types';\n\n// ============================================================================\n// Internal types\n// ============================================================================\n\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n meta: StoryMeta;\n currentStep: StoryStep | null;\n stepCounter: number;\n attachments: ScopedAttachment[];\n activeTimers: Map<number, TimerEntry>;\n timerCounter: number;\n}\n\n// ============================================================================\n// Playwright-specific context\n// ============================================================================\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Reference to testInfo for attaching metadata */\nlet activeTestInfo: TestInfo | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init(testInfo) must be called first. Use: test('name', async ({ page }, testInfo) => { story.init(testInfo); ... });\",\n );\n }\n return activeContext;\n}\n\n// ============================================================================\n// Helper functions (inlined from core)\n// ============================================================================\n\nfunction normalizeTickets(ticket: string | string[] | undefined): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\n}\n\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: 'tag', names, phase: 'runtime' });\n }\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: 'kv', label, value, phase: 'runtime' });\n }\n }\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\nfunction attachDoc(entry: DocEntry): void {\n const ctx = getContext();\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n}\n\n// ============================================================================\n// Suite path extraction\n// ============================================================================\n\n/**\n * Extract the suite path from testInfo.titlePath.\n * Playwright's titlePath includes: [projectName, ...describeTitles, testTitle]\n * We want just the describe titles (excluding project and test name).\n */\nfunction extractSuitePath(testInfo: TestInfo): string[] | undefined {\n const titlePath = testInfo.titlePath;\n if (titlePath.length <= 2) {\n return undefined;\n }\n const suitePath = titlePath.slice(1, -1);\n return suitePath.length > 0 ? suitePath : undefined;\n}\n\n// ============================================================================\n// Step markers\n// ============================================================================\n\nfunction createStepMarker(keyword: StepKeyword) {\n return function stepMarker(text: string, docs?: StoryDocs): void {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: docs ? convertStoryDocsToEntries(docs) : [],\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n };\n}\n\n// ============================================================================\n// story.init() - Playwright-specific\n// ============================================================================\n\nfunction init(testInfo: TestInfo, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: testInfo.title,\n steps: [],\n suitePath: extractSuitePath(testInfo),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n testInfo.annotations.push({\n type: 'story-meta',\n description: JSON.stringify(meta),\n });\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n activeTestInfo = testInfo;\n}\n\n/**\n * Update the story-meta annotation on testInfo with the current meta (including steps).\n * Called after each step/doc so the reporter sees the full story in onTestEnd.\n */\nfunction syncAnnotationToTest(): void {\n if (!activeTestInfo || !activeContext) return;\n const annotation = activeTestInfo.annotations.find(\n (a) => a.type === 'story-meta',\n );\n if (annotation) {\n annotation.description = JSON.stringify(activeContext.meta);\n }\n}\n\n// ============================================================================\n// story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function as a step with timing and error capture.\n * Records the step with `wrapped: true` and `durationMs`.\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: [],\n wrapped: true,\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n\n const start = performance.now();\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step with timing and error capture.\n * Shorthand for `story.fn('Then', text, body)`.\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Playwright-specific attach\n// ============================================================================\n\nfunction playwrightAttach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepId: ctx.currentStep?.id,\n });\n syncAnnotationToTest();\n\n if (activeTestInfo) {\n const attachOptions: { name: string; contentType: string; path?: string; body?: string | Buffer } = {\n name: options.name,\n contentType: options.mediaType,\n };\n if (options.path) attachOptions.path = options.path;\n if (options.body) attachOptions.body = options.body;\n activeTestInfo.attach(options.name, attachOptions);\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\nexport const story = {\n init,\n\n // BDD step markers\n given: createStepMarker('Given'),\n when: createStepMarker('When'),\n then: createStepMarker('Then'),\n and: createStepMarker('And'),\n but: createStepMarker('But'),\n\n // AAA pattern aliases\n arrange: createStepMarker('Given'),\n act: createStepMarker('When'),\n assert: createStepMarker('Then'),\n\n // Additional aliases\n setup: createStepMarker('Given'),\n context: createStepMarker('Given'),\n execute: createStepMarker('When'),\n action: createStepMarker('When'),\n verify: createStepMarker('Then'),\n\n // Standalone doc methods\n note(text: string): void {\n attachDoc({ kind: 'note', text, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n kv(options: KvOptions): void {\n attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n json(options: JsonOptions): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({ kind: 'code', label: options.label, content, lang: 'json', phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n code(options: CodeOptions): void {\n attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n table(options: TableOptions): void {\n attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n link(options: LinkOptions): void {\n attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n section(options: SectionOptions): void {\n attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n mermaid(options: MermaidOptions): void {\n attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n screenshot(options: ScreenshotOptions): void {\n attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n custom(options: CustomOptions): void {\n attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n // Attachments\n attach: playwrightAttach,\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n syncAnnotationToTest();\n return token;\n },\n\n endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n }\n syncAnnotationToTest();\n },\n\n // Step wrappers\n fn,\n expect: storyExpect,\n};\n\nexport type Story = typeof story;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Reporter, FullConfig, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
import { FormatterOptions } from 'executable-stories-formatters';
|
|
3
|
+
export { ColocatedStyle, FormatterOptions, OutputFormat, OutputMode, OutputRule } from 'executable-stories-formatters';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Playwright reporter for executable-stories.
|
|
7
|
+
* Generates reports using the executable-stories-formatters package.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface StoryReporterOptions extends FormatterOptions {
|
|
11
|
+
/** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */
|
|
12
|
+
rawRunPath?: string;
|
|
13
|
+
}
|
|
14
|
+
declare class StoryReporter implements Reporter {
|
|
15
|
+
private options;
|
|
16
|
+
private scenarios;
|
|
17
|
+
private startTime;
|
|
18
|
+
private packageVersion;
|
|
19
|
+
private gitSha;
|
|
20
|
+
private projectRoot;
|
|
21
|
+
constructor(options?: StoryReporterOptions);
|
|
22
|
+
onBegin(config: FullConfig): void;
|
|
23
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
24
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { type StoryReporterOptions, StoryReporter as default };
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/reporter.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import {
|
|
5
|
+
ReportGenerator,
|
|
6
|
+
canonicalizeRun,
|
|
7
|
+
readGitSha,
|
|
8
|
+
readPackageVersion,
|
|
9
|
+
detectCI
|
|
10
|
+
} from "executable-stories-formatters";
|
|
11
|
+
function toRelativePosix(absolutePath, projectRoot) {
|
|
12
|
+
return path.relative(projectRoot, absolutePath).split(path.sep).join("/");
|
|
13
|
+
}
|
|
14
|
+
var StoryReporter = class {
|
|
15
|
+
options;
|
|
16
|
+
scenarios = [];
|
|
17
|
+
startTime = 0;
|
|
18
|
+
packageVersion;
|
|
19
|
+
gitSha;
|
|
20
|
+
projectRoot = process.cwd();
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.options = options;
|
|
23
|
+
}
|
|
24
|
+
onBegin(config) {
|
|
25
|
+
this.startTime = Date.now();
|
|
26
|
+
this.projectRoot = config.rootDir ?? process.cwd();
|
|
27
|
+
const includeMetadata = this.options.markdown?.includeMetadata ?? true;
|
|
28
|
+
if (includeMetadata) {
|
|
29
|
+
this.packageVersion = readPackageVersion(this.projectRoot);
|
|
30
|
+
this.gitSha = readGitSha(this.projectRoot);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
onTestEnd(test, result) {
|
|
34
|
+
const storyAnnotation = test.annotations.find((a) => a.type === "story-meta");
|
|
35
|
+
if (!storyAnnotation?.description) return;
|
|
36
|
+
try {
|
|
37
|
+
const meta = JSON.parse(storyAnnotation.description);
|
|
38
|
+
const sourceFile = test.location?.file ? toRelativePosix(test.location.file, this.projectRoot) : "unknown";
|
|
39
|
+
const sourceLine = test.location?.line ?? 1;
|
|
40
|
+
let error;
|
|
41
|
+
let errorStack;
|
|
42
|
+
if (result.status === "failed" && result.errors?.length) {
|
|
43
|
+
const err = result.errors[0];
|
|
44
|
+
error = err.message || String(err);
|
|
45
|
+
errorStack = err.stack;
|
|
46
|
+
}
|
|
47
|
+
const attachments = (result.attachments ?? []).map((a) => {
|
|
48
|
+
let body;
|
|
49
|
+
let encoding;
|
|
50
|
+
if (a.body !== void 0) {
|
|
51
|
+
if (typeof a.body === "string") {
|
|
52
|
+
body = a.body;
|
|
53
|
+
encoding = "IDENTITY";
|
|
54
|
+
} else if (Buffer.isBuffer(a.body) || a.body instanceof Uint8Array) {
|
|
55
|
+
body = Buffer.from(a.body).toString("base64");
|
|
56
|
+
encoding = "BASE64";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
name: a.name,
|
|
61
|
+
mediaType: a.contentType,
|
|
62
|
+
path: a.path,
|
|
63
|
+
body,
|
|
64
|
+
encoding
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
const stepEvents = meta.steps.filter((s) => s.durationMs !== void 0).map((s, i) => ({
|
|
68
|
+
index: i,
|
|
69
|
+
title: s.text,
|
|
70
|
+
durationMs: s.durationMs
|
|
71
|
+
}));
|
|
72
|
+
this.scenarios.push({
|
|
73
|
+
meta,
|
|
74
|
+
sourceFile,
|
|
75
|
+
sourceLine,
|
|
76
|
+
status: result.status,
|
|
77
|
+
error,
|
|
78
|
+
errorStack,
|
|
79
|
+
durationMs: result.duration,
|
|
80
|
+
projectName: test.parent?.project()?.name,
|
|
81
|
+
retry: result.retry,
|
|
82
|
+
retries: test.retries,
|
|
83
|
+
attachments: attachments.length > 0 ? attachments : void 0,
|
|
84
|
+
stepEvents: stepEvents.length > 0 ? stepEvents : void 0
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async onEnd(_result) {
|
|
90
|
+
if (this.scenarios.length === 0) return;
|
|
91
|
+
const rawTestCases = this.scenarios.map((scenario) => {
|
|
92
|
+
const statusMap = {
|
|
93
|
+
passed: "pass",
|
|
94
|
+
failed: "fail",
|
|
95
|
+
skipped: "skip",
|
|
96
|
+
timedOut: "fail",
|
|
97
|
+
interrupted: "fail"
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
title: scenario.meta.scenario,
|
|
101
|
+
titlePath: scenario.meta.suitePath ? [...scenario.meta.suitePath, scenario.meta.scenario] : [scenario.meta.scenario],
|
|
102
|
+
story: scenario.meta,
|
|
103
|
+
sourceFile: scenario.sourceFile,
|
|
104
|
+
sourceLine: Math.max(1, scenario.sourceLine),
|
|
105
|
+
status: statusMap[scenario.status] ?? "unknown",
|
|
106
|
+
durationMs: scenario.durationMs,
|
|
107
|
+
error: scenario.error ? { message: scenario.error, stack: scenario.errorStack } : void 0,
|
|
108
|
+
projectName: scenario.projectName,
|
|
109
|
+
retry: scenario.retry,
|
|
110
|
+
retries: scenario.retries,
|
|
111
|
+
attachments: scenario.attachments,
|
|
112
|
+
stepEvents: scenario.stepEvents
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
const rawRun = {
|
|
116
|
+
testCases: rawTestCases,
|
|
117
|
+
startedAtMs: this.startTime,
|
|
118
|
+
finishedAtMs: Date.now(),
|
|
119
|
+
projectRoot: this.projectRoot,
|
|
120
|
+
packageVersion: this.packageVersion,
|
|
121
|
+
gitSha: this.gitSha,
|
|
122
|
+
ci: detectCI()
|
|
123
|
+
};
|
|
124
|
+
const rawRunPath = this.options.rawRunPath;
|
|
125
|
+
if (rawRunPath) {
|
|
126
|
+
const absolutePath = path.isAbsolute(rawRunPath) ? rawRunPath : path.join(this.projectRoot, rawRunPath);
|
|
127
|
+
const dir = path.dirname(absolutePath);
|
|
128
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
129
|
+
const payload = { schemaVersion: 1, ...rawRun };
|
|
130
|
+
fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), "utf8");
|
|
131
|
+
}
|
|
132
|
+
const canonicalRun = canonicalizeRun(rawRun);
|
|
133
|
+
const generator = new ReportGenerator(this.options);
|
|
134
|
+
try {
|
|
135
|
+
await generator.generate(canonicalRun);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("Failed to generate reports:", err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
export {
|
|
142
|
+
StoryReporter as default
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Playwright reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n */\n\nimport type {\n Reporter,\n FullConfig,\n TestCase,\n TestResult,\n FullResult,\n} from \"@playwright/test/reporter\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\ninterface CollectedScenario {\n meta: StoryMeta;\n sourceFile: string;\n sourceLine: number;\n status: \"passed\" | \"failed\" | \"skipped\" | \"timedOut\" | \"interrupted\";\n error?: string;\n errorStack?: string;\n durationMs: number;\n projectName?: string;\n retry: number;\n retries: number;\n attachments?: RawAttachment[];\n stepEvents?: RawStepEvent[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private scenarios: CollectedScenario[] = [];\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private projectRoot: string = process.cwd();\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onBegin(config: FullConfig): void {\n this.startTime = Date.now();\n this.projectRoot = config.rootDir ?? process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(this.projectRoot);\n this.gitSha = readGitSha(this.projectRoot);\n }\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n // Find story-meta annotation\n const storyAnnotation = test.annotations.find((a) => a.type === \"story-meta\");\n if (!storyAnnotation?.description) return;\n\n try {\n const meta: StoryMeta = JSON.parse(storyAnnotation.description);\n\n // Get source file and line for sorting\n const sourceFile = test.location?.file\n ? toRelativePosix(test.location.file, this.projectRoot)\n : \"unknown\";\n const sourceLine = (test.location as { line?: number })?.line ?? 1;\n\n // Get error message if failed\n let error: string | undefined;\n let errorStack: string | undefined;\n if (result.status === \"failed\" && result.errors?.length) {\n const err = result.errors[0];\n error = err.message || String(err);\n errorStack = err.stack;\n }\n\n // Map Playwright result.attachments → RawAttachment[]\n const attachments: RawAttachment[] = (result.attachments ?? []).map((a) => {\n let body: string | undefined;\n let encoding: \"BASE64\" | \"IDENTITY\" | undefined;\n if (a.body !== undefined) {\n if (typeof a.body === \"string\") {\n body = a.body;\n encoding = \"IDENTITY\";\n } else if (Buffer.isBuffer(a.body) || (a.body as unknown) instanceof Uint8Array) {\n body = Buffer.from(a.body as Buffer | Uint8Array).toString(\"base64\");\n encoding = \"BASE64\";\n }\n }\n return {\n name: a.name,\n mediaType: a.contentType,\n path: a.path,\n body,\n encoding,\n };\n });\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n this.scenarios.push({\n meta,\n sourceFile,\n sourceLine,\n status: result.status,\n error,\n errorStack,\n durationMs: result.duration,\n projectName: test.parent?.project()?.name,\n retry: result.retry,\n retries: test.retries,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n });\n } catch {\n // Ignore parse errors\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n if (this.scenarios.length === 0) return;\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = this.scenarios.map((scenario) => {\n // Map Playwright status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n timedOut: \"fail\",\n interrupted: \"fail\",\n };\n\n return {\n title: scenario.meta.scenario,\n titlePath: scenario.meta.suitePath\n ? [...scenario.meta.suitePath, scenario.meta.scenario]\n : [scenario.meta.scenario],\n story: scenario.meta,\n sourceFile: scenario.sourceFile,\n sourceLine: Math.max(1, scenario.sourceLine),\n status: statusMap[scenario.status] ?? \"unknown\",\n durationMs: scenario.durationMs,\n error: scenario.error\n ? { message: scenario.error, stack: scenario.errorStack }\n : undefined,\n projectName: scenario.projectName,\n retry: scenario.retry,\n retries: scenario.retries,\n attachments: scenario.attachments,\n stepEvents: scenario.stepEvents,\n };\n });\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: this.projectRoot,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(this.projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":";AAYA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAItB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AA8CP,SAAS,gBAAgB,cAAsB,aAA6B;AAC1E,SAAY,cAAS,aAAa,YAAY,EAAE,MAAW,QAAG,EAAE,KAAK,GAAG;AAC1E;AAMA,IAAqB,gBAArB,MAAuD;AAAA,EAC7C;AAAA,EACA,YAAiC,CAAC;AAAA,EAClC,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,cAAsB,QAAQ,IAAI;AAAA,EAE1C,YAAY,UAAgC,CAAC,GAAG;AAC9C,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,QAAQ,QAA0B;AAChC,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,cAAc,OAAO,WAAW,QAAQ,IAAI;AACjD,UAAM,kBAAkB,KAAK,QAAQ,UAAU,mBAAmB;AAClE,QAAI,iBAAiB;AACnB,WAAK,iBAAiB,mBAAmB,KAAK,WAAW;AACzD,WAAK,SAAS,WAAW,KAAK,WAAW;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,UAAU,MAAgB,QAA0B;AAElD,UAAM,kBAAkB,KAAK,YAAY,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AAC5E,QAAI,CAAC,iBAAiB,YAAa;AAEnC,QAAI;AACF,YAAM,OAAkB,KAAK,MAAM,gBAAgB,WAAW;AAG9D,YAAM,aAAa,KAAK,UAAU,OAC9B,gBAAgB,KAAK,SAAS,MAAM,KAAK,WAAW,IACpD;AACJ,YAAM,aAAc,KAAK,UAAgC,QAAQ;AAGjE,UAAI;AACJ,UAAI;AACJ,UAAI,OAAO,WAAW,YAAY,OAAO,QAAQ,QAAQ;AACvD,cAAM,MAAM,OAAO,OAAO,CAAC;AAC3B,gBAAQ,IAAI,WAAW,OAAO,GAAG;AACjC,qBAAa,IAAI;AAAA,MACnB;AAGA,YAAM,eAAgC,OAAO,eAAe,CAAC,GAAG,IAAI,CAAC,MAAM;AACzE,YAAI;AACJ,YAAI;AACJ,YAAI,EAAE,SAAS,QAAW;AACxB,cAAI,OAAO,EAAE,SAAS,UAAU;AAC9B,mBAAO,EAAE;AACT,uBAAW;AAAA,UACb,WAAW,OAAO,SAAS,EAAE,IAAI,KAAM,EAAE,gBAA4B,YAAY;AAC/E,mBAAO,OAAO,KAAK,EAAE,IAA2B,EAAE,SAAS,QAAQ;AACnE,uBAAW;AAAA,UACb;AAAA,QACF;AACA,eAAO;AAAA,UACL,MAAM,EAAE;AAAA,UACR,WAAW,EAAE;AAAA,UACb,MAAM,EAAE;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAGD,YAAM,aAA6B,KAAK,MACrC,OAAO,CAAC,MAA+B,EAAE,eAAe,MAAS,EACjE,IAAI,CAAC,GAA0C,OAAe;AAAA,QAC7D,OAAO;AAAA,QACP,OAAO,EAAE;AAAA,QACT,YAAY,EAAE;AAAA,MAChB,EAAE;AAEJ,WAAK,UAAU,KAAK;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,OAAO;AAAA,QACf;AAAA,QACA;AAAA,QACA,YAAY,OAAO;AAAA,QACnB,aAAa,KAAK,QAAQ,QAAQ,GAAG;AAAA,QACrC,OAAO,OAAO;AAAA,QACd,SAAS,KAAK;AAAA,QACd,aAAa,YAAY,SAAS,IAAI,cAAc;AAAA,QACpD,YAAY,WAAW,SAAS,IAAI,aAAa;AAAA,MACnD,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,SAAoC;AAC9C,QAAI,KAAK,UAAU,WAAW,EAAG;AAGjC,UAAM,eAA8B,KAAK,UAAU,IAAI,CAAC,aAAa;AAEnE,YAAM,YAAmD;AAAA,QACvD,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAEA,aAAO;AAAA,QACL,OAAO,SAAS,KAAK;AAAA,QACrB,WAAW,SAAS,KAAK,YACrB,CAAC,GAAG,SAAS,KAAK,WAAW,SAAS,KAAK,QAAQ,IACnD,CAAC,SAAS,KAAK,QAAQ;AAAA,QAC3B,OAAO,SAAS;AAAA,QAChB,YAAY,SAAS;AAAA,QACrB,YAAY,KAAK,IAAI,GAAG,SAAS,UAAU;AAAA,QAC3C,QAAQ,UAAU,SAAS,MAAM,KAAK;AAAA,QACtC,YAAY,SAAS;AAAA,QACrB,OAAO,SAAS,QACZ,EAAE,SAAS,SAAS,OAAO,OAAO,SAAS,WAAW,IACtD;AAAA,QACJ,aAAa,SAAS;AAAA,QACtB,OAAO,SAAS;AAAA,QAChB,SAAS,SAAS;AAAA,QAClB,aAAa,SAAS;AAAA,QACtB,YAAY,SAAS;AAAA,MACvB;AAAA,IACF,CAAC;AAGD,UAAM,SAAiB;AAAA,MACrB,WAAW;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,cAAc,KAAK,IAAI;AAAA,MACvB,aAAa,KAAK;AAAA,MAClB,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,IAAI,SAAS;AAAA,IACf;AAGA,UAAM,aAAa,KAAK,QAAQ;AAChC,QAAI,YAAY;AACd,YAAM,eAAoB,gBAAW,UAAU,IAC3C,aACK,UAAK,KAAK,aAAa,UAAU;AAC1C,YAAM,MAAW,aAAQ,YAAY;AACrC,UAAI,CAAI,cAAW,GAAG,EAAG,CAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC9D,YAAM,UAAU,EAAE,eAAe,GAAG,GAAG,OAAO;AAC9C,MAAG,iBAAc,cAAc,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,MAAM;AAAA,IACzE;AAGA,UAAM,eAAe,gBAAgB,MAAM;AAG3C,UAAM,YAAY,IAAI,gBAAgB,KAAK,OAAO;AAClD,QAAI;AACF,YAAM,UAAU,SAAS,YAAY;AAAA,IACvC,SAAS,KAAK;AACZ,cAAQ,MAAM,+BAA+B,GAAG;AAAA,IAClD;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "executable-stories-playwright",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "BDD-style executable stories for Playwright Test with documentation generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./reporter": {
|
|
16
|
+
"types": "./dist/reporter.d.ts",
|
|
17
|
+
"import": "./dist/reporter.js",
|
|
18
|
+
"require": "./dist/reporter.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"test": "playwright test --pass-with-no-tests"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@playwright/test": ">=1.58.1"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"executable-stories-formatters": "workspace:*"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@playwright/test": "^1.58.1",
|
|
37
|
+
"@types/node": "^25.2.1",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "~5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"playwright",
|
|
43
|
+
"bdd",
|
|
44
|
+
"testing",
|
|
45
|
+
"documentation",
|
|
46
|
+
"executable-stories"
|
|
47
|
+
]
|
|
48
|
+
}
|