executable-stories-cypress 8.6.3 → 8.8.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 +10 -1
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -1
- package/dist/reporter.cjs +11 -2
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.cts +14 -0
- package/dist/reporter.d.ts +14 -0
- package/dist/reporter.js +11 -2
- package/dist/reporter.js.map +1 -1
- package/dist/support.cjs +14 -2
- package/dist/support.cjs.map +1 -1
- package/dist/support.js +14 -2
- package/dist/support.js.map +1 -1
- package/package.json +3 -3
package/dist/plugin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress Node plugin: registers the task that receives story meta from the browser.\n * Use in cypress.config.ts (or plugins file) so the reporter can merge meta with run results.\n *\n * @example\n * // cypress.config.ts\n * import { defineConfig } from 'cypress';\n * import { registerExecutableStoriesPlugin } from 'executable-stories-cypress/plugin';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * registerExecutableStoriesPlugin(on);\n * },\n * },\n * });\n */\n\nimport { recordMeta, getMeta } from \"./store\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\nconst GET_STORED_META_TASK = \"executableStories:getStoredMeta\";\n\n/** Minimal type for Cypress plugin `on` (task registration) */\nexport interface PluginEvents {\n (event: \"task\", tasks: Record<string, (arg: unknown) => unknown>): void;\n}\n\n/**\n * Register the executable-stories task on the Cypress `on` handler.\n * Call this from setupNodeEvents in your Cypress config.\n */\n/** Argument for getStoredMeta task (used by tests to assert on recorded meta). */\nexport interface GetStoredMetaArg {\n specRelative: string;\n titlePath: string[];\n}\n\nexport function registerExecutableStoriesPlugin(on: PluginEvents): void {\n on(\"task\", {\n [TASK_NAME]: (payload: unknown) => recordMeta(payload as import(\"./types\").RecordMetaPayload),\n [GET_STORED_META_TASK]: (arg: unknown) => {\n const { specRelative, titlePath } = arg as GetStoredMetaArg;\n return getMeta(specRelative, titlePath) ?? null;\n },\n });\n}\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n return null;\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcA,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress Node plugin: registers the task that receives story meta from the browser.\n * Use in cypress.config.ts (or plugins file) so the reporter can merge meta with run results.\n *\n * @example\n * // cypress.config.ts\n * import { defineConfig } from 'cypress';\n * import { registerExecutableStoriesPlugin } from 'executable-stories-cypress/plugin';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * registerExecutableStoriesPlugin(on);\n * },\n * },\n * });\n */\n\nimport { recordMeta, getMeta } from \"./store\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\nconst GET_STORED_META_TASK = \"executableStories:getStoredMeta\";\n\n/** Minimal type for Cypress plugin `on` (task registration) */\nexport interface PluginEvents {\n (event: \"task\", tasks: Record<string, (arg: unknown) => unknown>): void;\n}\n\n/**\n * Register the executable-stories task on the Cypress `on` handler.\n * Call this from setupNodeEvents in your Cypress config.\n */\n/** Argument for getStoredMeta task (used by tests to assert on recorded meta). */\nexport interface GetStoredMetaArg {\n specRelative: string;\n titlePath: string[];\n}\n\nexport function registerExecutableStoriesPlugin(on: PluginEvents): void {\n on(\"task\", {\n [TASK_NAME]: (payload: unknown) => recordMeta(payload as import(\"./types\").RecordMetaPayload),\n [GET_STORED_META_TASK]: (arg: unknown) => {\n const { specRelative, titlePath } = arg as GetStoredMetaArg;\n return getMeta(specRelative, titlePath) ?? null;\n },\n });\n}\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload, FeatureInput } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\nconst features = new Map<string, FeatureInput>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n if (payload.feature?.title) features.set(payload.specRelative, payload.feature);\n return null;\n}\n\n/** The feature declared by a spec, if it declared one. */\nexport function getFeature(specRelative: string): FeatureInput | undefined {\n return features.get(specRelative);\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n features.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACcA,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;AAC1C,IAAM,WAAW,oBAAI,IAA0B;AAExC,SAAS,WAAW,SAAkC;AAC3D,QAAM,IAAI,IAAI,QAAQ,cAAc,QAAQ,SAAS,GAAG;AAAA,IACtD,cAAc,QAAQ;AAAA,IACtB,WAAW,QAAQ;AAAA,IACnB,MAAM,QAAQ;AAAA,IACd,aAAa,QAAQ;AAAA,EACvB,CAAC;AACD,MAAI,QAAQ,SAAS,MAAO,UAAS,IAAI,QAAQ,cAAc,QAAQ,OAAO;AAC9E,SAAO;AACT;AAWO,SAAS,QAAQ,cAAsB,WAA4C;AACxF,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;;;ADvBA,IAAM,YAAY;AAClB,IAAM,uBAAuB;AAiBtB,SAAS,gCAAgC,IAAwB;AACtE,KAAG,QAAQ;AAAA,IACT,CAAC,SAAS,GAAG,CAAC,YAAqB,WAAW,OAA8C;AAAA,IAC5F,CAAC,oBAAoB,GAAG,CAAC,QAAiB;AACxC,YAAM,EAAE,cAAc,UAAU,IAAI;AACpC,aAAO,QAAQ,cAAc,SAAS,KAAK;AAAA,IAC7C;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/plugin.js
CHANGED
|
@@ -3,6 +3,7 @@ function key(specRelative, titlePath) {
|
|
|
3
3
|
return `${specRelative}\0${titlePath.join("\0")}`;
|
|
4
4
|
}
|
|
5
5
|
var store = /* @__PURE__ */ new Map();
|
|
6
|
+
var features = /* @__PURE__ */ new Map();
|
|
6
7
|
function recordMeta(payload) {
|
|
7
8
|
store.set(key(payload.specRelative, payload.titlePath), {
|
|
8
9
|
specRelative: payload.specRelative,
|
|
@@ -10,6 +11,7 @@ function recordMeta(payload) {
|
|
|
10
11
|
meta: payload.meta,
|
|
11
12
|
attachments: payload.attachments
|
|
12
13
|
});
|
|
14
|
+
if (payload.feature?.title) features.set(payload.specRelative, payload.feature);
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
15
17
|
function getMeta(specRelative, titlePath) {
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n return null;\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n}\n","/**\n * Cypress Node plugin: registers the task that receives story meta from the browser.\n * Use in cypress.config.ts (or plugins file) so the reporter can merge meta with run results.\n *\n * @example\n * // cypress.config.ts\n * import { defineConfig } from 'cypress';\n * import { registerExecutableStoriesPlugin } from 'executable-stories-cypress/plugin';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * registerExecutableStoriesPlugin(on);\n * },\n * },\n * });\n */\n\nimport { recordMeta, getMeta } from \"./store\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\nconst GET_STORED_META_TASK = \"executableStories:getStoredMeta\";\n\n/** Minimal type for Cypress plugin `on` (task registration) */\nexport interface PluginEvents {\n (event: \"task\", tasks: Record<string, (arg: unknown) => unknown>): void;\n}\n\n/**\n * Register the executable-stories task on the Cypress `on` handler.\n * Call this from setupNodeEvents in your Cypress config.\n */\n/** Argument for getStoredMeta task (used by tests to assert on recorded meta). */\nexport interface GetStoredMetaArg {\n specRelative: string;\n titlePath: string[];\n}\n\nexport function registerExecutableStoriesPlugin(on: PluginEvents): void {\n on(\"task\", {\n [TASK_NAME]: (payload: unknown) => recordMeta(payload as import(\"./types\").RecordMetaPayload),\n [GET_STORED_META_TASK]: (arg: unknown) => {\n const { specRelative, titlePath } = arg as GetStoredMetaArg;\n return getMeta(specRelative, titlePath) ?? null;\n },\n });\n}\n"],"mappings":";AAcA,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload, FeatureInput } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\nconst features = new Map<string, FeatureInput>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n if (payload.feature?.title) features.set(payload.specRelative, payload.feature);\n return null;\n}\n\n/** The feature declared by a spec, if it declared one. */\nexport function getFeature(specRelative: string): FeatureInput | undefined {\n return features.get(specRelative);\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n features.clear();\n}\n","/**\n * Cypress Node plugin: registers the task that receives story meta from the browser.\n * Use in cypress.config.ts (or plugins file) so the reporter can merge meta with run results.\n *\n * @example\n * // cypress.config.ts\n * import { defineConfig } from 'cypress';\n * import { registerExecutableStoriesPlugin } from 'executable-stories-cypress/plugin';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * registerExecutableStoriesPlugin(on);\n * },\n * },\n * });\n */\n\nimport { recordMeta, getMeta } from \"./store\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\nconst GET_STORED_META_TASK = \"executableStories:getStoredMeta\";\n\n/** Minimal type for Cypress plugin `on` (task registration) */\nexport interface PluginEvents {\n (event: \"task\", tasks: Record<string, (arg: unknown) => unknown>): void;\n}\n\n/**\n * Register the executable-stories task on the Cypress `on` handler.\n * Call this from setupNodeEvents in your Cypress config.\n */\n/** Argument for getStoredMeta task (used by tests to assert on recorded meta). */\nexport interface GetStoredMetaArg {\n specRelative: string;\n titlePath: string[];\n}\n\nexport function registerExecutableStoriesPlugin(on: PluginEvents): void {\n on(\"task\", {\n [TASK_NAME]: (payload: unknown) => recordMeta(payload as import(\"./types\").RecordMetaPayload),\n [GET_STORED_META_TASK]: (arg: unknown) => {\n const { specRelative, titlePath } = arg as GetStoredMetaArg;\n return getMeta(specRelative, titlePath) ?? null;\n },\n });\n}\n"],"mappings":";AAcA,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;AAC1C,IAAM,WAAW,oBAAI,IAA0B;AAExC,SAAS,WAAW,SAAkC;AAC3D,QAAM,IAAI,IAAI,QAAQ,cAAc,QAAQ,SAAS,GAAG;AAAA,IACtD,cAAc,QAAQ;AAAA,IACtB,WAAW,QAAQ;AAAA,IACnB,MAAM,QAAQ;AAAA,IACd,aAAa,QAAQ;AAAA,EACvB,CAAC;AACD,MAAI,QAAQ,SAAS,MAAO,UAAS,IAAI,QAAQ,cAAc,QAAQ,OAAO;AAC9E,SAAO;AACT;AAWO,SAAS,QAAQ,cAAsB,WAA4C;AACxF,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;;;ACvBA,IAAM,YAAY;AAClB,IAAM,uBAAuB;AAiBtB,SAAS,gCAAgC,IAAwB;AACtE,KAAG,QAAQ;AAAA,IACT,CAAC,SAAS,GAAG,CAAC,YAAqB,WAAW,OAA8C;AAAA,IAC5F,CAAC,oBAAoB,GAAG,CAAC,QAAiB;AACxC,YAAM,EAAE,cAAc,UAAU,IAAI;AACpC,aAAO,QAAQ,cAAc,SAAS,KAAK;AAAA,IAC7C;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/reporter.cjs
CHANGED
|
@@ -44,6 +44,10 @@ function key(specRelative, titlePath) {
|
|
|
44
44
|
return `${specRelative}\0${titlePath.join("\0")}`;
|
|
45
45
|
}
|
|
46
46
|
var store = /* @__PURE__ */ new Map();
|
|
47
|
+
var features = /* @__PURE__ */ new Map();
|
|
48
|
+
function getFeature(specRelative) {
|
|
49
|
+
return features.get(specRelative);
|
|
50
|
+
}
|
|
47
51
|
function getAttachments(specRelative, titlePath) {
|
|
48
52
|
return store.get(key(specRelative, titlePath))?.attachments;
|
|
49
53
|
}
|
|
@@ -55,6 +59,7 @@ function getAllMeta() {
|
|
|
55
59
|
}
|
|
56
60
|
function clearStore() {
|
|
57
61
|
store.clear();
|
|
62
|
+
features.clear();
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
// src/reporter.ts
|
|
@@ -171,14 +176,17 @@ function createReporter(runner, options = {}) {
|
|
|
171
176
|
});
|
|
172
177
|
}
|
|
173
178
|
const includeMetadata = opts.markdown?.includeMetadata ?? true;
|
|
179
|
+
const declaredFeature = getFeature(effectiveSpecPath);
|
|
174
180
|
const rawRun = {
|
|
175
181
|
testCases: rawTestCases,
|
|
182
|
+
...declaredFeature ? { features: [{ ...declaredFeature, sourceFile: effectiveSpecPath }] } : {},
|
|
176
183
|
startedAtMs: startTime,
|
|
177
184
|
finishedAtMs: Date.now(),
|
|
178
185
|
projectRoot,
|
|
179
186
|
packageVersion: includeMetadata ? (0, import_executable_stories_formatters.readPackageVersion)(projectRoot) : void 0,
|
|
180
187
|
gitSha: includeMetadata ? (0, import_executable_stories_formatters.readGitSha)(projectRoot) : void 0,
|
|
181
|
-
ci: (0, import_executable_stories_formatters.detectCI)()
|
|
188
|
+
ci: (0, import_executable_stories_formatters.detectCI)(),
|
|
189
|
+
...opts.runScope ? { runScope: opts.runScope } : {}
|
|
182
190
|
};
|
|
183
191
|
const rawRunPath = opts.rawRunPath;
|
|
184
192
|
if (rawRunPath) {
|
|
@@ -228,7 +236,8 @@ function buildRawRunFromCypressResult(result, options = {}) {
|
|
|
228
236
|
projectRoot,
|
|
229
237
|
packageVersion: includeMetadata ? (0, import_executable_stories_formatters.readPackageVersion)(projectRoot) : void 0,
|
|
230
238
|
gitSha: includeMetadata ? (0, import_executable_stories_formatters.readGitSha)(projectRoot) : void 0,
|
|
231
|
-
ci: (0, import_executable_stories_formatters.detectCI)()
|
|
239
|
+
ci: (0, import_executable_stories_formatters.detectCI)(),
|
|
240
|
+
...options.runScope ? { runScope: options.runScope } : {}
|
|
232
241
|
};
|
|
233
242
|
}
|
|
234
243
|
async function generateReportsFromRawRun(rawRun, options) {
|
package/dist/reporter.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reporter.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress/Mocha reporter for executable-stories.\n * Builds RawRun (formatters schema) from run results + stored story meta, then generates reports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\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 RawStatus,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\nimport { getMeta, getAttachments, getAllMeta, clearStore } from \"./store\";\n\n// Re-export types from formatters\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for CLI/binary */\n rawRunPath?: string;\n /** Spec file path (relative to projectRoot) for this run. If omitted, inferred from stored meta when possible. */\n specPath?: string;\n /** Project root. Defaults to process.cwd(). */\n projectRoot?: string;\n}\n\n// ============================================================================\n// Mocha Runner / Test types (minimal for reporter)\n// ============================================================================\n\ninterface MochaSuite {\n title: string;\n suites: MochaSuite[];\n tests: MochaTest[];\n parent?: MochaSuite;\n}\n\ninterface MochaTest {\n title: string;\n state?: string;\n duration?: number;\n err?: Error & { message?: string; stack?: string };\n parent?: MochaSuite;\n /** Mocha marks both `it.skip(...)` and a bodyless `it(\"title\")` as pending. */\n pending?: boolean;\n /** Absent only when the test was declared with no callback at all. */\n fn?: unknown;\n}\n\n/**\n * A bodyless `it(\"title\")` is Mocha's way of writing down behaviour that does\n * not exist yet, which is what a planned scenario is. `it.skip(title, fn)` is\n * also pending but keeps its body: that means \"do not run this now\", a\n * different claim, so it stays a skip.\n */\nfunction isPlannedTest(test: MochaTest): boolean {\n return test.pending === true && test.fn === undefined;\n}\n\ninterface MochaRunner {\n suite: MochaSuite;\n startTime?: number;\n on?(event: string, callback: () => void): void;\n}\n\nfunction getTitlePath(test: MochaTest): string[] {\n const path: string[] = [];\n let current: MochaSuite | MochaTest | undefined = test;\n while (current) {\n const title = \"title\" in current ? current.title : \"\";\n if (title) path.unshift(title);\n current = \"parent\" in current ? current.parent : undefined;\n }\n return path;\n}\n\nfunction collectTests(suite: MochaSuite): MochaTest[] {\n const tests = [...suite.tests];\n for (const s of suite.suites) {\n tests.push(...collectTests(s));\n }\n return tests;\n}\n\nfunction mapCypressStateToRaw(state: string | undefined): RawStatus {\n switch (state) {\n case \"passed\":\n return \"pass\";\n case \"failed\":\n return \"fail\";\n case \"pending\":\n return \"skip\";\n default:\n return \"unknown\";\n }\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nconst DEFAULT_OPTIONS: Partial<StoryReporterOptions> = {\n projectRoot: process.cwd(),\n};\n\n/**\n * Create a Mocha reporter function that Cypress can use (--reporter path/to/reporter).\n * Cypress passes (runner, reporterOptions) with reporterOptions as the second argument directly.\n * Also supports options.reporterOptions for wrapped usage.\n */\nfunction createReporter(\n runner: MochaRunner,\n options: StoryReporterOptions | { reporterOptions?: StoryReporterOptions } = {}\n) {\n const reporterOpts =\n \"reporterOptions\" in options && options.reporterOptions\n ? options.reporterOptions\n : (options as StoryReporterOptions);\n const opts: StoryReporterOptions = { ...DEFAULT_OPTIONS, ...reporterOpts };\n const projectRoot = opts.projectRoot ?? process.cwd();\n const specPath = opts.specPath ?? \"unknown\";\n const startTime = runner.startTime ?? Date.now();\n\n runner.suite && runner.on?.(\"end\", () => {\n const tests = collectTests(runner.suite);\n const rawTestCases: RawTestCase[] = [];\n\n // If specPath not provided, infer from first stored meta (all stored entries are for this spec in a single-spec run)\n const effectiveSpecPath =\n specPath !== \"unknown\"\n ? specPath\n : (() => {\n const all = getAllMeta();\n return all.length > 0 ? all[0].specRelative : \"unknown\";\n })();\n\n // Bodyless tests never run story.init(), so they are collected separately\n // and only kept when the spec has real story tests to sit alongside.\n const plannedTests: MochaTest[] = [];\n\n for (const test of tests) {\n const titlePath = getTitlePath(test);\n const meta = getMeta(effectiveSpecPath, titlePath);\n if (!meta) {\n if (isPlannedTest(test)) plannedTests.push(test);\n continue; // only document tests that used story.init()\n }\n\n const status = mapCypressStateToRaw(test.state);\n\n // Map stored attachments to RawAttachment[]\n const storedAttachments = getAttachments(effectiveSpecPath, titlePath);\n const attachments: RawAttachment[] | undefined = storedAttachments?.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: typeof a.body === 'string' ? a.body : a.body instanceof Buffer ? a.body.toString('base64') : undefined,\n encoding: a.encoding ?? (a.body instanceof Buffer ? 'BASE64' : undefined),\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\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 rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status,\n durationMs: test.duration,\n error: test.err\n ? { message: test.err.message, stack: test.err.stack }\n : undefined,\n attachments: attachments && attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n if (rawTestCases.length === 0) {\n clearStore();\n return;\n }\n\n for (const test of plannedTests) {\n const titlePath = getTitlePath(test);\n const suitePath = titlePath.slice(0, -1);\n rawTestCases.push({\n title: test.title,\n titlePath,\n story: {\n scenario: test.title,\n steps: [],\n ...(suitePath.length > 0 ? { suitePath } : {}),\n },\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n\n const includeMetadata = opts.markdown?.includeMetadata ?? true;\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: startTime,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n };\n\n const rawRunPath = opts.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(absolutePath, JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2), \"utf8\");\n }\n\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(opts);\n generator.generate(canonicalRun).catch((err) => {\n console.error(\"Failed to generate reports:\", err);\n });\n\n clearStore();\n });\n}\n\n/**\n * Alternative: build RawRun from Cypress Module API results + stored meta.\n * Use this when not using the Mocha reporter (e.g. in after:run plugin).\n * Call recordMeta via plugin during the run, then call this with cypress.run() result.\n */\nexport interface CypressRunResultTest {\n title: string[];\n state?: string;\n displayError?: string;\n duration?: number;\n attempts?: Array<{\n state?: string;\n duration?: number;\n error?: { message?: string; stack?: string };\n }>;\n}\n\nexport interface CypressRunResultRun {\n spec?: { relative?: string; absolute?: string };\n tests?: CypressRunResultTest[];\n}\n\nexport interface CypressRunResult {\n runs?: CypressRunResultRun[];\n config?: { projectRoot?: string };\n}\n\nexport function buildRawRunFromCypressResult(\n result: CypressRunResult,\n options: StoryReporterOptions = {}\n): RawRun {\n const projectRoot = options.projectRoot ?? result.config?.projectRoot ?? process.cwd();\n const rawTestCases: RawTestCase[] = [];\n\n for (const run of result.runs ?? []) {\n const specRelative = run.spec?.relative ?? \"unknown\";\n for (const test of run.tests ?? []) {\n const titlePath = test.title ?? [];\n const meta = getMeta(specRelative, titlePath);\n if (!meta) continue;\n\n const lastAttempt = test.attempts?.length\n ? test.attempts[test.attempts.length - 1]\n : undefined;\n const state = lastAttempt?.state ?? test.state;\n const err = lastAttempt?.error ?? (test.displayError ? { message: test.displayError } : undefined);\n const duration = lastAttempt?.duration ?? test.duration;\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: specRelative,\n sourceLine: 1,\n status: mapCypressStateToRaw(state),\n durationMs: duration,\n error: err ? { message: err.message, stack: err.stack } : undefined,\n });\n }\n }\n\n const includeMetadata = options.markdown?.includeMetadata ?? true;\n return {\n testCases: rawTestCases,\n startedAtMs: undefined,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n };\n}\n\n/**\n * Generate reports from a RawRun (e.g. after buildRawRunFromCypressResult).\n */\nexport async function generateReportsFromRawRun(\n rawRun: RawRun,\n options: FormatterOptions\n): Promise<void> {\n if (rawRun.testCases.length === 0) return;\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(options);\n await generator.generate(canonicalRun);\n}\n\nexport default createReporter;\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n return null;\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n}\n"],"mappings":"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","names":["path"]}
|
|
1
|
+
{"version":3,"sources":["../src/reporter.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress/Mocha reporter for executable-stories.\n * Builds RawRun (formatters schema) from run results + stored story meta, then generates reports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\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 RawStatus,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\nimport { getMeta, getAttachments, getAllMeta, getFeature, clearStore } from \"./store\";\n\n// Re-export types from formatters\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for CLI/binary */\n rawRunPath?: string;\n /** Spec file path (relative to projectRoot) for this run. If omitted, inferred from stored meta when possible. */\n specPath?: string;\n /** Project root. Defaults to process.cwd(). */\n projectRoot?: string;\n /**\n * Declare how much of each spec this run covered.\n *\n * Cypress has no built-in name filter — narrowing by title needs\n * @cypress/grep, which the reporter cannot observe — so unlike the Vitest,\n * Jest and Playwright adapters this cannot be detected. Left unset the run\n * states no scope, and consumers keep what it did not report rather than\n * retiring it on a guess.\n *\n * Set `\"filtered\"` when running under @cypress/grep. Set `\"full\"` only when\n * you know the run covered whole specs, since that is what permits a\n * scenario to be removed from the accumulated report.\n */\n runScope?: \"full\" | \"filtered\";\n}\n\n// ============================================================================\n// Mocha Runner / Test types (minimal for reporter)\n// ============================================================================\n\ninterface MochaSuite {\n title: string;\n suites: MochaSuite[];\n tests: MochaTest[];\n parent?: MochaSuite;\n}\n\ninterface MochaTest {\n title: string;\n state?: string;\n duration?: number;\n err?: Error & { message?: string; stack?: string };\n parent?: MochaSuite;\n /** Mocha marks both `it.skip(...)` and a bodyless `it(\"title\")` as pending. */\n pending?: boolean;\n /** Absent only when the test was declared with no callback at all. */\n fn?: unknown;\n}\n\n/**\n * A bodyless `it(\"title\")` is Mocha's way of writing down behaviour that does\n * not exist yet, which is what a planned scenario is. `it.skip(title, fn)` is\n * also pending but keeps its body: that means \"do not run this now\", a\n * different claim, so it stays a skip.\n */\nfunction isPlannedTest(test: MochaTest): boolean {\n return test.pending === true && test.fn === undefined;\n}\n\ninterface MochaRunner {\n suite: MochaSuite;\n startTime?: number;\n on?(event: string, callback: () => void): void;\n}\n\nfunction getTitlePath(test: MochaTest): string[] {\n const path: string[] = [];\n let current: MochaSuite | MochaTest | undefined = test;\n while (current) {\n const title = \"title\" in current ? current.title : \"\";\n if (title) path.unshift(title);\n current = \"parent\" in current ? current.parent : undefined;\n }\n return path;\n}\n\nfunction collectTests(suite: MochaSuite): MochaTest[] {\n const tests = [...suite.tests];\n for (const s of suite.suites) {\n tests.push(...collectTests(s));\n }\n return tests;\n}\n\nfunction mapCypressStateToRaw(state: string | undefined): RawStatus {\n switch (state) {\n case \"passed\":\n return \"pass\";\n case \"failed\":\n return \"fail\";\n case \"pending\":\n return \"skip\";\n default:\n return \"unknown\";\n }\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nconst DEFAULT_OPTIONS: Partial<StoryReporterOptions> = {\n projectRoot: process.cwd(),\n};\n\n/**\n * Create a Mocha reporter function that Cypress can use (--reporter path/to/reporter).\n * Cypress passes (runner, reporterOptions) with reporterOptions as the second argument directly.\n * Also supports options.reporterOptions for wrapped usage.\n */\nfunction createReporter(\n runner: MochaRunner,\n options: StoryReporterOptions | { reporterOptions?: StoryReporterOptions } = {}\n) {\n const reporterOpts =\n \"reporterOptions\" in options && options.reporterOptions\n ? options.reporterOptions\n : (options as StoryReporterOptions);\n const opts: StoryReporterOptions = { ...DEFAULT_OPTIONS, ...reporterOpts };\n const projectRoot = opts.projectRoot ?? process.cwd();\n const specPath = opts.specPath ?? \"unknown\";\n const startTime = runner.startTime ?? Date.now();\n\n runner.suite && runner.on?.(\"end\", () => {\n const tests = collectTests(runner.suite);\n const rawTestCases: RawTestCase[] = [];\n\n // If specPath not provided, infer from first stored meta (all stored entries are for this spec in a single-spec run)\n const effectiveSpecPath =\n specPath !== \"unknown\"\n ? specPath\n : (() => {\n const all = getAllMeta();\n return all.length > 0 ? all[0].specRelative : \"unknown\";\n })();\n\n // Bodyless tests never run story.init(), so they are collected separately\n // and only kept when the spec has real story tests to sit alongside.\n const plannedTests: MochaTest[] = [];\n\n for (const test of tests) {\n const titlePath = getTitlePath(test);\n const meta = getMeta(effectiveSpecPath, titlePath);\n if (!meta) {\n if (isPlannedTest(test)) plannedTests.push(test);\n continue; // only document tests that used story.init()\n }\n\n const status = mapCypressStateToRaw(test.state);\n\n // Map stored attachments to RawAttachment[]\n const storedAttachments = getAttachments(effectiveSpecPath, titlePath);\n const attachments: RawAttachment[] | undefined = storedAttachments?.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: typeof a.body === 'string' ? a.body : a.body instanceof Buffer ? a.body.toString('base64') : undefined,\n encoding: a.encoding ?? (a.body instanceof Buffer ? 'BASE64' : undefined),\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\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 rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status,\n durationMs: test.duration,\n error: test.err\n ? { message: test.err.message, stack: test.err.stack }\n : undefined,\n attachments: attachments && attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n if (rawTestCases.length === 0) {\n clearStore();\n return;\n }\n\n for (const test of plannedTests) {\n const titlePath = getTitlePath(test);\n const suitePath = titlePath.slice(0, -1);\n rawTestCases.push({\n title: test.title,\n titlePath,\n story: {\n scenario: test.title,\n steps: [],\n ...(suitePath.length > 0 ? { suitePath } : {}),\n },\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n\n const includeMetadata = opts.markdown?.includeMetadata ?? true;\n const declaredFeature = getFeature(effectiveSpecPath);\n const rawRun: RawRun = {\n testCases: rawTestCases,\n ...(declaredFeature\n ? { features: [{ ...declaredFeature, sourceFile: effectiveSpecPath }] }\n : {}),\n startedAtMs: startTime,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n ...(opts.runScope ? { runScope: opts.runScope } : {}),\n };\n\n const rawRunPath = opts.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(absolutePath, JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2), \"utf8\");\n }\n\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(opts);\n generator.generate(canonicalRun).catch((err) => {\n console.error(\"Failed to generate reports:\", err);\n });\n\n clearStore();\n });\n}\n\n/**\n * Alternative: build RawRun from Cypress Module API results + stored meta.\n * Use this when not using the Mocha reporter (e.g. in after:run plugin).\n * Call recordMeta via plugin during the run, then call this with cypress.run() result.\n */\nexport interface CypressRunResultTest {\n title: string[];\n state?: string;\n displayError?: string;\n duration?: number;\n attempts?: Array<{\n state?: string;\n duration?: number;\n error?: { message?: string; stack?: string };\n }>;\n}\n\nexport interface CypressRunResultRun {\n spec?: { relative?: string; absolute?: string };\n tests?: CypressRunResultTest[];\n}\n\nexport interface CypressRunResult {\n runs?: CypressRunResultRun[];\n config?: { projectRoot?: string };\n}\n\nexport function buildRawRunFromCypressResult(\n result: CypressRunResult,\n options: StoryReporterOptions = {}\n): RawRun {\n const projectRoot = options.projectRoot ?? result.config?.projectRoot ?? process.cwd();\n const rawTestCases: RawTestCase[] = [];\n\n for (const run of result.runs ?? []) {\n const specRelative = run.spec?.relative ?? \"unknown\";\n for (const test of run.tests ?? []) {\n const titlePath = test.title ?? [];\n const meta = getMeta(specRelative, titlePath);\n if (!meta) continue;\n\n const lastAttempt = test.attempts?.length\n ? test.attempts[test.attempts.length - 1]\n : undefined;\n const state = lastAttempt?.state ?? test.state;\n const err = lastAttempt?.error ?? (test.displayError ? { message: test.displayError } : undefined);\n const duration = lastAttempt?.duration ?? test.duration;\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: specRelative,\n sourceLine: 1,\n status: mapCypressStateToRaw(state),\n durationMs: duration,\n error: err ? { message: err.message, stack: err.stack } : undefined,\n });\n }\n }\n\n const includeMetadata = options.markdown?.includeMetadata ?? true;\n return {\n testCases: rawTestCases,\n startedAtMs: undefined,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n ...(options.runScope ? { runScope: options.runScope } : {}),\n };\n}\n\n/**\n * Generate reports from a RawRun (e.g. after buildRawRunFromCypressResult).\n */\nexport async function generateReportsFromRawRun(\n rawRun: RawRun,\n options: FormatterOptions\n): Promise<void> {\n if (rawRun.testCases.length === 0) return;\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(options);\n await generator.generate(canonicalRun);\n}\n\nexport default createReporter;\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload, FeatureInput } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\nconst features = new Map<string, FeatureInput>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n if (payload.feature?.title) features.set(payload.specRelative, payload.feature);\n return null;\n}\n\n/** The feature declared by a spec, if it declared one. */\nexport function getFeature(specRelative: string): FeatureInput | undefined {\n return features.get(specRelative);\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n features.clear();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAoB;AACpB,WAAsB;AAGtB,2CAYO;;;ACPP,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;AAC1C,IAAM,WAAW,oBAAI,IAA0B;AAcxC,SAAS,WAAW,cAAgD;AACzE,SAAO,SAAS,IAAI,YAAY;AAClC;AAEO,SAAS,eAAe,cAAsB,WAAqD;AACxG,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;AAEO,SAAS,QAAQ,cAAsB,WAA4C;AACxF,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;AAEO,SAAS,aAA2B;AACzC,SAAO,MAAM,KAAK,MAAM,OAAO,CAAC;AAClC;AAEO,SAAS,aAAmB;AACjC,QAAM,MAAM;AACZ,WAAS,MAAM;AACjB;;;ADsCA,SAAS,cAAc,MAA0B;AAC/C,SAAO,KAAK,YAAY,QAAQ,KAAK,OAAO;AAC9C;AAQA,SAAS,aAAa,MAA2B;AAC/C,QAAMA,QAAiB,CAAC;AACxB,MAAI,UAA8C;AAClD,SAAO,SAAS;AACd,UAAM,QAAQ,WAAW,UAAU,QAAQ,QAAQ;AACnD,QAAI,MAAO,CAAAA,MAAK,QAAQ,KAAK;AAC7B,cAAU,YAAY,UAAU,QAAQ,SAAS;AAAA,EACnD;AACA,SAAOA;AACT;AAEA,SAAS,aAAa,OAAgC;AACpD,QAAM,QAAQ,CAAC,GAAG,MAAM,KAAK;AAC7B,aAAW,KAAK,MAAM,QAAQ;AAC5B,UAAM,KAAK,GAAG,aAAa,CAAC,CAAC;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAsC;AAClE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAMA,IAAM,kBAAiD;AAAA,EACrD,aAAa,QAAQ,IAAI;AAC3B;AAOA,SAAS,eACP,QACA,UAA6E,CAAC,GAC9E;AACA,QAAM,eACJ,qBAAqB,WAAW,QAAQ,kBACpC,QAAQ,kBACP;AACP,QAAM,OAA6B,EAAE,GAAG,iBAAiB,GAAG,aAAa;AACzE,QAAM,cAAc,KAAK,eAAe,QAAQ,IAAI;AACpD,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,YAAY,OAAO,aAAa,KAAK,IAAI;AAE/C,SAAO,SAAS,OAAO,KAAK,OAAO,MAAM;AACvC,UAAM,QAAQ,aAAa,OAAO,KAAK;AACvC,UAAM,eAA8B,CAAC;AAGrC,UAAM,oBACJ,aAAa,YACT,YACC,MAAM;AACL,YAAM,MAAM,WAAW;AACvB,aAAO,IAAI,SAAS,IAAI,IAAI,CAAC,EAAE,eAAe;AAAA,IAChD,GAAG;AAIT,UAAM,eAA4B,CAAC;AAEnC,eAAW,QAAQ,OAAO;AACxB,YAAM,YAAY,aAAa,IAAI;AACnC,YAAM,OAAO,QAAQ,mBAAmB,SAAS;AACjD,UAAI,CAAC,MAAM;AACT,YAAI,cAAc,IAAI,EAAG,cAAa,KAAK,IAAI;AAC/C;AAAA,MACF;AAEA,YAAM,SAAS,qBAAqB,KAAK,KAAK;AAG9C,YAAM,oBAAoB,eAAe,mBAAmB,SAAS;AACrE,YAAM,cAA2C,mBAAmB,IAAI,CAAC,OAAO;AAAA,QAC9E,MAAM,EAAE;AAAA,QACR,WAAW,EAAE;AAAA,QACb,MAAM,EAAE;AAAA,QACR,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,gBAAgB,SAAS,EAAE,KAAK,SAAS,QAAQ,IAAI;AAAA,QACnG,UAAU,EAAE,aAAa,EAAE,gBAAgB,SAAS,WAAW;AAAA,QAC/D,SAAS,EAAE;AAAA,QACX,UAAU,EAAE;AAAA,QACZ,WAAW,EAAE;AAAA,QACb,QAAQ,EAAE;AAAA,MACZ,EAAE;AAGF,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,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK,YAAY,CAAC,GAAG,KAAK,WAAW,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ;AAAA,QAC/E,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ;AAAA,QACA,YAAY,KAAK;AAAA,QACjB,OAAO,KAAK,MACR,EAAE,SAAS,KAAK,IAAI,SAAS,OAAO,KAAK,IAAI,MAAM,IACnD;AAAA,QACJ,aAAa,eAAe,YAAY,SAAS,IAAI,cAAc;AAAA,QACnE,YAAY,WAAW,SAAS,IAAI,aAAa;AAAA,QACjD,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B,iBAAW;AACX;AAAA,IACF;AAEA,eAAW,QAAQ,cAAc;AAC/B,YAAM,YAAY,aAAa,IAAI;AACnC,YAAM,YAAY,UAAU,MAAM,GAAG,EAAE;AACvC,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,OAAO;AAAA,UACL,UAAU,KAAK;AAAA,UACf,OAAO,CAAC;AAAA,UACR,GAAI,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,QAC9C;AAAA,QACA,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,UAAM,kBAAkB,KAAK,UAAU,mBAAmB;AAC1D,UAAM,kBAAkB,WAAW,iBAAiB;AACpD,UAAM,SAAiB;AAAA,MACrB,WAAW;AAAA,MACX,GAAI,kBACA,EAAE,UAAU,CAAC,EAAE,GAAG,iBAAiB,YAAY,kBAAkB,CAAC,EAAE,IACpE,CAAC;AAAA,MACL,aAAa;AAAA,MACb,cAAc,KAAK,IAAI;AAAA,MACvB;AAAA,MACA,gBAAgB,sBAAkB,yDAAmB,WAAW,IAAI;AAAA,MACpE,QAAQ,sBAAkB,iDAAW,WAAW,IAAI;AAAA,MACpD,QAAI,+CAAS;AAAA,MACb,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACrD;AAEA,UAAM,aAAa,KAAK;AACxB,QAAI,YAAY;AACd,YAAM,eAAoB,gBAAW,UAAU,IAC3C,aACK,UAAK,aAAa,UAAU;AACrC,YAAM,MAAW,aAAQ,YAAY;AACrC,UAAI,CAAI,cAAW,GAAG,EAAG,CAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC9D,MAAG,iBAAc,cAAc,KAAK,UAAU,EAAE,eAAe,GAAG,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG,MAAM;AAAA,IACjG;AAEA,UAAM,mBAAe,sDAAgB,MAAM;AAC3C,UAAM,YAAY,IAAI,qDAAgB,IAAI;AAC1C,cAAU,SAAS,YAAY,EAAE,MAAM,CAAC,QAAQ;AAC9C,cAAQ,MAAM,+BAA+B,GAAG;AAAA,IAClD,CAAC;AAED,eAAW;AAAA,EACb,CAAC;AACH;AA6BO,SAAS,6BACd,QACA,UAAgC,CAAC,GACzB;AACR,QAAM,cAAc,QAAQ,eAAe,OAAO,QAAQ,eAAe,QAAQ,IAAI;AACrF,QAAM,eAA8B,CAAC;AAErC,aAAW,OAAO,OAAO,QAAQ,CAAC,GAAG;AACnC,UAAM,eAAe,IAAI,MAAM,YAAY;AAC3C,eAAW,QAAQ,IAAI,SAAS,CAAC,GAAG;AAClC,YAAM,YAAY,KAAK,SAAS,CAAC;AACjC,YAAM,OAAO,QAAQ,cAAc,SAAS;AAC5C,UAAI,CAAC,KAAM;AAEX,YAAM,cAAc,KAAK,UAAU,SAC/B,KAAK,SAAS,KAAK,SAAS,SAAS,CAAC,IACtC;AACJ,YAAM,QAAQ,aAAa,SAAS,KAAK;AACzC,YAAM,MAAM,aAAa,UAAU,KAAK,eAAe,EAAE,SAAS,KAAK,aAAa,IAAI;AACxF,YAAM,WAAW,aAAa,YAAY,KAAK;AAE/C,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK,YAAY,CAAC,GAAG,KAAK,WAAW,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ;AAAA,QAC/E,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ,qBAAqB,KAAK;AAAA,QAClC,YAAY;AAAA,QACZ,OAAO,MAAM,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,IAAI;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,kBAAkB,QAAQ,UAAU,mBAAmB;AAC7D,SAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc,KAAK,IAAI;AAAA,IACvB;AAAA,IACA,gBAAgB,sBAAkB,yDAAmB,WAAW,IAAI;AAAA,IACpE,QAAQ,sBAAkB,iDAAW,WAAW,IAAI;AAAA,IACpD,QAAI,+CAAS;AAAA,IACb,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,EAC3D;AACF;AAKA,eAAsB,0BACpB,QACA,SACe;AACf,MAAI,OAAO,UAAU,WAAW,EAAG;AACnC,QAAM,mBAAe,sDAAgB,MAAM;AAC3C,QAAM,YAAY,IAAI,qDAAgB,OAAO;AAC7C,QAAM,UAAU,SAAS,YAAY;AACvC;AAEA,IAAO,mBAAQ;","names":["path"]}
|
package/dist/reporter.d.cts
CHANGED
|
@@ -13,6 +13,20 @@ interface StoryReporterOptions extends FormatterOptions {
|
|
|
13
13
|
specPath?: string;
|
|
14
14
|
/** Project root. Defaults to process.cwd(). */
|
|
15
15
|
projectRoot?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Declare how much of each spec this run covered.
|
|
18
|
+
*
|
|
19
|
+
* Cypress has no built-in name filter — narrowing by title needs
|
|
20
|
+
* @cypress/grep, which the reporter cannot observe — so unlike the Vitest,
|
|
21
|
+
* Jest and Playwright adapters this cannot be detected. Left unset the run
|
|
22
|
+
* states no scope, and consumers keep what it did not report rather than
|
|
23
|
+
* retiring it on a guess.
|
|
24
|
+
*
|
|
25
|
+
* Set `"filtered"` when running under @cypress/grep. Set `"full"` only when
|
|
26
|
+
* you know the run covered whole specs, since that is what permits a
|
|
27
|
+
* scenario to be removed from the accumulated report.
|
|
28
|
+
*/
|
|
29
|
+
runScope?: "full" | "filtered";
|
|
16
30
|
}
|
|
17
31
|
interface MochaSuite {
|
|
18
32
|
title: string;
|
package/dist/reporter.d.ts
CHANGED
|
@@ -13,6 +13,20 @@ interface StoryReporterOptions extends FormatterOptions {
|
|
|
13
13
|
specPath?: string;
|
|
14
14
|
/** Project root. Defaults to process.cwd(). */
|
|
15
15
|
projectRoot?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Declare how much of each spec this run covered.
|
|
18
|
+
*
|
|
19
|
+
* Cypress has no built-in name filter — narrowing by title needs
|
|
20
|
+
* @cypress/grep, which the reporter cannot observe — so unlike the Vitest,
|
|
21
|
+
* Jest and Playwright adapters this cannot be detected. Left unset the run
|
|
22
|
+
* states no scope, and consumers keep what it did not report rather than
|
|
23
|
+
* retiring it on a guess.
|
|
24
|
+
*
|
|
25
|
+
* Set `"filtered"` when running under @cypress/grep. Set `"full"` only when
|
|
26
|
+
* you know the run covered whole specs, since that is what permits a
|
|
27
|
+
* scenario to be removed from the accumulated report.
|
|
28
|
+
*/
|
|
29
|
+
runScope?: "full" | "filtered";
|
|
16
30
|
}
|
|
17
31
|
interface MochaSuite {
|
|
18
32
|
title: string;
|
package/dist/reporter.js
CHANGED
|
@@ -14,6 +14,10 @@ function key(specRelative, titlePath) {
|
|
|
14
14
|
return `${specRelative}\0${titlePath.join("\0")}`;
|
|
15
15
|
}
|
|
16
16
|
var store = /* @__PURE__ */ new Map();
|
|
17
|
+
var features = /* @__PURE__ */ new Map();
|
|
18
|
+
function getFeature(specRelative) {
|
|
19
|
+
return features.get(specRelative);
|
|
20
|
+
}
|
|
17
21
|
function getAttachments(specRelative, titlePath) {
|
|
18
22
|
return store.get(key(specRelative, titlePath))?.attachments;
|
|
19
23
|
}
|
|
@@ -25,6 +29,7 @@ function getAllMeta() {
|
|
|
25
29
|
}
|
|
26
30
|
function clearStore() {
|
|
27
31
|
store.clear();
|
|
32
|
+
features.clear();
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
// src/reporter.ts
|
|
@@ -141,14 +146,17 @@ function createReporter(runner, options = {}) {
|
|
|
141
146
|
});
|
|
142
147
|
}
|
|
143
148
|
const includeMetadata = opts.markdown?.includeMetadata ?? true;
|
|
149
|
+
const declaredFeature = getFeature(effectiveSpecPath);
|
|
144
150
|
const rawRun = {
|
|
145
151
|
testCases: rawTestCases,
|
|
152
|
+
...declaredFeature ? { features: [{ ...declaredFeature, sourceFile: effectiveSpecPath }] } : {},
|
|
146
153
|
startedAtMs: startTime,
|
|
147
154
|
finishedAtMs: Date.now(),
|
|
148
155
|
projectRoot,
|
|
149
156
|
packageVersion: includeMetadata ? readPackageVersion(projectRoot) : void 0,
|
|
150
157
|
gitSha: includeMetadata ? readGitSha(projectRoot) : void 0,
|
|
151
|
-
ci: detectCI()
|
|
158
|
+
ci: detectCI(),
|
|
159
|
+
...opts.runScope ? { runScope: opts.runScope } : {}
|
|
152
160
|
};
|
|
153
161
|
const rawRunPath = opts.rawRunPath;
|
|
154
162
|
if (rawRunPath) {
|
|
@@ -198,7 +206,8 @@ function buildRawRunFromCypressResult(result, options = {}) {
|
|
|
198
206
|
projectRoot,
|
|
199
207
|
packageVersion: includeMetadata ? readPackageVersion(projectRoot) : void 0,
|
|
200
208
|
gitSha: includeMetadata ? readGitSha(projectRoot) : void 0,
|
|
201
|
-
ci: detectCI()
|
|
209
|
+
ci: detectCI(),
|
|
210
|
+
...options.runScope ? { runScope: options.runScope } : {}
|
|
202
211
|
};
|
|
203
212
|
}
|
|
204
213
|
async function generateReportsFromRawRun(rawRun, options) {
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reporter.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress/Mocha reporter for executable-stories.\n * Builds RawRun (formatters schema) from run results + stored story meta, then generates reports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\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 RawStatus,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\nimport { getMeta, getAttachments, getAllMeta, clearStore } from \"./store\";\n\n// Re-export types from formatters\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for CLI/binary */\n rawRunPath?: string;\n /** Spec file path (relative to projectRoot) for this run. If omitted, inferred from stored meta when possible. */\n specPath?: string;\n /** Project root. Defaults to process.cwd(). */\n projectRoot?: string;\n}\n\n// ============================================================================\n// Mocha Runner / Test types (minimal for reporter)\n// ============================================================================\n\ninterface MochaSuite {\n title: string;\n suites: MochaSuite[];\n tests: MochaTest[];\n parent?: MochaSuite;\n}\n\ninterface MochaTest {\n title: string;\n state?: string;\n duration?: number;\n err?: Error & { message?: string; stack?: string };\n parent?: MochaSuite;\n /** Mocha marks both `it.skip(...)` and a bodyless `it(\"title\")` as pending. */\n pending?: boolean;\n /** Absent only when the test was declared with no callback at all. */\n fn?: unknown;\n}\n\n/**\n * A bodyless `it(\"title\")` is Mocha's way of writing down behaviour that does\n * not exist yet, which is what a planned scenario is. `it.skip(title, fn)` is\n * also pending but keeps its body: that means \"do not run this now\", a\n * different claim, so it stays a skip.\n */\nfunction isPlannedTest(test: MochaTest): boolean {\n return test.pending === true && test.fn === undefined;\n}\n\ninterface MochaRunner {\n suite: MochaSuite;\n startTime?: number;\n on?(event: string, callback: () => void): void;\n}\n\nfunction getTitlePath(test: MochaTest): string[] {\n const path: string[] = [];\n let current: MochaSuite | MochaTest | undefined = test;\n while (current) {\n const title = \"title\" in current ? current.title : \"\";\n if (title) path.unshift(title);\n current = \"parent\" in current ? current.parent : undefined;\n }\n return path;\n}\n\nfunction collectTests(suite: MochaSuite): MochaTest[] {\n const tests = [...suite.tests];\n for (const s of suite.suites) {\n tests.push(...collectTests(s));\n }\n return tests;\n}\n\nfunction mapCypressStateToRaw(state: string | undefined): RawStatus {\n switch (state) {\n case \"passed\":\n return \"pass\";\n case \"failed\":\n return \"fail\";\n case \"pending\":\n return \"skip\";\n default:\n return \"unknown\";\n }\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nconst DEFAULT_OPTIONS: Partial<StoryReporterOptions> = {\n projectRoot: process.cwd(),\n};\n\n/**\n * Create a Mocha reporter function that Cypress can use (--reporter path/to/reporter).\n * Cypress passes (runner, reporterOptions) with reporterOptions as the second argument directly.\n * Also supports options.reporterOptions for wrapped usage.\n */\nfunction createReporter(\n runner: MochaRunner,\n options: StoryReporterOptions | { reporterOptions?: StoryReporterOptions } = {}\n) {\n const reporterOpts =\n \"reporterOptions\" in options && options.reporterOptions\n ? options.reporterOptions\n : (options as StoryReporterOptions);\n const opts: StoryReporterOptions = { ...DEFAULT_OPTIONS, ...reporterOpts };\n const projectRoot = opts.projectRoot ?? process.cwd();\n const specPath = opts.specPath ?? \"unknown\";\n const startTime = runner.startTime ?? Date.now();\n\n runner.suite && runner.on?.(\"end\", () => {\n const tests = collectTests(runner.suite);\n const rawTestCases: RawTestCase[] = [];\n\n // If specPath not provided, infer from first stored meta (all stored entries are for this spec in a single-spec run)\n const effectiveSpecPath =\n specPath !== \"unknown\"\n ? specPath\n : (() => {\n const all = getAllMeta();\n return all.length > 0 ? all[0].specRelative : \"unknown\";\n })();\n\n // Bodyless tests never run story.init(), so they are collected separately\n // and only kept when the spec has real story tests to sit alongside.\n const plannedTests: MochaTest[] = [];\n\n for (const test of tests) {\n const titlePath = getTitlePath(test);\n const meta = getMeta(effectiveSpecPath, titlePath);\n if (!meta) {\n if (isPlannedTest(test)) plannedTests.push(test);\n continue; // only document tests that used story.init()\n }\n\n const status = mapCypressStateToRaw(test.state);\n\n // Map stored attachments to RawAttachment[]\n const storedAttachments = getAttachments(effectiveSpecPath, titlePath);\n const attachments: RawAttachment[] | undefined = storedAttachments?.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: typeof a.body === 'string' ? a.body : a.body instanceof Buffer ? a.body.toString('base64') : undefined,\n encoding: a.encoding ?? (a.body instanceof Buffer ? 'BASE64' : undefined),\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\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 rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status,\n durationMs: test.duration,\n error: test.err\n ? { message: test.err.message, stack: test.err.stack }\n : undefined,\n attachments: attachments && attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n if (rawTestCases.length === 0) {\n clearStore();\n return;\n }\n\n for (const test of plannedTests) {\n const titlePath = getTitlePath(test);\n const suitePath = titlePath.slice(0, -1);\n rawTestCases.push({\n title: test.title,\n titlePath,\n story: {\n scenario: test.title,\n steps: [],\n ...(suitePath.length > 0 ? { suitePath } : {}),\n },\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n\n const includeMetadata = opts.markdown?.includeMetadata ?? true;\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: startTime,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n };\n\n const rawRunPath = opts.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(absolutePath, JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2), \"utf8\");\n }\n\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(opts);\n generator.generate(canonicalRun).catch((err) => {\n console.error(\"Failed to generate reports:\", err);\n });\n\n clearStore();\n });\n}\n\n/**\n * Alternative: build RawRun from Cypress Module API results + stored meta.\n * Use this when not using the Mocha reporter (e.g. in after:run plugin).\n * Call recordMeta via plugin during the run, then call this with cypress.run() result.\n */\nexport interface CypressRunResultTest {\n title: string[];\n state?: string;\n displayError?: string;\n duration?: number;\n attempts?: Array<{\n state?: string;\n duration?: number;\n error?: { message?: string; stack?: string };\n }>;\n}\n\nexport interface CypressRunResultRun {\n spec?: { relative?: string; absolute?: string };\n tests?: CypressRunResultTest[];\n}\n\nexport interface CypressRunResult {\n runs?: CypressRunResultRun[];\n config?: { projectRoot?: string };\n}\n\nexport function buildRawRunFromCypressResult(\n result: CypressRunResult,\n options: StoryReporterOptions = {}\n): RawRun {\n const projectRoot = options.projectRoot ?? result.config?.projectRoot ?? process.cwd();\n const rawTestCases: RawTestCase[] = [];\n\n for (const run of result.runs ?? []) {\n const specRelative = run.spec?.relative ?? \"unknown\";\n for (const test of run.tests ?? []) {\n const titlePath = test.title ?? [];\n const meta = getMeta(specRelative, titlePath);\n if (!meta) continue;\n\n const lastAttempt = test.attempts?.length\n ? test.attempts[test.attempts.length - 1]\n : undefined;\n const state = lastAttempt?.state ?? test.state;\n const err = lastAttempt?.error ?? (test.displayError ? { message: test.displayError } : undefined);\n const duration = lastAttempt?.duration ?? test.duration;\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: specRelative,\n sourceLine: 1,\n status: mapCypressStateToRaw(state),\n durationMs: duration,\n error: err ? { message: err.message, stack: err.stack } : undefined,\n });\n }\n }\n\n const includeMetadata = options.markdown?.includeMetadata ?? true;\n return {\n testCases: rawTestCases,\n startedAtMs: undefined,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n };\n}\n\n/**\n * Generate reports from a RawRun (e.g. after buildRawRunFromCypressResult).\n */\nexport async function generateReportsFromRawRun(\n rawRun: RawRun,\n options: FormatterOptions\n): Promise<void> {\n if (rawRun.testCases.length === 0) return;\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(options);\n await generator.generate(canonicalRun);\n}\n\nexport default createReporter;\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n return null;\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n}\n"],"mappings":"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","names":["path"]}
|
|
1
|
+
{"version":3,"sources":["../src/reporter.ts","../src/store.ts"],"sourcesContent":["/**\n * Cypress/Mocha reporter for executable-stories.\n * Builds RawRun (formatters schema) from run results + stored story meta, then generates reports.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\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 RawStatus,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\nimport { getMeta, getAttachments, getAllMeta, getFeature, clearStore } from \"./store\";\n\n// Re-export types from formatters\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for CLI/binary */\n rawRunPath?: string;\n /** Spec file path (relative to projectRoot) for this run. If omitted, inferred from stored meta when possible. */\n specPath?: string;\n /** Project root. Defaults to process.cwd(). */\n projectRoot?: string;\n /**\n * Declare how much of each spec this run covered.\n *\n * Cypress has no built-in name filter — narrowing by title needs\n * @cypress/grep, which the reporter cannot observe — so unlike the Vitest,\n * Jest and Playwright adapters this cannot be detected. Left unset the run\n * states no scope, and consumers keep what it did not report rather than\n * retiring it on a guess.\n *\n * Set `\"filtered\"` when running under @cypress/grep. Set `\"full\"` only when\n * you know the run covered whole specs, since that is what permits a\n * scenario to be removed from the accumulated report.\n */\n runScope?: \"full\" | \"filtered\";\n}\n\n// ============================================================================\n// Mocha Runner / Test types (minimal for reporter)\n// ============================================================================\n\ninterface MochaSuite {\n title: string;\n suites: MochaSuite[];\n tests: MochaTest[];\n parent?: MochaSuite;\n}\n\ninterface MochaTest {\n title: string;\n state?: string;\n duration?: number;\n err?: Error & { message?: string; stack?: string };\n parent?: MochaSuite;\n /** Mocha marks both `it.skip(...)` and a bodyless `it(\"title\")` as pending. */\n pending?: boolean;\n /** Absent only when the test was declared with no callback at all. */\n fn?: unknown;\n}\n\n/**\n * A bodyless `it(\"title\")` is Mocha's way of writing down behaviour that does\n * not exist yet, which is what a planned scenario is. `it.skip(title, fn)` is\n * also pending but keeps its body: that means \"do not run this now\", a\n * different claim, so it stays a skip.\n */\nfunction isPlannedTest(test: MochaTest): boolean {\n return test.pending === true && test.fn === undefined;\n}\n\ninterface MochaRunner {\n suite: MochaSuite;\n startTime?: number;\n on?(event: string, callback: () => void): void;\n}\n\nfunction getTitlePath(test: MochaTest): string[] {\n const path: string[] = [];\n let current: MochaSuite | MochaTest | undefined = test;\n while (current) {\n const title = \"title\" in current ? current.title : \"\";\n if (title) path.unshift(title);\n current = \"parent\" in current ? current.parent : undefined;\n }\n return path;\n}\n\nfunction collectTests(suite: MochaSuite): MochaTest[] {\n const tests = [...suite.tests];\n for (const s of suite.suites) {\n tests.push(...collectTests(s));\n }\n return tests;\n}\n\nfunction mapCypressStateToRaw(state: string | undefined): RawStatus {\n switch (state) {\n case \"passed\":\n return \"pass\";\n case \"failed\":\n return \"fail\";\n case \"pending\":\n return \"skip\";\n default:\n return \"unknown\";\n }\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nconst DEFAULT_OPTIONS: Partial<StoryReporterOptions> = {\n projectRoot: process.cwd(),\n};\n\n/**\n * Create a Mocha reporter function that Cypress can use (--reporter path/to/reporter).\n * Cypress passes (runner, reporterOptions) with reporterOptions as the second argument directly.\n * Also supports options.reporterOptions for wrapped usage.\n */\nfunction createReporter(\n runner: MochaRunner,\n options: StoryReporterOptions | { reporterOptions?: StoryReporterOptions } = {}\n) {\n const reporterOpts =\n \"reporterOptions\" in options && options.reporterOptions\n ? options.reporterOptions\n : (options as StoryReporterOptions);\n const opts: StoryReporterOptions = { ...DEFAULT_OPTIONS, ...reporterOpts };\n const projectRoot = opts.projectRoot ?? process.cwd();\n const specPath = opts.specPath ?? \"unknown\";\n const startTime = runner.startTime ?? Date.now();\n\n runner.suite && runner.on?.(\"end\", () => {\n const tests = collectTests(runner.suite);\n const rawTestCases: RawTestCase[] = [];\n\n // If specPath not provided, infer from first stored meta (all stored entries are for this spec in a single-spec run)\n const effectiveSpecPath =\n specPath !== \"unknown\"\n ? specPath\n : (() => {\n const all = getAllMeta();\n return all.length > 0 ? all[0].specRelative : \"unknown\";\n })();\n\n // Bodyless tests never run story.init(), so they are collected separately\n // and only kept when the spec has real story tests to sit alongside.\n const plannedTests: MochaTest[] = [];\n\n for (const test of tests) {\n const titlePath = getTitlePath(test);\n const meta = getMeta(effectiveSpecPath, titlePath);\n if (!meta) {\n if (isPlannedTest(test)) plannedTests.push(test);\n continue; // only document tests that used story.init()\n }\n\n const status = mapCypressStateToRaw(test.state);\n\n // Map stored attachments to RawAttachment[]\n const storedAttachments = getAttachments(effectiveSpecPath, titlePath);\n const attachments: RawAttachment[] | undefined = storedAttachments?.map((a) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: typeof a.body === 'string' ? a.body : a.body instanceof Buffer ? a.body.toString('base64') : undefined,\n encoding: a.encoding ?? (a.body instanceof Buffer ? 'BASE64' : undefined),\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\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 rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status,\n durationMs: test.duration,\n error: test.err\n ? { message: test.err.message, stack: test.err.stack }\n : undefined,\n attachments: attachments && attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n if (rawTestCases.length === 0) {\n clearStore();\n return;\n }\n\n for (const test of plannedTests) {\n const titlePath = getTitlePath(test);\n const suitePath = titlePath.slice(0, -1);\n rawTestCases.push({\n title: test.title,\n titlePath,\n story: {\n scenario: test.title,\n steps: [],\n ...(suitePath.length > 0 ? { suitePath } : {}),\n },\n sourceFile: effectiveSpecPath,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n\n const includeMetadata = opts.markdown?.includeMetadata ?? true;\n const declaredFeature = getFeature(effectiveSpecPath);\n const rawRun: RawRun = {\n testCases: rawTestCases,\n ...(declaredFeature\n ? { features: [{ ...declaredFeature, sourceFile: effectiveSpecPath }] }\n : {}),\n startedAtMs: startTime,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n ...(opts.runScope ? { runScope: opts.runScope } : {}),\n };\n\n const rawRunPath = opts.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(absolutePath, JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2), \"utf8\");\n }\n\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(opts);\n generator.generate(canonicalRun).catch((err) => {\n console.error(\"Failed to generate reports:\", err);\n });\n\n clearStore();\n });\n}\n\n/**\n * Alternative: build RawRun from Cypress Module API results + stored meta.\n * Use this when not using the Mocha reporter (e.g. in after:run plugin).\n * Call recordMeta via plugin during the run, then call this with cypress.run() result.\n */\nexport interface CypressRunResultTest {\n title: string[];\n state?: string;\n displayError?: string;\n duration?: number;\n attempts?: Array<{\n state?: string;\n duration?: number;\n error?: { message?: string; stack?: string };\n }>;\n}\n\nexport interface CypressRunResultRun {\n spec?: { relative?: string; absolute?: string };\n tests?: CypressRunResultTest[];\n}\n\nexport interface CypressRunResult {\n runs?: CypressRunResultRun[];\n config?: { projectRoot?: string };\n}\n\nexport function buildRawRunFromCypressResult(\n result: CypressRunResult,\n options: StoryReporterOptions = {}\n): RawRun {\n const projectRoot = options.projectRoot ?? result.config?.projectRoot ?? process.cwd();\n const rawTestCases: RawTestCase[] = [];\n\n for (const run of result.runs ?? []) {\n const specRelative = run.spec?.relative ?? \"unknown\";\n for (const test of run.tests ?? []) {\n const titlePath = test.title ?? [];\n const meta = getMeta(specRelative, titlePath);\n if (!meta) continue;\n\n const lastAttempt = test.attempts?.length\n ? test.attempts[test.attempts.length - 1]\n : undefined;\n const state = lastAttempt?.state ?? test.state;\n const err = lastAttempt?.error ?? (test.displayError ? { message: test.displayError } : undefined);\n const duration = lastAttempt?.duration ?? test.duration;\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath ? [...meta.suitePath, meta.scenario] : [meta.scenario],\n story: meta,\n sourceFile: specRelative,\n sourceLine: 1,\n status: mapCypressStateToRaw(state),\n durationMs: duration,\n error: err ? { message: err.message, stack: err.stack } : undefined,\n });\n }\n }\n\n const includeMetadata = options.markdown?.includeMetadata ?? true;\n return {\n testCases: rawTestCases,\n startedAtMs: undefined,\n finishedAtMs: Date.now(),\n projectRoot,\n packageVersion: includeMetadata ? readPackageVersion(projectRoot) : undefined,\n gitSha: includeMetadata ? readGitSha(projectRoot) : undefined,\n ci: detectCI(),\n ...(options.runScope ? { runScope: options.runScope } : {}),\n };\n}\n\n/**\n * Generate reports from a RawRun (e.g. after buildRawRunFromCypressResult).\n */\nexport async function generateReportsFromRawRun(\n rawRun: RawRun,\n options: FormatterOptions\n): Promise<void> {\n if (rawRun.testCases.length === 0) return;\n const canonicalRun = canonicalizeRun(rawRun);\n const generator = new ReportGenerator(options);\n await generator.generate(canonicalRun);\n}\n\nexport default createReporter;\n","/**\n * Node-only store for story meta sent from the browser via cy.task.\n * Plugin writes; reporter reads. Keyed by spec + titlePath for merging with run results.\n */\n\nimport type { StoryMeta, ScopedAttachment, RecordMetaPayload, FeatureInput } from \"./types\";\n\nexport interface StoredMeta {\n specRelative: string;\n titlePath: string[];\n meta: StoryMeta;\n attachments?: ScopedAttachment[];\n}\n\nfunction key(specRelative: string, titlePath: string[]): string {\n return `${specRelative}\\0${titlePath.join(\"\\0\")}`;\n}\n\nconst store = new Map<string, StoredMeta>();\nconst features = new Map<string, FeatureInput>();\n\nexport function recordMeta(payload: RecordMetaPayload): null {\n store.set(key(payload.specRelative, payload.titlePath), {\n specRelative: payload.specRelative,\n titlePath: payload.titlePath,\n meta: payload.meta,\n attachments: payload.attachments,\n });\n if (payload.feature?.title) features.set(payload.specRelative, payload.feature);\n return null;\n}\n\n/** The feature declared by a spec, if it declared one. */\nexport function getFeature(specRelative: string): FeatureInput | undefined {\n return features.get(specRelative);\n}\n\nexport function getAttachments(specRelative: string, titlePath: string[]): ScopedAttachment[] | undefined {\n return store.get(key(specRelative, titlePath))?.attachments;\n}\n\nexport function getMeta(specRelative: string, titlePath: string[]): StoryMeta | undefined {\n return store.get(key(specRelative, titlePath))?.meta;\n}\n\nexport function getAllMeta(): StoredMeta[] {\n return Array.from(store.values());\n}\n\nexport function clearStore(): void {\n store.clear();\n features.clear();\n}\n"],"mappings":";AAKA,YAAY,QAAQ;AACpB,YAAY,UAAU;AAGtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAOK;;;ACPP,SAAS,IAAI,cAAsB,WAA6B;AAC9D,SAAO,GAAG,YAAY,KAAK,UAAU,KAAK,IAAI,CAAC;AACjD;AAEA,IAAM,QAAQ,oBAAI,IAAwB;AAC1C,IAAM,WAAW,oBAAI,IAA0B;AAcxC,SAAS,WAAW,cAAgD;AACzE,SAAO,SAAS,IAAI,YAAY;AAClC;AAEO,SAAS,eAAe,cAAsB,WAAqD;AACxG,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;AAEO,SAAS,QAAQ,cAAsB,WAA4C;AACxF,SAAO,MAAM,IAAI,IAAI,cAAc,SAAS,CAAC,GAAG;AAClD;AAEO,SAAS,aAA2B;AACzC,SAAO,MAAM,KAAK,MAAM,OAAO,CAAC;AAClC;AAEO,SAAS,aAAmB;AACjC,QAAM,MAAM;AACZ,WAAS,MAAM;AACjB;;;ADsCA,SAAS,cAAc,MAA0B;AAC/C,SAAO,KAAK,YAAY,QAAQ,KAAK,OAAO;AAC9C;AAQA,SAAS,aAAa,MAA2B;AAC/C,QAAMA,QAAiB,CAAC;AACxB,MAAI,UAA8C;AAClD,SAAO,SAAS;AACd,UAAM,QAAQ,WAAW,UAAU,QAAQ,QAAQ;AACnD,QAAI,MAAO,CAAAA,MAAK,QAAQ,KAAK;AAC7B,cAAU,YAAY,UAAU,QAAQ,SAAS;AAAA,EACnD;AACA,SAAOA;AACT;AAEA,SAAS,aAAa,OAAgC;AACpD,QAAM,QAAQ,CAAC,GAAG,MAAM,KAAK;AAC7B,aAAW,KAAK,MAAM,QAAQ;AAC5B,UAAM,KAAK,GAAG,aAAa,CAAC,CAAC;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAAsC;AAClE,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAMA,IAAM,kBAAiD;AAAA,EACrD,aAAa,QAAQ,IAAI;AAC3B;AAOA,SAAS,eACP,QACA,UAA6E,CAAC,GAC9E;AACA,QAAM,eACJ,qBAAqB,WAAW,QAAQ,kBACpC,QAAQ,kBACP;AACP,QAAM,OAA6B,EAAE,GAAG,iBAAiB,GAAG,aAAa;AACzE,QAAM,cAAc,KAAK,eAAe,QAAQ,IAAI;AACpD,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,YAAY,OAAO,aAAa,KAAK,IAAI;AAE/C,SAAO,SAAS,OAAO,KAAK,OAAO,MAAM;AACvC,UAAM,QAAQ,aAAa,OAAO,KAAK;AACvC,UAAM,eAA8B,CAAC;AAGrC,UAAM,oBACJ,aAAa,YACT,YACC,MAAM;AACL,YAAM,MAAM,WAAW;AACvB,aAAO,IAAI,SAAS,IAAI,IAAI,CAAC,EAAE,eAAe;AAAA,IAChD,GAAG;AAIT,UAAM,eAA4B,CAAC;AAEnC,eAAW,QAAQ,OAAO;AACxB,YAAM,YAAY,aAAa,IAAI;AACnC,YAAM,OAAO,QAAQ,mBAAmB,SAAS;AACjD,UAAI,CAAC,MAAM;AACT,YAAI,cAAc,IAAI,EAAG,cAAa,KAAK,IAAI;AAC/C;AAAA,MACF;AAEA,YAAM,SAAS,qBAAqB,KAAK,KAAK;AAG9C,YAAM,oBAAoB,eAAe,mBAAmB,SAAS;AACrE,YAAM,cAA2C,mBAAmB,IAAI,CAAC,OAAO;AAAA,QAC9E,MAAM,EAAE;AAAA,QACR,WAAW,EAAE;AAAA,QACb,MAAM,EAAE;AAAA,QACR,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,gBAAgB,SAAS,EAAE,KAAK,SAAS,QAAQ,IAAI;AAAA,QACnG,UAAU,EAAE,aAAa,EAAE,gBAAgB,SAAS,WAAW;AAAA,QAC/D,SAAS,EAAE;AAAA,QACX,UAAU,EAAE;AAAA,QACZ,WAAW,EAAE;AAAA,QACb,QAAQ,EAAE;AAAA,MACZ,EAAE;AAGF,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,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK,YAAY,CAAC,GAAG,KAAK,WAAW,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ;AAAA,QAC/E,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ;AAAA,QACA,YAAY,KAAK;AAAA,QACjB,OAAO,KAAK,MACR,EAAE,SAAS,KAAK,IAAI,SAAS,OAAO,KAAK,IAAI,MAAM,IACnD;AAAA,QACJ,aAAa,eAAe,YAAY,SAAS,IAAI,cAAc;AAAA,QACnE,YAAY,WAAW,SAAS,IAAI,aAAa;AAAA,QACjD,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B,iBAAW;AACX;AAAA,IACF;AAEA,eAAW,QAAQ,cAAc;AAC/B,YAAM,YAAY,aAAa,IAAI;AACnC,YAAM,YAAY,UAAU,MAAM,GAAG,EAAE;AACvC,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,OAAO;AAAA,UACL,UAAU,KAAK;AAAA,UACf,OAAO,CAAC;AAAA,UACR,GAAI,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,QAC9C;AAAA,QACA,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,UAAM,kBAAkB,KAAK,UAAU,mBAAmB;AAC1D,UAAM,kBAAkB,WAAW,iBAAiB;AACpD,UAAM,SAAiB;AAAA,MACrB,WAAW;AAAA,MACX,GAAI,kBACA,EAAE,UAAU,CAAC,EAAE,GAAG,iBAAiB,YAAY,kBAAkB,CAAC,EAAE,IACpE,CAAC;AAAA,MACL,aAAa;AAAA,MACb,cAAc,KAAK,IAAI;AAAA,MACvB;AAAA,MACA,gBAAgB,kBAAkB,mBAAmB,WAAW,IAAI;AAAA,MACpE,QAAQ,kBAAkB,WAAW,WAAW,IAAI;AAAA,MACpD,IAAI,SAAS;AAAA,MACb,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;AAAA,IACrD;AAEA,UAAM,aAAa,KAAK;AACxB,QAAI,YAAY;AACd,YAAM,eAAoB,gBAAW,UAAU,IAC3C,aACK,UAAK,aAAa,UAAU;AACrC,YAAM,MAAW,aAAQ,YAAY;AACrC,UAAI,CAAI,cAAW,GAAG,EAAG,CAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAC9D,MAAG,iBAAc,cAAc,KAAK,UAAU,EAAE,eAAe,GAAG,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG,MAAM;AAAA,IACjG;AAEA,UAAM,eAAe,gBAAgB,MAAM;AAC3C,UAAM,YAAY,IAAI,gBAAgB,IAAI;AAC1C,cAAU,SAAS,YAAY,EAAE,MAAM,CAAC,QAAQ;AAC9C,cAAQ,MAAM,+BAA+B,GAAG;AAAA,IAClD,CAAC;AAED,eAAW;AAAA,EACb,CAAC;AACH;AA6BO,SAAS,6BACd,QACA,UAAgC,CAAC,GACzB;AACR,QAAM,cAAc,QAAQ,eAAe,OAAO,QAAQ,eAAe,QAAQ,IAAI;AACrF,QAAM,eAA8B,CAAC;AAErC,aAAW,OAAO,OAAO,QAAQ,CAAC,GAAG;AACnC,UAAM,eAAe,IAAI,MAAM,YAAY;AAC3C,eAAW,QAAQ,IAAI,SAAS,CAAC,GAAG;AAClC,YAAM,YAAY,KAAK,SAAS,CAAC;AACjC,YAAM,OAAO,QAAQ,cAAc,SAAS;AAC5C,UAAI,CAAC,KAAM;AAEX,YAAM,cAAc,KAAK,UAAU,SAC/B,KAAK,SAAS,KAAK,SAAS,SAAS,CAAC,IACtC;AACJ,YAAM,QAAQ,aAAa,SAAS,KAAK;AACzC,YAAM,MAAM,aAAa,UAAU,KAAK,eAAe,EAAE,SAAS,KAAK,aAAa,IAAI;AACxF,YAAM,WAAW,aAAa,YAAY,KAAK;AAE/C,mBAAa,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK,YAAY,CAAC,GAAG,KAAK,WAAW,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ;AAAA,QAC/E,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ,qBAAqB,KAAK;AAAA,QAClC,YAAY;AAAA,QACZ,OAAO,MAAM,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,IAAI;AAAA,MAC5D,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,kBAAkB,QAAQ,UAAU,mBAAmB;AAC7D,SAAO;AAAA,IACL,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc,KAAK,IAAI;AAAA,IACvB;AAAA,IACA,gBAAgB,kBAAkB,mBAAmB,WAAW,IAAI;AAAA,IACpE,QAAQ,kBAAkB,WAAW,WAAW,IAAI;AAAA,IACpD,IAAI,SAAS;AAAA,IACb,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,EAC3D;AACF;AAKA,eAAsB,0BACpB,QACA,SACe;AACf,MAAI,OAAO,UAAU,WAAW,EAAG;AACnC,QAAM,eAAe,gBAAgB,MAAM;AAC3C,QAAM,YAAY,IAAI,gBAAgB,OAAO;AAC7C,QAAM,UAAU,SAAS,YAAY;AACvC;AAEA,IAAO,mBAAQ;","names":["path"]}
|
package/dist/support.cjs
CHANGED
|
@@ -289,7 +289,8 @@ function getAndClearMeta() {
|
|
|
289
289
|
titlePath: activeContext.titlePath,
|
|
290
290
|
meta: activeContext.meta,
|
|
291
291
|
attachments: activeContext.attachments.length > 0 ? activeContext.attachments : void 0,
|
|
292
|
-
otelSpans: activeContext.otelSpans
|
|
292
|
+
otelSpans: activeContext.otelSpans,
|
|
293
|
+
...declaredFeature ? { feature: declaredFeature } : {}
|
|
293
294
|
};
|
|
294
295
|
activeContext = null;
|
|
295
296
|
return payload;
|
|
@@ -302,7 +303,13 @@ function fn(keyword, text, body) {
|
|
|
302
303
|
keyword: resolvedKeyword,
|
|
303
304
|
text,
|
|
304
305
|
docs: [],
|
|
305
|
-
wrapped: true
|
|
306
|
+
wrapped: true,
|
|
307
|
+
// Wrapping a claim is the only signal Cypress can give that the step
|
|
308
|
+
// checked something: its assertions are queued commands, so there is no
|
|
309
|
+
// per-step counter to read. Setup steps arrange, so only a claim counts —
|
|
310
|
+
// tested against the keyword as written, since auto-And rewrites a repeated
|
|
311
|
+
// Then before the step is stored.
|
|
312
|
+
...keyword === "Then" ? { assertions: 1 } : {}
|
|
306
313
|
};
|
|
307
314
|
ctx.meta.steps.push(step);
|
|
308
315
|
ctx.currentStep = step;
|
|
@@ -333,6 +340,7 @@ function storyExpect(text, body) {
|
|
|
333
340
|
}
|
|
334
341
|
var story = {
|
|
335
342
|
init,
|
|
343
|
+
feature,
|
|
336
344
|
skip(title, body, options) {
|
|
337
345
|
return runScenario("skip", title, body, options);
|
|
338
346
|
},
|
|
@@ -485,6 +493,10 @@ var stepCallbacks = {
|
|
|
485
493
|
action: story.action,
|
|
486
494
|
verify: story.verify
|
|
487
495
|
};
|
|
496
|
+
var declaredFeature = null;
|
|
497
|
+
function feature(input) {
|
|
498
|
+
declaredFeature = input;
|
|
499
|
+
}
|
|
488
500
|
|
|
489
501
|
// src/support.ts
|
|
490
502
|
var TASK_NAME = "executableStories:recordMeta";
|
package/dist/support.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/story-api.ts","../src/support.ts"],"sourcesContent":["/**\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 StepMode,\n StoryMeta,\n StoryStep,\n DocEntry,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n ScopedAttachment,\n RecordMetaPayload,\n NormalizedTicket,\n TicketInput,\n KvOptions,\n JsonOptions,\n StateOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n VideoOptions,\n HtmlOptions,\n CustomOptions,\n} from './types';\nimport { buildHtmlDocEntry } from 'executable-stories-core/utils/doc-builders';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n NormalizedTicket,\n TicketInput,\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 otelSpans?: ReadonlyArray<Record<string, unknown>>;\n traceUrlTemplate?: string;\n traceDocAdded: boolean;\n}\n\ntype ScenarioBody = () => unknown;\ntype ItLike = ((title: string, body: ScenarioBody) => unknown) & {\n skip?: (title: string, body: ScenarioBody) => unknown;\n only?: (title: string, body: ScenarioBody) => unknown;\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(\n ticket: TicketInput | TicketInput[] | undefined,\n): NormalizedTicket[] | undefined {\n if (!ticket) return undefined;\n const arr = Array.isArray(ticket) ? ticket : [ticket];\n return arr.map((t) => (typeof t === 'string' ? { id: t } : t));\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.state) {\n warnIfStateLarge(docs.state.label, docs.state.value);\n entries.push({\n kind: 'state',\n label: docs.state.label,\n value: docs.state.value,\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.video) {\n entries.push({\n kind: 'video',\n path: docs.video.path,\n caption: docs.video.caption,\n poster: docs.video.poster,\n phase: 'runtime',\n });\n }\n if (docs.html) {\n entries.push(buildHtmlDocEntry(docs.html));\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\n\n/**\n * Warn (non-fatal) when a state snapshot serializes past 100KB — the entry is\n * still recorded, but a projection is usually what the storyboard needs.\n */\nfunction warnIfStateLarge(label: string | undefined, value: unknown): void {\n try {\n const len = JSON.stringify(value)?.length ?? 0;\n if (len > 100_000) {\n console.warn(\n `[executable-stories] state \"${label ?? ''}\" is ${Math.round(len / 1024)}KB — consider capturing a projection`,\n );\n }\n } catch {\n // Non-serializable value: recorded as-is, size unknown.\n }\n}\n\nfunction attachDoc(entry: DocEntry, children?: DocEntry[]): DocEntry {\n const ctx = getContext();\n if (children && children.length > 0) {\n entry.children = children;\n const childSet = new Set<DocEntry>(children);\n const filterDocs = (docs: DocEntry[]) => docs.filter((d) => !childSet.has(d));\n // Remove children from ALL containers (story-level + every step)\n ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);\n for (const step of ctx.meta.steps) {\n if (step.docs) step.docs = filterDocs(step.docs);\n }\n }\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 return entry;\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\nfunction extractTraceIdFromSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n): string | undefined {\n for (const span of spans) {\n const direct = span.traceId;\n if (typeof direct === 'string' && direct.length > 0) return direct;\n\n const context = span.context;\n if (context && typeof context === 'object' && context !== null) {\n const nested = (context as { traceId?: unknown }).traceId;\n if (typeof nested === 'string' && nested.length > 0) return nested;\n }\n }\n return undefined;\n}\n\n// ============================================================================\n// Step markers\n// ============================================================================\n\nexport type StepMarker = {\n (text: string, docs?: StoryDocs): void;\n (text: string, children: DocEntry[]): void;\n <T>(text: string, body: () => T): T;\n skip: StepMarker;\n only: StepMarker;\n todo: StepMarker;\n fails: StepMarker;\n concurrent: StepMarker;\n};\n\nfunction createStepMarker(keyword: StepKeyword, mode?: StepMode): StepMarker {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n const isChildrenArray = Array.isArray(docsOrBody);\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n let stepDocs: DocEntry[] = [];\n if (!isCallback && !isChildrenArray && docsOrBody) {\n stepDocs = convertStoryDocsToEntries(docsOrBody as StoryDocs);\n }\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: stepDocs,\n ...(mode ? { mode } : {}),\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n\n // Handle DocEntry[] children: attach as step docs and deduplicate from story-level\n if (isChildrenArray) {\n const children = docsOrBody as DocEntry[];\n if (children.length > 0) {\n const childSet = new Set<DocEntry>(children);\n // Deduplicate from story-level docs\n ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));\n // Deduplicate from step docs of earlier steps\n for (const prevStep of ctx.meta.steps) {\n if (prevStep !== step && prevStep.docs) {\n prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));\n }\n }\n step.docs = [...(step.docs ?? []), ...children];\n }\n return;\n }\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; return val; },\n (err) => { step.durationMs = performance.now() - start; throw err; },\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 const marker = stepMarker as StepMarker;\n marker.skip = mode ? marker : createStepMarker(keyword, 'skip');\n marker.only = mode ? marker : createStepMarker(keyword, 'only');\n marker.todo = mode ? marker : createStepMarker(keyword, 'todo');\n marker.fails = mode ? marker : createStepMarker(keyword, 'fails');\n marker.concurrent = mode ? marker : createStepMarker(keyword, 'concurrent');\n return marker;\n}\n\n// ============================================================================\n// story.init() - Cypress-specific\n// ============================================================================\n\nfunction init(options?: StoryOptions, scenarioOverride?: string): 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 = scenarioOverride ?? 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 covers: options?.covers,\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 traceUrlTemplate: options?.traceUrlTemplate,\n traceDocAdded: false,\n };\n}\n\nfunction runScenario(mode: 'normal' | 'skip' | 'only', title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n const globalIt = (globalThis as { it?: ItLike }).it;\n if (!globalIt) {\n throw new Error('Global it() is not available. Use story.skip/story.only inside Cypress spec files.');\n }\n\n const runner =\n mode === 'skip'\n ? globalIt.skip\n : mode === 'only'\n ? globalIt.only\n : globalIt;\n\n if (!runner) {\n throw new Error(`Global it.${mode}() is not available in this environment.`);\n }\n\n return runner(title, () => {\n init(options);\n return body();\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 otelSpans: activeContext.otelSpans,\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\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\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 skip(title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n return runScenario('skip', title, body, options);\n },\n only(title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n return runScenario('only', title, body, options);\n },\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, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'note', text, phase: 'runtime' }, children);\n },\n\n tag(name: string | string[], children?: DocEntry[]): DocEntry {\n const names = Array.isArray(name) ? name : [name];\n return attachDoc({ kind: 'tag', names, phase: 'runtime' }, children);\n },\n\n kv(options: KvOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' }, children);\n },\n\n json(options: JsonOptions, children?: DocEntry[]): DocEntry {\n const content = JSON.stringify(options.value, null, 2);\n return attachDoc({ kind: 'code', label: options.label, content, lang: 'json', phase: 'runtime' }, children);\n },\n\n /**\n * Capture what the world looks like at this step: a JSON-serializable data\n * snapshot that becomes a storyboard frame. Consecutive states with the same\n * label are diffed downstream, so snapshot the same projection at each step.\n */\n state(options: StateOptions, children?: DocEntry[]): DocEntry {\n warnIfStateLarge(options.label, options.value);\n return attachDoc({ kind: 'state', label: options.label, value: options.value, phase: 'runtime' }, children);\n },\n\n code(options: CodeOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' }, children);\n },\n\n table(options: TableOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' }, children);\n },\n\n link(options: LinkOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' }, children);\n },\n\n section(options: SectionOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' }, children);\n },\n\n mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' }, children);\n },\n\n screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' }, children);\n },\n\n video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'video', path: options.path, caption: options.caption, poster: options.poster, phase: 'runtime' }, children);\n },\n\n html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlDocEntry(options), children);\n },\n\n custom(options: CustomOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' }, children);\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 // OTel span attachment\n attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void {\n const ctx = getContext();\n ctx.otelSpans = spans;\n\n if (ctx.traceDocAdded) return;\n const traceId = extractTraceIdFromSpans(spans);\n if (!traceId) return;\n\n ctx.meta.meta = {\n ...ctx.meta.meta,\n otel: { traceId },\n };\n ctx.meta.docs ??= [];\n ctx.meta.docs.push({ kind: 'kv', label: 'Trace ID', value: traceId, phase: 'runtime' });\n\n const template = ctx.traceUrlTemplate;\n if (template) {\n const url = template.replace('{traceId}', traceId);\n ctx.meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n ctx.traceDocAdded = true;\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\nconst stepCallbacks = {\n given: story.given,\n when: story.when,\n then: story.then,\n and: story.and,\n but: story.but,\n arrange: story.arrange,\n act: story.act,\n assert: story.assert,\n setup: story.setup,\n context: story.context,\n execute: story.execute,\n action: story.action,\n verify: story.verify,\n};\n\nfunction docStory(title: string): void;\nfunction docStory(title: string, callback: (steps: typeof stepCallbacks) => void): void;\nfunction docStory(title: string, callback?: (steps: typeof stepCallbacks) => void): void {\n init(undefined, title);\n if (callback) callback(stepCallbacks);\n}\n\nexport const doc = {\n story: docStory,\n};\n\nexport type Story = typeof story;\n","/**\n * Cypress support file for executable-stories.\n * Register this in cypress/support/e2e.ts (or support file) so story meta\n * is sent to Node after each test for the reporter to consume.\n *\n * @example\n * In cypress/support/e2e.ts:\n * import 'executable-stories-cypress/support';\n */\n\nimport { getAndClearMeta } from \"./story-api\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\n\nafterEach(function () {\n const payload = getAndClearMeta();\n if (payload) {\n cy.task(TASK_NAME, payload);\n }\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/story-api.ts","../src/support.ts"],"sourcesContent":["/**\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 StepMode,\n StoryMeta,\n StoryStep,\n DocEntry,\n StoryDocs,\n StoryOptions,\n GlossaryTerm,\n AttachmentOptions,\n ScopedAttachment,\n RecordMetaPayload,\n NormalizedTicket,\n TicketInput,\n KvOptions,\n JsonOptions,\n StateOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n VideoOptions,\n HtmlOptions,\n CustomOptions,\n FeatureInput,\n} from './types';\nimport { buildHtmlDocEntry } from 'executable-stories-core/utils/doc-builders';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n GlossaryTerm,\n AttachmentOptions,\n NormalizedTicket,\n TicketInput,\n} from './types';\n\nexport type { RecordMetaPayload, FeatureInput } 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 otelSpans?: ReadonlyArray<Record<string, unknown>>;\n traceUrlTemplate?: string;\n traceDocAdded: boolean;\n}\n\ntype ScenarioBody = () => unknown;\ntype ItLike = ((title: string, body: ScenarioBody) => unknown) & {\n skip?: (title: string, body: ScenarioBody) => unknown;\n only?: (title: string, body: ScenarioBody) => unknown;\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(\n ticket: TicketInput | TicketInput[] | undefined,\n): NormalizedTicket[] | undefined {\n if (!ticket) return undefined;\n const arr = Array.isArray(ticket) ? ticket : [ticket];\n return arr.map((t) => (typeof t === 'string' ? { id: t } : t));\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.state) {\n warnIfStateLarge(docs.state.label, docs.state.value);\n entries.push({\n kind: 'state',\n label: docs.state.label,\n value: docs.state.value,\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.video) {\n entries.push({\n kind: 'video',\n path: docs.video.path,\n caption: docs.video.caption,\n poster: docs.video.poster,\n phase: 'runtime',\n });\n }\n if (docs.html) {\n entries.push(buildHtmlDocEntry(docs.html));\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\n\n/**\n * Warn (non-fatal) when a state snapshot serializes past 100KB — the entry is\n * still recorded, but a projection is usually what the storyboard needs.\n */\nfunction warnIfStateLarge(label: string | undefined, value: unknown): void {\n try {\n const len = JSON.stringify(value)?.length ?? 0;\n if (len > 100_000) {\n console.warn(\n `[executable-stories] state \"${label ?? ''}\" is ${Math.round(len / 1024)}KB — consider capturing a projection`,\n );\n }\n } catch {\n // Non-serializable value: recorded as-is, size unknown.\n }\n}\n\nfunction attachDoc(entry: DocEntry, children?: DocEntry[]): DocEntry {\n const ctx = getContext();\n if (children && children.length > 0) {\n entry.children = children;\n const childSet = new Set<DocEntry>(children);\n const filterDocs = (docs: DocEntry[]) => docs.filter((d) => !childSet.has(d));\n // Remove children from ALL containers (story-level + every step)\n ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);\n for (const step of ctx.meta.steps) {\n if (step.docs) step.docs = filterDocs(step.docs);\n }\n }\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 return entry;\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\nfunction extractTraceIdFromSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n): string | undefined {\n for (const span of spans) {\n const direct = span.traceId;\n if (typeof direct === 'string' && direct.length > 0) return direct;\n\n const context = span.context;\n if (context && typeof context === 'object' && context !== null) {\n const nested = (context as { traceId?: unknown }).traceId;\n if (typeof nested === 'string' && nested.length > 0) return nested;\n }\n }\n return undefined;\n}\n\n// ============================================================================\n// Step markers\n// ============================================================================\n\nexport type StepMarker = {\n (text: string, docs?: StoryDocs): void;\n (text: string, children: DocEntry[]): void;\n <T>(text: string, body: () => T): T;\n skip: StepMarker;\n only: StepMarker;\n todo: StepMarker;\n fails: StepMarker;\n concurrent: StepMarker;\n};\n\nfunction createStepMarker(keyword: StepKeyword, mode?: StepMode): StepMarker {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n const isChildrenArray = Array.isArray(docsOrBody);\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n let stepDocs: DocEntry[] = [];\n if (!isCallback && !isChildrenArray && docsOrBody) {\n stepDocs = convertStoryDocsToEntries(docsOrBody as StoryDocs);\n }\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: stepDocs,\n ...(mode ? { mode } : {}),\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n\n // Handle DocEntry[] children: attach as step docs and deduplicate from story-level\n if (isChildrenArray) {\n const children = docsOrBody as DocEntry[];\n if (children.length > 0) {\n const childSet = new Set<DocEntry>(children);\n // Deduplicate from story-level docs\n ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));\n // Deduplicate from step docs of earlier steps\n for (const prevStep of ctx.meta.steps) {\n if (prevStep !== step && prevStep.docs) {\n prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));\n }\n }\n step.docs = [...(step.docs ?? []), ...children];\n }\n return;\n }\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; return val; },\n (err) => { step.durationMs = performance.now() - start; throw err; },\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 const marker = stepMarker as StepMarker;\n marker.skip = mode ? marker : createStepMarker(keyword, 'skip');\n marker.only = mode ? marker : createStepMarker(keyword, 'only');\n marker.todo = mode ? marker : createStepMarker(keyword, 'todo');\n marker.fails = mode ? marker : createStepMarker(keyword, 'fails');\n marker.concurrent = mode ? marker : createStepMarker(keyword, 'concurrent');\n return marker;\n}\n\n// ============================================================================\n// story.init() - Cypress-specific\n// ============================================================================\n\nfunction init(options?: StoryOptions, scenarioOverride?: string): 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 = scenarioOverride ?? 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 covers: options?.covers,\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 traceUrlTemplate: options?.traceUrlTemplate,\n traceDocAdded: false,\n };\n}\n\nfunction runScenario(mode: 'normal' | 'skip' | 'only', title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n const globalIt = (globalThis as { it?: ItLike }).it;\n if (!globalIt) {\n throw new Error('Global it() is not available. Use story.skip/story.only inside Cypress spec files.');\n }\n\n const runner =\n mode === 'skip'\n ? globalIt.skip\n : mode === 'only'\n ? globalIt.only\n : globalIt;\n\n if (!runner) {\n throw new Error(`Global it.${mode}() is not available in this environment.`);\n }\n\n return runner(title, () => {\n init(options);\n return body();\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 otelSpans: activeContext.otelSpans,\n ...(declaredFeature ? { feature: declaredFeature } : {}),\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\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: [],\n wrapped: true,\n // Wrapping a claim is the only signal Cypress can give that the step\n // checked something: its assertions are queued commands, so there is no\n // per-step counter to read. Setup steps arrange, so only a claim counts —\n // tested against the keyword as written, since auto-And rewrites a repeated\n // Then before the step is stored.\n ...(keyword === 'Then' ? { assertions: 1 } : {}),\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 feature,\n skip(title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n return runScenario('skip', title, body, options);\n },\n only(title: string, body: ScenarioBody, options?: StoryOptions): unknown {\n return runScenario('only', title, body, options);\n },\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, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'note', text, phase: 'runtime' }, children);\n },\n\n tag(name: string | string[], children?: DocEntry[]): DocEntry {\n const names = Array.isArray(name) ? name : [name];\n return attachDoc({ kind: 'tag', names, phase: 'runtime' }, children);\n },\n\n kv(options: KvOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' }, children);\n },\n\n json(options: JsonOptions, children?: DocEntry[]): DocEntry {\n const content = JSON.stringify(options.value, null, 2);\n return attachDoc({ kind: 'code', label: options.label, content, lang: 'json', phase: 'runtime' }, children);\n },\n\n /**\n * Capture what the world looks like at this step: a JSON-serializable data\n * snapshot that becomes a storyboard frame. Consecutive states with the same\n * label are diffed downstream, so snapshot the same projection at each step.\n */\n state(options: StateOptions, children?: DocEntry[]): DocEntry {\n warnIfStateLarge(options.label, options.value);\n return attachDoc({ kind: 'state', label: options.label, value: options.value, phase: 'runtime' }, children);\n },\n\n code(options: CodeOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' }, children);\n },\n\n table(options: TableOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' }, children);\n },\n\n link(options: LinkOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' }, children);\n },\n\n section(options: SectionOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' }, children);\n },\n\n mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' }, children);\n },\n\n screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' }, children);\n },\n\n video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'video', path: options.path, caption: options.caption, poster: options.poster, phase: 'runtime' }, children);\n },\n\n html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlDocEntry(options), children);\n },\n\n custom(options: CustomOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' }, children);\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 // OTel span attachment\n attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void {\n const ctx = getContext();\n ctx.otelSpans = spans;\n\n if (ctx.traceDocAdded) return;\n const traceId = extractTraceIdFromSpans(spans);\n if (!traceId) return;\n\n ctx.meta.meta = {\n ...ctx.meta.meta,\n otel: { traceId },\n };\n ctx.meta.docs ??= [];\n ctx.meta.docs.push({ kind: 'kv', label: 'Trace ID', value: traceId, phase: 'runtime' });\n\n const template = ctx.traceUrlTemplate;\n if (template) {\n const url = template.replace('{traceId}', traceId);\n ctx.meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n ctx.traceDocAdded = true;\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\nconst stepCallbacks = {\n given: story.given,\n when: story.when,\n then: story.then,\n and: story.and,\n but: story.but,\n arrange: story.arrange,\n act: story.act,\n assert: story.assert,\n setup: story.setup,\n context: story.context,\n execute: story.execute,\n action: story.action,\n verify: story.verify,\n};\n\nfunction docStory(title: string): void;\nfunction docStory(title: string, callback: (steps: typeof stepCallbacks) => void): void;\nfunction docStory(title: string, callback?: (steps: typeof stepCallbacks) => void): void {\n init(undefined, title);\n if (callback) callback(stepCallbacks);\n}\n\nexport const doc = {\n story: docStory,\n};\n\nexport type Story = typeof story;\n\n/**\n * The declaration for the spec currently being loaded.\n *\n * Cypress runs one spec per browser context, so the last declaration seen is\n * the one that belongs to the tests registering after it.\n */\nlet declaredFeature: FeatureInput | null = null;\n\n/**\n * Declare what the spec's scenarios are for, before any of them run.\n *\n * Scenarios say what the system does. This says why the feature exists and who\n * it serves, so a reader meets the intent before the examples. Call it once per\n * spec file, at module scope or at the top of the outermost `describe`.\n */\nfunction feature(input: FeatureInput): void {\n declaredFeature = input;\n}\n","/**\n * Cypress support file for executable-stories.\n * Register this in cypress/support/e2e.ts (or support file) so story meta\n * is sent to Node after each test for the reporter to consume.\n *\n * @example\n * In cypress/support/e2e.ts:\n * import 'executable-stories-cypress/support';\n */\n\nimport { getAndClearMeta } from \"./story-api\";\n\nconst TASK_NAME = \"executableStories:recordMeta\";\n\nafterEach(function () {\n const payload = getAndClearMeta();\n if (payload) {\n cy.task(TASK_NAME, payload);\n }\n});\n"],"mappings":"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","names":[]}
|