executable-stories-cypress 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/README.md +79 -0
- package/dist/index.cjs +334 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +173 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.js +306 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.cjs +61 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +33 -0
- package/dist/plugin.d.ts +33 -0
- package/dist/plugin.js +34 -0
- package/dist/plugin.js.map +1 -0
- package/dist/reporter.cjs +220 -0
- package/dist/reporter.cjs.map +1 -0
- package/dist/reporter.d.cts +84 -0
- package/dist/reporter.d.ts +84 -0
- package/dist/reporter.js +190 -0
- package/dist/reporter.js.map +1 -0
- package/dist/support.cjs +313 -0
- package/dist/support.cjs.map +1 -0
- package/dist/support.d.cts +2 -0
- package/dist/support.d.ts +2 -0
- package/dist/support.js +311 -0
- package/dist/support.js.map +1 -0
- package/package.json +61 -0
- package/reporter.cjs +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# executable-stories-cypress
|
|
2
|
+
|
|
3
|
+
BDD-style executable stories for Cypress with documentation generation. Uses Cypress’s native `describe`/`it`; story meta is sent from the browser to Node via `cy.task` and merged with run results for the reporter.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -D executable-stories-cypress
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. **Plugin** — register the task in `cypress.config.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineConfig } from "cypress";
|
|
17
|
+
import { registerExecutableStoriesPlugin } from "executable-stories-cypress/plugin";
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
e2e: {
|
|
21
|
+
setupNodeEvents(on) {
|
|
22
|
+
registerExecutableStoriesPlugin(on);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. **Support file** — import the support file so story meta is sent after each test (e.g. in `cypress/support/e2e.ts`):
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import "executable-stories-cypress/support";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. **Reporter** (optional) — to generate Markdown/HTML from runs, use the reporter or build a RawRun and pass it to the formatters. See [Reporter options](#reporter-options) below.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
In a spec, call `story.init()` at the start of each test that should be documented, then use step markers:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { story } from "executable-stories-cypress";
|
|
42
|
+
|
|
43
|
+
describe("Calculator", () => {
|
|
44
|
+
it("adds two numbers", () => {
|
|
45
|
+
story.init();
|
|
46
|
+
|
|
47
|
+
story.given("two numbers 5 and 3");
|
|
48
|
+
const a = 5, b = 3;
|
|
49
|
+
|
|
50
|
+
story.when("I add them together");
|
|
51
|
+
const result = a + b;
|
|
52
|
+
|
|
53
|
+
story.then("the result is 8");
|
|
54
|
+
expect(result).toBe(8);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With options:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
story.init({ tags: ["smoke"], ticket: "JIRA-123" });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Reporter options
|
|
66
|
+
|
|
67
|
+
The package outputs to the **executable-stories-formatters** schema (RawRun). You can:
|
|
68
|
+
|
|
69
|
+
- Use the Mocha reporter (when Cypress invokes it) with `--reporter executable-stories-cypress/reporter` and `--reporter-options outputDir=...,outputName=...`.
|
|
70
|
+
- Or use the Module API: after `cypress.run()`, call `buildRawRunFromCypressResult(result, options)` then `generateReportsFromRawRun(rawRun, options)` (see exports from `executable-stories-cypress/reporter`).
|
|
71
|
+
|
|
72
|
+
Options match the formatters’ `FormatterOptions` (e.g. `formats`, `outputDir`, `outputName`, `markdown`, `rawRunPath`).
|
|
73
|
+
|
|
74
|
+
## Exports
|
|
75
|
+
|
|
76
|
+
- **Main:** `story`, `getAndClearMeta`, types from `executable-stories-cypress`.
|
|
77
|
+
- **Support:** `executable-stories-cypress/support` (side-effect: registers `afterEach` + `cy.task`).
|
|
78
|
+
- **Plugin:** `registerExecutableStoriesPlugin` from `executable-stories-cypress/plugin`.
|
|
79
|
+
- **Reporter:** default reporter and `buildRawRunFromCypressResult`, `generateReportsFromRawRun` from `executable-stories-cypress/reporter`.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
getAndClearMeta: () => getAndClearMeta,
|
|
24
|
+
story: () => story
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
|
|
28
|
+
// src/story-api.ts
|
|
29
|
+
var activeContext = null;
|
|
30
|
+
var sourceOrderCounter = 0;
|
|
31
|
+
function getContext() {
|
|
32
|
+
if (!activeContext) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"story.init() must be called first. Use: it('name', () => { story.init(); ... });"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return activeContext;
|
|
38
|
+
}
|
|
39
|
+
function normalizeTickets(ticket) {
|
|
40
|
+
if (!ticket) return void 0;
|
|
41
|
+
return Array.isArray(ticket) ? ticket : [ticket];
|
|
42
|
+
}
|
|
43
|
+
function convertStoryDocsToEntries(docs) {
|
|
44
|
+
const entries = [];
|
|
45
|
+
if (docs.note) {
|
|
46
|
+
entries.push({ kind: "note", text: docs.note, phase: "runtime" });
|
|
47
|
+
}
|
|
48
|
+
if (docs.tag) {
|
|
49
|
+
const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];
|
|
50
|
+
entries.push({ kind: "tag", names, phase: "runtime" });
|
|
51
|
+
}
|
|
52
|
+
if (docs.kv) {
|
|
53
|
+
for (const [label, value] of Object.entries(docs.kv)) {
|
|
54
|
+
entries.push({ kind: "kv", label, value, phase: "runtime" });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (docs.code) {
|
|
58
|
+
entries.push({
|
|
59
|
+
kind: "code",
|
|
60
|
+
label: docs.code.label,
|
|
61
|
+
content: docs.code.content,
|
|
62
|
+
lang: docs.code.lang,
|
|
63
|
+
phase: "runtime"
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (docs.json) {
|
|
67
|
+
entries.push({
|
|
68
|
+
kind: "code",
|
|
69
|
+
label: docs.json.label,
|
|
70
|
+
content: JSON.stringify(docs.json.value, null, 2),
|
|
71
|
+
lang: "json",
|
|
72
|
+
phase: "runtime"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (docs.table) {
|
|
76
|
+
entries.push({
|
|
77
|
+
kind: "table",
|
|
78
|
+
label: docs.table.label,
|
|
79
|
+
columns: docs.table.columns,
|
|
80
|
+
rows: docs.table.rows,
|
|
81
|
+
phase: "runtime"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (docs.link) {
|
|
85
|
+
entries.push({
|
|
86
|
+
kind: "link",
|
|
87
|
+
label: docs.link.label,
|
|
88
|
+
url: docs.link.url,
|
|
89
|
+
phase: "runtime"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (docs.section) {
|
|
93
|
+
entries.push({
|
|
94
|
+
kind: "section",
|
|
95
|
+
title: docs.section.title,
|
|
96
|
+
markdown: docs.section.markdown,
|
|
97
|
+
phase: "runtime"
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (docs.mermaid) {
|
|
101
|
+
entries.push({
|
|
102
|
+
kind: "mermaid",
|
|
103
|
+
code: docs.mermaid.code,
|
|
104
|
+
title: docs.mermaid.title,
|
|
105
|
+
phase: "runtime"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (docs.screenshot) {
|
|
109
|
+
entries.push({
|
|
110
|
+
kind: "screenshot",
|
|
111
|
+
path: docs.screenshot.path,
|
|
112
|
+
alt: docs.screenshot.alt,
|
|
113
|
+
phase: "runtime"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (docs.custom) {
|
|
117
|
+
entries.push({
|
|
118
|
+
kind: "custom",
|
|
119
|
+
type: docs.custom.type,
|
|
120
|
+
data: docs.custom.data,
|
|
121
|
+
phase: "runtime"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return entries;
|
|
125
|
+
}
|
|
126
|
+
function attachDoc(entry) {
|
|
127
|
+
const ctx = getContext();
|
|
128
|
+
if (ctx.currentStep) {
|
|
129
|
+
ctx.currentStep.docs ??= [];
|
|
130
|
+
ctx.currentStep.docs.push(entry);
|
|
131
|
+
} else {
|
|
132
|
+
ctx.meta.docs ??= [];
|
|
133
|
+
ctx.meta.docs.push(entry);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function extractSuitePath(titlePath) {
|
|
137
|
+
if (titlePath.length <= 1) return void 0;
|
|
138
|
+
const suitePath = titlePath.slice(0, -1);
|
|
139
|
+
return suitePath.length > 0 ? suitePath : void 0;
|
|
140
|
+
}
|
|
141
|
+
function createStepMarker(keyword) {
|
|
142
|
+
return function stepMarker(text, docs) {
|
|
143
|
+
const ctx = getContext();
|
|
144
|
+
const step = {
|
|
145
|
+
id: `step-${ctx.stepCounter++}`,
|
|
146
|
+
keyword,
|
|
147
|
+
text,
|
|
148
|
+
docs: docs ? convertStoryDocsToEntries(docs) : []
|
|
149
|
+
};
|
|
150
|
+
ctx.meta.steps.push(step);
|
|
151
|
+
ctx.currentStep = step;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function init(options) {
|
|
155
|
+
const currentTest = Cypress.currentTest;
|
|
156
|
+
const spec = Cypress.spec;
|
|
157
|
+
if (!currentTest) {
|
|
158
|
+
throw new Error("story.init() must be called inside an it() block so Cypress.currentTest is available.");
|
|
159
|
+
}
|
|
160
|
+
const titlePath = currentTest.titlePath ?? [currentTest.title];
|
|
161
|
+
const scenario = currentTest.title;
|
|
162
|
+
const suitePath = extractSuitePath(titlePath);
|
|
163
|
+
const specRelative = spec?.relative ?? "unknown";
|
|
164
|
+
const meta = {
|
|
165
|
+
scenario,
|
|
166
|
+
steps: [],
|
|
167
|
+
suitePath,
|
|
168
|
+
tags: options?.tags,
|
|
169
|
+
tickets: normalizeTickets(options?.ticket),
|
|
170
|
+
meta: options?.meta,
|
|
171
|
+
sourceOrder: sourceOrderCounter++
|
|
172
|
+
};
|
|
173
|
+
activeContext = {
|
|
174
|
+
meta,
|
|
175
|
+
currentStep: null,
|
|
176
|
+
stepCounter: 0,
|
|
177
|
+
attachments: [],
|
|
178
|
+
activeTimers: /* @__PURE__ */ new Map(),
|
|
179
|
+
timerCounter: 0,
|
|
180
|
+
specRelative,
|
|
181
|
+
titlePath
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function getAndClearMeta() {
|
|
185
|
+
if (!activeContext) return null;
|
|
186
|
+
const payload = {
|
|
187
|
+
specRelative: activeContext.specRelative,
|
|
188
|
+
titlePath: activeContext.titlePath,
|
|
189
|
+
meta: activeContext.meta,
|
|
190
|
+
attachments: activeContext.attachments.length > 0 ? activeContext.attachments : void 0
|
|
191
|
+
};
|
|
192
|
+
activeContext = null;
|
|
193
|
+
return payload;
|
|
194
|
+
}
|
|
195
|
+
function fn(keyword, text, body) {
|
|
196
|
+
const ctx = getContext();
|
|
197
|
+
const step = {
|
|
198
|
+
id: `step-${ctx.stepCounter++}`,
|
|
199
|
+
keyword,
|
|
200
|
+
text,
|
|
201
|
+
docs: [],
|
|
202
|
+
wrapped: true
|
|
203
|
+
};
|
|
204
|
+
ctx.meta.steps.push(step);
|
|
205
|
+
ctx.currentStep = step;
|
|
206
|
+
const start = performance.now();
|
|
207
|
+
try {
|
|
208
|
+
const result = body();
|
|
209
|
+
if (result instanceof Promise) {
|
|
210
|
+
return result.then(
|
|
211
|
+
(val) => {
|
|
212
|
+
step.durationMs = performance.now() - start;
|
|
213
|
+
return val;
|
|
214
|
+
},
|
|
215
|
+
(err) => {
|
|
216
|
+
step.durationMs = performance.now() - start;
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
step.durationMs = performance.now() - start;
|
|
222
|
+
return result;
|
|
223
|
+
} catch (err) {
|
|
224
|
+
step.durationMs = performance.now() - start;
|
|
225
|
+
throw err;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function storyExpect(text, body) {
|
|
229
|
+
return fn("Then", text, body);
|
|
230
|
+
}
|
|
231
|
+
var story = {
|
|
232
|
+
init,
|
|
233
|
+
// BDD step markers
|
|
234
|
+
given: createStepMarker("Given"),
|
|
235
|
+
when: createStepMarker("When"),
|
|
236
|
+
then: createStepMarker("Then"),
|
|
237
|
+
and: createStepMarker("And"),
|
|
238
|
+
but: createStepMarker("But"),
|
|
239
|
+
// AAA pattern aliases
|
|
240
|
+
arrange: createStepMarker("Given"),
|
|
241
|
+
act: createStepMarker("When"),
|
|
242
|
+
assert: createStepMarker("Then"),
|
|
243
|
+
// Additional aliases
|
|
244
|
+
setup: createStepMarker("Given"),
|
|
245
|
+
context: createStepMarker("Given"),
|
|
246
|
+
execute: createStepMarker("When"),
|
|
247
|
+
action: createStepMarker("When"),
|
|
248
|
+
verify: createStepMarker("Then"),
|
|
249
|
+
// Standalone doc methods
|
|
250
|
+
note(text) {
|
|
251
|
+
attachDoc({ kind: "note", text, phase: "runtime" });
|
|
252
|
+
},
|
|
253
|
+
tag(name) {
|
|
254
|
+
const names = Array.isArray(name) ? name : [name];
|
|
255
|
+
attachDoc({ kind: "tag", names, phase: "runtime" });
|
|
256
|
+
},
|
|
257
|
+
kv(options) {
|
|
258
|
+
attachDoc({ kind: "kv", label: options.label, value: options.value, phase: "runtime" });
|
|
259
|
+
},
|
|
260
|
+
json(options) {
|
|
261
|
+
const content = JSON.stringify(options.value, null, 2);
|
|
262
|
+
attachDoc({ kind: "code", label: options.label, content, lang: "json", phase: "runtime" });
|
|
263
|
+
},
|
|
264
|
+
code(options) {
|
|
265
|
+
attachDoc({ kind: "code", label: options.label, content: options.content, lang: options.lang, phase: "runtime" });
|
|
266
|
+
},
|
|
267
|
+
table(options) {
|
|
268
|
+
attachDoc({ kind: "table", label: options.label, columns: options.columns, rows: options.rows, phase: "runtime" });
|
|
269
|
+
},
|
|
270
|
+
link(options) {
|
|
271
|
+
attachDoc({ kind: "link", label: options.label, url: options.url, phase: "runtime" });
|
|
272
|
+
},
|
|
273
|
+
section(options) {
|
|
274
|
+
attachDoc({ kind: "section", title: options.title, markdown: options.markdown, phase: "runtime" });
|
|
275
|
+
},
|
|
276
|
+
mermaid(options) {
|
|
277
|
+
attachDoc({ kind: "mermaid", code: options.code, title: options.title, phase: "runtime" });
|
|
278
|
+
},
|
|
279
|
+
screenshot(options) {
|
|
280
|
+
attachDoc({ kind: "screenshot", path: options.path, alt: options.alt, phase: "runtime" });
|
|
281
|
+
},
|
|
282
|
+
custom(options) {
|
|
283
|
+
attachDoc({ kind: "custom", type: options.type, data: options.data, phase: "runtime" });
|
|
284
|
+
},
|
|
285
|
+
// Attachments
|
|
286
|
+
attach(options) {
|
|
287
|
+
const ctx = getContext();
|
|
288
|
+
const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
|
|
289
|
+
ctx.attachments.push({
|
|
290
|
+
...options,
|
|
291
|
+
stepIndex: stepIndex !== void 0 && stepIndex >= 0 ? stepIndex : void 0,
|
|
292
|
+
stepId: ctx.currentStep?.id
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
// Step timing
|
|
296
|
+
startTimer() {
|
|
297
|
+
const ctx = getContext();
|
|
298
|
+
const token = ctx.timerCounter++;
|
|
299
|
+
const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
|
|
300
|
+
ctx.activeTimers.set(token, {
|
|
301
|
+
start: performance.now(),
|
|
302
|
+
stepIndex: stepIndex !== void 0 && stepIndex >= 0 ? stepIndex : void 0,
|
|
303
|
+
stepId: ctx.currentStep?.id,
|
|
304
|
+
consumed: false
|
|
305
|
+
});
|
|
306
|
+
return token;
|
|
307
|
+
},
|
|
308
|
+
endTimer(token) {
|
|
309
|
+
const ctx = getContext();
|
|
310
|
+
const entry = ctx.activeTimers.get(token);
|
|
311
|
+
if (!entry || entry.consumed) return;
|
|
312
|
+
entry.consumed = true;
|
|
313
|
+
const durationMs = performance.now() - entry.start;
|
|
314
|
+
let step;
|
|
315
|
+
if (entry.stepId) {
|
|
316
|
+
step = ctx.meta.steps.find((s) => s.id === entry.stepId);
|
|
317
|
+
}
|
|
318
|
+
if (!step && entry.stepIndex !== void 0) {
|
|
319
|
+
step = ctx.meta.steps[entry.stepIndex];
|
|
320
|
+
}
|
|
321
|
+
if (step) {
|
|
322
|
+
step.durationMs = durationMs;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
// Step wrappers
|
|
326
|
+
fn,
|
|
327
|
+
expect: storyExpect
|
|
328
|
+
};
|
|
329
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
330
|
+
0 && (module.exports = {
|
|
331
|
+
getAndClearMeta,
|
|
332
|
+
story
|
|
333
|
+
});
|
|
334
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/story-api.ts"],"sourcesContent":["/**\n * Cypress Executable Stories\n *\n * BDD-style executable documentation for Cypress.\n *\n * @example\n * ```ts\n * import { story } from 'executable-stories-cypress';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', () => {\n * story.init();\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, 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\n// Story API\nexport { story, getAndClearMeta } from './story-api';\nexport type { Story, RecordMetaPayload } from './story-api';\n\n// Re-export types from local types module\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from './types';\n","/**\n * Cypress story.* API for executable-stories.\n *\n * Uses native Cypress describe/it with opt-in documentation.\n * Story meta is flushed to Node via cy.task from the support file.\n *\n * @example\n * ```ts\n * import { story } from 'executable-stories-cypress';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', () => {\n * story.init();\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, 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 {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n ScopedAttachment,\n RecordMetaPayload,\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\nexport type { RecordMetaPayload } 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 specRelative: string;\n titlePath: string[];\n}\n\n// ============================================================================\n// Cypress-specific context\n// ============================================================================\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | 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() must be called first. Use: it('name', () => { story.init(); ... });\"\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 * Extract suite path from Cypress.currentTest.titlePath (describe blocks only).\n * titlePath is [describe1, describe2, ..., testTitle] — we want everything except the last.\n */\nfunction extractSuitePath(titlePath: string[]): string[] | undefined {\n if (titlePath.length <= 1) return undefined;\n const suitePath = titlePath.slice(0, -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 };\n}\n\n// ============================================================================\n// story.init() - Cypress-specific\n// ============================================================================\n\nfunction init(options?: StoryOptions): void {\n const currentTest = Cypress.currentTest;\n const spec = Cypress.spec;\n if (!currentTest) {\n throw new Error(\"story.init() must be called inside an it() block so Cypress.currentTest is available.\");\n }\n\n const titlePath = currentTest.titlePath ?? [currentTest.title];\n const scenario = currentTest.title;\n const suitePath = extractSuitePath(titlePath);\n const specRelative = spec?.relative ?? \"unknown\";\n\n const meta: StoryMeta = {\n scenario,\n steps: [],\n suitePath,\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n specRelative,\n titlePath,\n };\n}\n\n/**\n * Get the current story meta and clear the active context.\n * Called by the support file after each test to send meta to Node via cy.task.\n * Returns null if story.init() was never called for this test.\n */\nexport function getAndClearMeta(): RecordMetaPayload | null {\n if (!activeContext) return null;\n const payload: RecordMetaPayload = {\n specRelative: activeContext.specRelative,\n titlePath: activeContext.titlePath,\n meta: activeContext.meta,\n attachments: activeContext.attachments.length > 0 ? activeContext.attachments : undefined,\n };\n activeContext = null;\n return payload;\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\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 return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n throw err;\n },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\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// 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 },\n\n tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\n },\n\n kv(options: KvOptions): void {\n attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' });\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 },\n\n code(options: CodeOptions): void {\n attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' });\n },\n\n table(options: TableOptions): void {\n attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' });\n },\n\n link(options: LinkOptions): void {\n attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' });\n },\n\n section(options: SectionOptions): void {\n attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' });\n },\n\n mermaid(options: MermaidOptions): void {\n attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' });\n },\n\n screenshot(options: ScreenshotOptions): void {\n attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' });\n },\n\n custom(options: CustomOptions): void {\n attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' });\n },\n\n // Attachments\n attach(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 stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n });\n },\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 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 },\n\n // Step wrappers\n fn,\n expect: storyExpect,\n};\n\nexport type Story = typeof story;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { StoryMeta as StoryMeta$1, StepKeyword } from 'executable-stories-formatters';
|
|
2
|
+
export { DocEntry, StepKeyword, StoryMeta, StoryStep } from 'executable-stories-formatters';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared types between browser (story-api) and Node (store, reporter).
|
|
6
|
+
* No Cypress or Node-only imports so both environments can use them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type StoryMeta = StoryMeta$1;
|
|
10
|
+
/** Scoped attachment stored in context. */
|
|
11
|
+
interface ScopedAttachment {
|
|
12
|
+
name: string;
|
|
13
|
+
mediaType: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
body?: string | Buffer;
|
|
16
|
+
encoding?: 'BASE64' | 'IDENTITY';
|
|
17
|
+
charset?: string;
|
|
18
|
+
fileName?: string;
|
|
19
|
+
stepIndex?: number;
|
|
20
|
+
stepId?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Payload sent from browser to Node via cy.task for the reporter to merge with run results */
|
|
23
|
+
interface RecordMetaPayload {
|
|
24
|
+
specRelative: string;
|
|
25
|
+
titlePath: string[];
|
|
26
|
+
meta: StoryMeta;
|
|
27
|
+
attachments?: ScopedAttachment[];
|
|
28
|
+
}
|
|
29
|
+
/** All inline doc options that can be passed to step markers. */
|
|
30
|
+
interface StoryDocs {
|
|
31
|
+
note?: string;
|
|
32
|
+
tag?: string | string[];
|
|
33
|
+
kv?: Record<string, unknown>;
|
|
34
|
+
json?: JsonOptions;
|
|
35
|
+
table?: TableOptions;
|
|
36
|
+
link?: LinkOptions;
|
|
37
|
+
code?: CodeOptions;
|
|
38
|
+
section?: SectionOptions;
|
|
39
|
+
mermaid?: MermaidOptions;
|
|
40
|
+
screenshot?: ScreenshotOptions;
|
|
41
|
+
custom?: CustomOptions;
|
|
42
|
+
}
|
|
43
|
+
/** Options for story.init(). */
|
|
44
|
+
interface StoryOptions {
|
|
45
|
+
tags?: string[];
|
|
46
|
+
ticket?: string | string[];
|
|
47
|
+
meta?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/** Options for story.attach(). */
|
|
50
|
+
interface AttachmentOptions {
|
|
51
|
+
name: string;
|
|
52
|
+
mediaType: string;
|
|
53
|
+
path?: string;
|
|
54
|
+
body?: string | Buffer;
|
|
55
|
+
}
|
|
56
|
+
interface KvOptions {
|
|
57
|
+
label: string;
|
|
58
|
+
value: unknown;
|
|
59
|
+
}
|
|
60
|
+
interface JsonOptions {
|
|
61
|
+
label: string;
|
|
62
|
+
value: unknown;
|
|
63
|
+
}
|
|
64
|
+
interface CodeOptions {
|
|
65
|
+
label: string;
|
|
66
|
+
content: string;
|
|
67
|
+
lang?: string;
|
|
68
|
+
}
|
|
69
|
+
interface TableOptions {
|
|
70
|
+
label: string;
|
|
71
|
+
columns: string[];
|
|
72
|
+
rows: string[][];
|
|
73
|
+
}
|
|
74
|
+
interface LinkOptions {
|
|
75
|
+
label: string;
|
|
76
|
+
url: string;
|
|
77
|
+
}
|
|
78
|
+
interface SectionOptions {
|
|
79
|
+
title: string;
|
|
80
|
+
markdown: string;
|
|
81
|
+
}
|
|
82
|
+
interface MermaidOptions {
|
|
83
|
+
code: string;
|
|
84
|
+
title?: string;
|
|
85
|
+
}
|
|
86
|
+
interface ScreenshotOptions {
|
|
87
|
+
path: string;
|
|
88
|
+
alt?: string;
|
|
89
|
+
}
|
|
90
|
+
interface CustomOptions {
|
|
91
|
+
type: string;
|
|
92
|
+
data: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Cypress story.* API for executable-stories.
|
|
97
|
+
*
|
|
98
|
+
* Uses native Cypress describe/it with opt-in documentation.
|
|
99
|
+
* Story meta is flushed to Node via cy.task from the support file.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { story } from 'executable-stories-cypress';
|
|
104
|
+
*
|
|
105
|
+
* describe('Calculator', () => {
|
|
106
|
+
* it('adds two numbers', () => {
|
|
107
|
+
* story.init();
|
|
108
|
+
*
|
|
109
|
+
* story.given('two numbers 5 and 3');
|
|
110
|
+
* const a = 5, b = 3;
|
|
111
|
+
*
|
|
112
|
+
* story.when('I add them together');
|
|
113
|
+
* const result = a + b;
|
|
114
|
+
*
|
|
115
|
+
* story.then('the result is 8');
|
|
116
|
+
* expect(result).toBe(8);
|
|
117
|
+
* });
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
|
|
122
|
+
declare function init(options?: StoryOptions): void;
|
|
123
|
+
/**
|
|
124
|
+
* Get the current story meta and clear the active context.
|
|
125
|
+
* Called by the support file after each test to send meta to Node via cy.task.
|
|
126
|
+
* Returns null if story.init() was never called for this test.
|
|
127
|
+
*/
|
|
128
|
+
declare function getAndClearMeta(): RecordMetaPayload | null;
|
|
129
|
+
/**
|
|
130
|
+
* Wrap a function as a step with timing and error capture.
|
|
131
|
+
* Records the step with `wrapped: true` and `durationMs`.
|
|
132
|
+
*/
|
|
133
|
+
declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
|
|
134
|
+
/**
|
|
135
|
+
* Wrap an assertion as a Then step with timing and error capture.
|
|
136
|
+
* Shorthand for `story.fn('Then', text, body)`.
|
|
137
|
+
*/
|
|
138
|
+
declare function storyExpect<T>(text: string, body: () => T): T;
|
|
139
|
+
declare const story: {
|
|
140
|
+
init: typeof init;
|
|
141
|
+
given: (text: string, docs?: StoryDocs) => void;
|
|
142
|
+
when: (text: string, docs?: StoryDocs) => void;
|
|
143
|
+
then: (text: string, docs?: StoryDocs) => void;
|
|
144
|
+
and: (text: string, docs?: StoryDocs) => void;
|
|
145
|
+
but: (text: string, docs?: StoryDocs) => void;
|
|
146
|
+
arrange: (text: string, docs?: StoryDocs) => void;
|
|
147
|
+
act: (text: string, docs?: StoryDocs) => void;
|
|
148
|
+
assert: (text: string, docs?: StoryDocs) => void;
|
|
149
|
+
setup: (text: string, docs?: StoryDocs) => void;
|
|
150
|
+
context: (text: string, docs?: StoryDocs) => void;
|
|
151
|
+
execute: (text: string, docs?: StoryDocs) => void;
|
|
152
|
+
action: (text: string, docs?: StoryDocs) => void;
|
|
153
|
+
verify: (text: string, docs?: StoryDocs) => void;
|
|
154
|
+
note(text: string): void;
|
|
155
|
+
tag(name: string | string[]): void;
|
|
156
|
+
kv(options: KvOptions): void;
|
|
157
|
+
json(options: JsonOptions): void;
|
|
158
|
+
code(options: CodeOptions): void;
|
|
159
|
+
table(options: TableOptions): void;
|
|
160
|
+
link(options: LinkOptions): void;
|
|
161
|
+
section(options: SectionOptions): void;
|
|
162
|
+
mermaid(options: MermaidOptions): void;
|
|
163
|
+
screenshot(options: ScreenshotOptions): void;
|
|
164
|
+
custom(options: CustomOptions): void;
|
|
165
|
+
attach(options: AttachmentOptions): void;
|
|
166
|
+
startTimer(): number;
|
|
167
|
+
endTimer(token: number): void;
|
|
168
|
+
fn: typeof fn;
|
|
169
|
+
expect: typeof storyExpect;
|
|
170
|
+
};
|
|
171
|
+
type Story = typeof story;
|
|
172
|
+
|
|
173
|
+
export { type CodeOptions, type CustomOptions, type JsonOptions, type KvOptions, type LinkOptions, type MermaidOptions, type RecordMetaPayload, type ScreenshotOptions, type SectionOptions, type Story, type StoryDocs, type StoryOptions, type TableOptions, getAndClearMeta, story };
|