executable-stories-playwright 3.1.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -66,6 +66,8 @@ interface StoryOptions {
66
66
  tags?: string[];
67
67
  ticket?: string | string[];
68
68
  meta?: Record<string, unknown>;
69
+ /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */
70
+ traceUrlTemplate?: string;
69
71
  }
70
72
  /** Options for story.attach(). */
71
73
  interface AttachmentOptions {
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // src/story-api.ts
2
+ import { createRequire } from "module";
3
+ import {
4
+ tryGetActiveOtelContext,
5
+ resolveTraceUrl
6
+ } from "executable-stories-formatters";
2
7
  var activeContext = null;
3
8
  var activeTestInfo = null;
4
9
  var sourceOrderCounter = 0;
@@ -139,6 +144,32 @@ function init(testInfo, options) {
139
144
  meta: options?.meta,
140
145
  sourceOrder: sourceOrderCounter++
141
146
  };
147
+ const otelCtx = tryGetActiveOtelContext();
148
+ if (otelCtx) {
149
+ meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };
150
+ meta.docs = meta.docs ?? [];
151
+ meta.docs.push({ kind: "kv", label: "Trace ID", value: otelCtx.traceId, phase: "runtime" });
152
+ const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;
153
+ const url = resolveTraceUrl(template, otelCtx.traceId);
154
+ if (url) {
155
+ meta.docs.push({ kind: "link", label: "View Trace", url, phase: "runtime" });
156
+ }
157
+ try {
158
+ const reqUrl = import.meta.url ?? (typeof __filename !== "undefined" ? `file://${__filename}` : void 0);
159
+ const req = createRequire(reqUrl);
160
+ const api = req("@opentelemetry/api");
161
+ const span = api.trace?.getActiveSpan?.();
162
+ if (span) {
163
+ span.setAttribute("story.scenario", testInfo.title);
164
+ if (options?.tags?.length) span.setAttribute("story.tags", options.tags);
165
+ if (options?.ticket) {
166
+ const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];
167
+ span.setAttribute("story.tickets", tickets);
168
+ }
169
+ }
170
+ } catch {
171
+ }
172
+ }
142
173
  testInfo.annotations.push({
143
174
  type: "story-meta",
144
175
  description: JSON.stringify(meta)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/story-api.ts"],"sourcesContent":["/**\n * Playwright story.* API for executable-stories.\n *\n * Uses native Playwright test() with opt-in documentation:\n *\n * @example\n * ```ts\n * import { test, expect } from '@playwright/test';\n * import { story } from 'executable-stories-playwright';\n *\n * test.describe('Calculator', () => {\n * test('adds two numbers', async ({ page }, testInfo) => {\n * story.init(testInfo);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport type { TestInfo } from '@playwright/test';\nimport type {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n} from './types';\nimport type {\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n ScopedAttachment,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n} from './types';\n\n// ============================================================================\n// Internal types\n// ============================================================================\n\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n meta: StoryMeta;\n currentStep: StoryStep | null;\n stepCounter: number;\n attachments: ScopedAttachment[];\n activeTimers: Map<number, TimerEntry>;\n timerCounter: number;\n}\n\n// ============================================================================\n// Playwright-specific context\n// ============================================================================\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Reference to testInfo for attaching metadata */\nlet activeTestInfo: TestInfo | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init(testInfo) must be called first. Use: test('name', async ({ page }, testInfo) => { story.init(testInfo); ... });\",\n );\n }\n return activeContext;\n}\n\n// ============================================================================\n// Helper functions (inlined from core)\n// ============================================================================\n\nfunction normalizeTickets(ticket: string | string[] | undefined): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\n}\n\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: 'tag', names, phase: 'runtime' });\n }\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: 'kv', label, value, phase: 'runtime' });\n }\n }\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\nfunction attachDoc(entry: DocEntry): void {\n const ctx = getContext();\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n}\n\n// ============================================================================\n// Suite path extraction\n// ============================================================================\n\n/**\n * Extract the suite path from testInfo.titlePath.\n * Playwright's titlePath includes: [projectName, ...describeTitles, testTitle]\n * We want just the describe titles (excluding project and test name).\n */\nfunction extractSuitePath(testInfo: TestInfo): string[] | undefined {\n const titlePath = testInfo.titlePath;\n if (titlePath.length <= 2) {\n return undefined;\n }\n const suitePath = titlePath.slice(1, -1);\n return suitePath.length > 0 ? suitePath : undefined;\n}\n\n// ============================================================================\n// Step markers\n// ============================================================================\n\nfunction createStepMarker(keyword: StepKeyword) {\n return function stepMarker(text: string, docs?: StoryDocs): void {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: docs ? convertStoryDocsToEntries(docs) : [],\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n };\n}\n\n// ============================================================================\n// story.init() - Playwright-specific\n// ============================================================================\n\nfunction init(testInfo: TestInfo, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: testInfo.title,\n steps: [],\n suitePath: extractSuitePath(testInfo),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n testInfo.annotations.push({\n type: 'story-meta',\n description: JSON.stringify(meta),\n });\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n activeTestInfo = testInfo;\n}\n\n/**\n * Update the story-meta annotation on testInfo with the current meta (including steps).\n * Called after each step/doc so the reporter sees the full story in onTestEnd.\n */\nfunction syncAnnotationToTest(): void {\n if (!activeTestInfo || !activeContext) return;\n const annotation = activeTestInfo.annotations.find(\n (a) => a.type === 'story-meta',\n );\n if (annotation) {\n annotation.description = JSON.stringify(activeContext.meta);\n }\n}\n\n// ============================================================================\n// story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function as a step with timing and error capture.\n * Records the step with `wrapped: true` and `durationMs`.\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: [],\n wrapped: true,\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n\n const start = performance.now();\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step with timing and error capture.\n * Shorthand for `story.fn('Then', text, body)`.\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Playwright-specific attach\n// ============================================================================\n\nfunction playwrightAttach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepId: ctx.currentStep?.id,\n });\n syncAnnotationToTest();\n\n if (activeTestInfo) {\n const attachOptions: { name: string; contentType: string; path?: string; body?: string | Buffer } = {\n name: options.name,\n contentType: options.mediaType,\n };\n if (options.path) attachOptions.path = options.path;\n if (options.body) attachOptions.body = options.body;\n activeTestInfo.attach(options.name, attachOptions);\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\nexport const story = {\n init,\n\n // BDD step markers\n given: createStepMarker('Given'),\n when: createStepMarker('When'),\n then: createStepMarker('Then'),\n and: createStepMarker('And'),\n but: createStepMarker('But'),\n\n // AAA pattern aliases\n arrange: createStepMarker('Given'),\n act: createStepMarker('When'),\n assert: createStepMarker('Then'),\n\n // Additional aliases\n setup: createStepMarker('Given'),\n context: createStepMarker('Given'),\n execute: createStepMarker('When'),\n action: createStepMarker('When'),\n verify: createStepMarker('Then'),\n\n // Standalone doc methods\n note(text: string): void {\n attachDoc({ kind: 'note', text, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n kv(options: KvOptions): void {\n attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n json(options: JsonOptions): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({ kind: 'code', label: options.label, content, lang: 'json', phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n code(options: CodeOptions): void {\n attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n table(options: TableOptions): void {\n attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n link(options: LinkOptions): void {\n attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n section(options: SectionOptions): void {\n attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n mermaid(options: MermaidOptions): void {\n attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n screenshot(options: ScreenshotOptions): void {\n attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n custom(options: CustomOptions): void {\n attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n // Attachments\n attach: playwrightAttach,\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n syncAnnotationToTest();\n return token;\n },\n\n endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n }\n syncAnnotationToTest();\n },\n\n // Step wrappers\n fn,\n expect: storyExpect,\n};\n\nexport type Story = typeof story;\n"],"mappings":";AAuFA,IAAI,gBAAqC;AAGzC,IAAI,iBAAkC;AAGtC,IAAI,qBAAqB;AAKzB,SAAS,aAA2B;AAClC,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,iBAAiB,QAA6D;AACrF,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AACjD;AAEA,SAAS,0BAA0B,MAA6B;AAC9D,QAAM,UAAsB,CAAC;AAE7B,MAAI,KAAK,MAAM;AACb,YAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,MAAM,OAAO,UAAU,CAAC;AAAA,EAClE;AACA,MAAI,KAAK,KAAK;AACZ,UAAM,QAAQ,MAAM,QAAQ,KAAK,GAAG,IAAI,KAAK,MAAM,CAAC,KAAK,GAAG;AAC5D,YAAQ,KAAK,EAAE,MAAM,OAAO,OAAO,OAAO,UAAU,CAAC;AAAA,EACvD;AACA,MAAI,KAAK,IAAI;AACX,eAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,KAAK,EAAE,GAAG;AACpD,cAAQ,KAAK,EAAE,MAAM,MAAM,OAAO,OAAO,OAAO,UAAU,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,MAAI,KAAK,MAAM;AACb,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK,KAAK;AAAA,MACjB,SAAS,KAAK,KAAK;AAAA,MACnB,MAAM,KAAK,KAAK;AAAA,MAChB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,MAAM;AACb,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK,KAAK;AAAA,MACjB,SAAS,KAAK,UAAU,KAAK,KAAK,OAAO,MAAM,CAAC;AAAA,MAChD,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,OAAO;AACd,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK,MAAM;AAAA,MAClB,SAAS,KAAK,MAAM;AAAA,MACpB,MAAM,KAAK,MAAM;AAAA,MACjB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,MAAM;AACb,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK,KAAK;AAAA,MACjB,KAAK,KAAK,KAAK;AAAA,MACf,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,SAAS;AAChB,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,OAAO,KAAK,QAAQ;AAAA,MACpB,UAAU,KAAK,QAAQ;AAAA,MACvB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,SAAS;AAChB,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,KAAK,QAAQ;AAAA,MACnB,OAAO,KAAK,QAAQ;AAAA,MACpB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,YAAY;AACnB,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,KAAK,WAAW;AAAA,MACtB,KAAK,KAAK,WAAW;AAAA,MACrB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,QAAQ;AACf,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,KAAK,OAAO;AAAA,MAClB,MAAM,KAAK,OAAO;AAAA,MAClB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,OAAuB;AACxC,QAAM,MAAM,WAAW;AACvB,MAAI,IAAI,aAAa;AACnB,QAAI,YAAY,SAAS,CAAC;AAC1B,QAAI,YAAY,KAAK,KAAK,KAAK;AAAA,EACjC,OAAO;AACL,QAAI,KAAK,SAAS,CAAC;AACnB,QAAI,KAAK,KAAK,KAAK,KAAK;AAAA,EAC1B;AACF;AAWA,SAAS,iBAAiB,UAA0C;AAClE,QAAM,YAAY,SAAS;AAC3B,MAAI,UAAU,UAAU,GAAG;AACzB,WAAO;AAAA,EACT;AACA,QAAM,YAAY,UAAU,MAAM,GAAG,EAAE;AACvC,SAAO,UAAU,SAAS,IAAI,YAAY;AAC5C;AAMA,SAAS,iBAAiB,SAAsB;AAC9C,SAAO,SAAS,WAAW,MAAc,MAAwB;AAC/D,UAAM,MAAM,WAAW;AACvB,UAAM,OAAkB;AAAA,MACtB,IAAI,QAAQ,IAAI,aAAa;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM,OAAO,0BAA0B,IAAI,IAAI,CAAC;AAAA,IAClD;AACA,QAAI,KAAK,MAAM,KAAK,IAAI;AACxB,QAAI,cAAc;AAClB,yBAAqB;AAAA,EACvB;AACF;AAMA,SAAS,KAAK,UAAoB,SAA8B;AAC9D,QAAM,OAAkB;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,OAAO,CAAC;AAAA,IACR,WAAW,iBAAiB,QAAQ;AAAA,IACpC,MAAM,SAAS;AAAA,IACf,SAAS,iBAAiB,SAAS,MAAM;AAAA,IACzC,MAAM,SAAS;AAAA,IACf,aAAa;AAAA,EACf;AAEA,WAAS,YAAY,KAAK;AAAA,IACxB,MAAM;AAAA,IACN,aAAa,KAAK,UAAU,IAAI;AAAA,EAClC,CAAC;AAED,kBAAgB;AAAA,IACd;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAa,CAAC;AAAA,IACd,cAAc,oBAAI,IAAI;AAAA,IACtB,cAAc;AAAA,EAChB;AACA,mBAAiB;AACnB;AAMA,SAAS,uBAA6B;AACpC,MAAI,CAAC,kBAAkB,CAAC,cAAe;AACvC,QAAM,aAAa,eAAe,YAAY;AAAA,IAC5C,CAAC,MAAM,EAAE,SAAS;AAAA,EACpB;AACA,MAAI,YAAY;AACd,eAAW,cAAc,KAAK,UAAU,cAAc,IAAI;AAAA,EAC5D;AACF;AAUA,SAAS,GAAM,SAAsB,MAAc,MAAkB;AACnE,QAAM,MAAM,WAAW;AACvB,QAAM,OAAkB;AAAA,IACtB,IAAI,QAAQ,IAAI,aAAa;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,MAAM,CAAC;AAAA,IACP,SAAS;AAAA,EACX;AACA,MAAI,KAAK,MAAM,KAAK,IAAI;AACxB,MAAI,cAAc;AAClB,uBAAqB;AAErB,QAAM,QAAQ,YAAY,IAAI;AAC9B,MAAI;AACF,UAAM,SAAS,KAAK;AACpB,QAAI,kBAAkB,SAAS;AAC7B,aAAO,OAAO;AAAA,QACZ,CAAC,QAAQ;AACP,eAAK,aAAa,YAAY,IAAI,IAAI;AACtC,+BAAqB;AACrB,iBAAO;AAAA,QACT;AAAA,QACA,CAAC,QAAQ;AACP,eAAK,aAAa,YAAY,IAAI,IAAI;AACtC,+BAAqB;AACrB,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,aAAa,YAAY,IAAI,IAAI;AACtC,yBAAqB;AACrB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,SAAK,aAAa,YAAY,IAAI,IAAI;AACtC,yBAAqB;AACrB,UAAM;AAAA,EACR;AACF;AAMA,SAAS,YAAe,MAAc,MAAkB;AACtD,SAAO,GAAG,QAAQ,MAAM,IAAI;AAC9B;AAMA,SAAS,iBAAiB,SAAkC;AAC1D,QAAM,MAAM,WAAW;AACvB,QAAM,YAAY,IAAI,cAClB,IAAI,KAAK,MAAM,QAAQ,IAAI,WAAW,IACtC;AACJ,MAAI,YAAY,KAAK;AAAA,IACnB,GAAG;AAAA,IACH,QAAQ,IAAI,aAAa;AAAA,EAC3B,CAAC;AACD,uBAAqB;AAErB,MAAI,gBAAgB;AAClB,UAAM,gBAA8F;AAAA,MAClG,MAAM,QAAQ;AAAA,MACd,aAAa,QAAQ;AAAA,IACvB;AACA,QAAI,QAAQ,KAAM,eAAc,OAAO,QAAQ;AAC/C,QAAI,QAAQ,KAAM,eAAc,OAAO,QAAQ;AAC/C,mBAAe,OAAO,QAAQ,MAAM,aAAa;AAAA,EACnD;AACF;AAMO,IAAM,QAAQ;AAAA,EACnB;AAAA;AAAA,EAGA,OAAO,iBAAiB,OAAO;AAAA,EAC/B,MAAM,iBAAiB,MAAM;AAAA,EAC7B,MAAM,iBAAiB,MAAM;AAAA,EAC7B,KAAK,iBAAiB,KAAK;AAAA,EAC3B,KAAK,iBAAiB,KAAK;AAAA;AAAA,EAG3B,SAAS,iBAAiB,OAAO;AAAA,EACjC,KAAK,iBAAiB,MAAM;AAAA,EAC5B,QAAQ,iBAAiB,MAAM;AAAA;AAAA,EAG/B,OAAO,iBAAiB,OAAO;AAAA,EAC/B,SAAS,iBAAiB,OAAO;AAAA,EACjC,SAAS,iBAAiB,MAAM;AAAA,EAChC,QAAQ,iBAAiB,MAAM;AAAA,EAC/B,QAAQ,iBAAiB,MAAM;AAAA;AAAA,EAG/B,KAAK,MAAoB;AACvB,cAAU,EAAE,MAAM,QAAQ,MAAM,OAAO,UAAU,CAAC;AAClD,yBAAqB;AAAA,EACvB;AAAA,EAEA,IAAI,MAA+B;AACjC,UAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,cAAU,EAAE,MAAM,OAAO,OAAO,OAAO,UAAU,CAAC;AAClD,yBAAqB;AAAA,EACvB;AAAA,EAEA,GAAG,SAA0B;AAC3B,cAAU,EAAE,MAAM,MAAM,OAAO,QAAQ,OAAO,OAAO,QAAQ,OAAO,OAAO,UAAU,CAAC;AACtF,yBAAqB;AAAA,EACvB;AAAA,EAEA,KAAK,SAA4B;AAC/B,UAAM,UAAU,KAAK,UAAU,QAAQ,OAAO,MAAM,CAAC;AACrD,cAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,SAAS,MAAM,QAAQ,OAAO,UAAU,CAAC;AACzF,yBAAqB;AAAA,EACvB;AAAA,EAEA,KAAK,SAA4B;AAC/B,cAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ,MAAM,OAAO,UAAU,CAAC;AAChH,yBAAqB;AAAA,EACvB;AAAA,EAEA,MAAM,SAA6B;AACjC,cAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ,MAAM,OAAO,UAAU,CAAC;AACjH,yBAAqB;AAAA,EACvB;AAAA,EAEA,KAAK,SAA4B;AAC/B,cAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,KAAK,QAAQ,KAAK,OAAO,UAAU,CAAC;AACpF,yBAAqB;AAAA,EACvB;AAAA,EAEA,QAAQ,SAA+B;AACrC,cAAU,EAAE,MAAM,WAAW,OAAO,QAAQ,OAAO,UAAU,QAAQ,UAAU,OAAO,UAAU,CAAC;AACjG,yBAAqB;AAAA,EACvB;AAAA,EAEA,QAAQ,SAA+B;AACrC,cAAU,EAAE,MAAM,WAAW,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,OAAO,UAAU,CAAC;AACzF,yBAAqB;AAAA,EACvB;AAAA,EAEA,WAAW,SAAkC;AAC3C,cAAU,EAAE,MAAM,cAAc,MAAM,QAAQ,MAAM,KAAK,QAAQ,KAAK,OAAO,UAAU,CAAC;AACxF,yBAAqB;AAAA,EACvB;AAAA,EAEA,OAAO,SAA8B;AACnC,cAAU,EAAE,MAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,QAAQ,MAAM,OAAO,UAAU,CAAC;AACtF,yBAAqB;AAAA,EACvB;AAAA;AAAA,EAGA,QAAQ;AAAA;AAAA,EAGR,aAAqB;AACnB,UAAM,MAAM,WAAW;AACvB,UAAM,QAAQ,IAAI;AAClB,UAAM,YAAY,IAAI,cAClB,IAAI,KAAK,MAAM,QAAQ,IAAI,WAAW,IACtC;AACJ,QAAI,aAAa,IAAI,OAAO;AAAA,MAC1B,OAAO,YAAY,IAAI;AAAA,MACvB,WAAW,cAAc,UAAa,aAAa,IAAI,YAAY;AAAA,MACnE,QAAQ,IAAI,aAAa;AAAA,MACzB,UAAU;AAAA,IACZ,CAAC;AACD,yBAAqB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,OAAqB;AAC5B,UAAM,MAAM,WAAW;AACvB,UAAM,QAAQ,IAAI,aAAa,IAAI,KAAK;AACxC,QAAI,CAAC,SAAS,MAAM,SAAU;AAE9B,UAAM,WAAW;AACjB,UAAM,aAAa,YAAY,IAAI,IAAI,MAAM;AAE7C,QAAI;AACJ,QAAI,MAAM,QAAQ;AAChB,aAAO,IAAI,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,MAAM;AAAA,IACzD;AACA,QAAI,CAAC,QAAQ,MAAM,cAAc,QAAW;AAC1C,aAAO,IAAI,KAAK,MAAM,MAAM,SAAS;AAAA,IACvC;AAEA,QAAI,MAAM;AACR,WAAK,aAAa;AAAA,IACpB;AACA,yBAAqB;AAAA,EACvB;AAAA;AAAA,EAGA;AAAA,EACA,QAAQ;AACV;","names":[]}
1
+ {"version":3,"sources":["../src/story-api.ts"],"sourcesContent":["/**\n * Playwright story.* API for executable-stories.\n *\n * Uses native Playwright test() with opt-in documentation:\n *\n * @example\n * ```ts\n * import { test, expect } from '@playwright/test';\n * import { story } from 'executable-stories-playwright';\n *\n * test.describe('Calculator', () => {\n * test('adds two numbers', async ({ page }, testInfo) => {\n * story.init(testInfo);\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport { createRequire } from 'node:module';\nimport type { TestInfo } from '@playwright/test';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n} from 'executable-stories-formatters';\nimport type {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n} from './types';\nimport type {\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n ScopedAttachment,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n} from './types';\n\n// ============================================================================\n// Internal types\n// ============================================================================\n\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n meta: StoryMeta;\n currentStep: StoryStep | null;\n stepCounter: number;\n attachments: ScopedAttachment[];\n activeTimers: Map<number, TimerEntry>;\n timerCounter: number;\n}\n\n// ============================================================================\n// Playwright-specific context\n// ============================================================================\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Reference to testInfo for attaching metadata */\nlet activeTestInfo: TestInfo | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init(testInfo) must be called first. Use: test('name', async ({ page }, testInfo) => { story.init(testInfo); ... });\",\n );\n }\n return activeContext;\n}\n\n// ============================================================================\n// Helper functions (inlined from core)\n// ============================================================================\n\nfunction normalizeTickets(ticket: string | string[] | undefined): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\n}\n\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: 'tag', names, phase: 'runtime' });\n }\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: 'kv', label, value, phase: 'runtime' });\n }\n }\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\nfunction attachDoc(entry: DocEntry): void {\n const ctx = getContext();\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n}\n\n// ============================================================================\n// Suite path extraction\n// ============================================================================\n\n/**\n * Extract the suite path from testInfo.titlePath.\n * Playwright's titlePath includes: [projectName, ...describeTitles, testTitle]\n * We want just the describe titles (excluding project and test name).\n */\nfunction extractSuitePath(testInfo: TestInfo): string[] | undefined {\n const titlePath = testInfo.titlePath;\n if (titlePath.length <= 2) {\n return undefined;\n }\n const suitePath = titlePath.slice(1, -1);\n return suitePath.length > 0 ? suitePath : undefined;\n}\n\n// ============================================================================\n// Step markers\n// ============================================================================\n\nfunction createStepMarker(keyword: StepKeyword) {\n return function stepMarker(text: string, docs?: StoryDocs): void {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: docs ? convertStoryDocsToEntries(docs) : [],\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n };\n}\n\n// ============================================================================\n// story.init() - Playwright-specific\n// ============================================================================\n\nfunction init(testInfo: TestInfo, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: testInfo.title,\n steps: [],\n suitePath: extractSuitePath(testInfo),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId in structured meta\n meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };\n\n // OTel -> Story: inject human-readable doc entries\n meta.docs = meta.docs ?? [];\n meta.docs.push({ kind: 'kv', label: 'Trace ID', value: otelCtx.traceId, phase: 'runtime' });\n\n const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const url = resolveTraceUrl(template, otelCtx.traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n\n // Story -> OTel: enrich active span with story attributes\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const reqUrl = import.meta.url\n ?? (typeof __filename !== 'undefined' ? `file://${__filename}` : undefined);\n const req = createRequire(reqUrl!);\n const api = req('@opentelemetry/api');\n const span = api.trace?.getActiveSpan?.();\n if (span) {\n span.setAttribute('story.scenario', testInfo.title);\n if (options?.tags?.length) span.setAttribute('story.tags', options.tags);\n if (options?.ticket) {\n const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];\n span.setAttribute('story.tickets', tickets);\n }\n }\n } catch { /* OTel not available */ }\n }\n\n testInfo.annotations.push({\n type: 'story-meta',\n description: JSON.stringify(meta),\n });\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n activeTestInfo = testInfo;\n}\n\n/**\n * Update the story-meta annotation on testInfo with the current meta (including steps).\n * Called after each step/doc so the reporter sees the full story in onTestEnd.\n */\nfunction syncAnnotationToTest(): void {\n if (!activeTestInfo || !activeContext) return;\n const annotation = activeTestInfo.annotations.find(\n (a) => a.type === 'story-meta',\n );\n if (annotation) {\n annotation.description = JSON.stringify(activeContext.meta);\n }\n}\n\n// ============================================================================\n// story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function as a step with timing and error capture.\n * Records the step with `wrapped: true` and `durationMs`.\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: [],\n wrapped: true,\n };\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\n\n const start = performance.now();\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncAnnotationToTest();\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step with timing and error capture.\n * Shorthand for `story.fn('Then', text, body)`.\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Playwright-specific attach\n// ============================================================================\n\nfunction playwrightAttach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepId: ctx.currentStep?.id,\n });\n syncAnnotationToTest();\n\n if (activeTestInfo) {\n const attachOptions: { name: string; contentType: string; path?: string; body?: string | Buffer } = {\n name: options.name,\n contentType: options.mediaType,\n };\n if (options.path) attachOptions.path = options.path;\n if (options.body) attachOptions.body = options.body;\n activeTestInfo.attach(options.name, attachOptions);\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\nexport const story = {\n init,\n\n // BDD step markers\n given: createStepMarker('Given'),\n when: createStepMarker('When'),\n then: createStepMarker('Then'),\n and: createStepMarker('And'),\n but: createStepMarker('But'),\n\n // AAA pattern aliases\n arrange: createStepMarker('Given'),\n act: createStepMarker('When'),\n assert: createStepMarker('Then'),\n\n // Additional aliases\n setup: createStepMarker('Given'),\n context: createStepMarker('Given'),\n execute: createStepMarker('When'),\n action: createStepMarker('When'),\n verify: createStepMarker('Then'),\n\n // Standalone doc methods\n note(text: string): void {\n attachDoc({ kind: 'note', text, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n kv(options: KvOptions): void {\n attachDoc({ kind: 'kv', label: options.label, value: options.value, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n json(options: JsonOptions): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({ kind: 'code', label: options.label, content, lang: 'json', phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n code(options: CodeOptions): void {\n attachDoc({ kind: 'code', label: options.label, content: options.content, lang: options.lang, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n table(options: TableOptions): void {\n attachDoc({ kind: 'table', label: options.label, columns: options.columns, rows: options.rows, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n link(options: LinkOptions): void {\n attachDoc({ kind: 'link', label: options.label, url: options.url, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n section(options: SectionOptions): void {\n attachDoc({ kind: 'section', title: options.title, markdown: options.markdown, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n mermaid(options: MermaidOptions): void {\n attachDoc({ kind: 'mermaid', code: options.code, title: options.title, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n screenshot(options: ScreenshotOptions): void {\n attachDoc({ kind: 'screenshot', path: options.path, alt: options.alt, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n custom(options: CustomOptions): void {\n attachDoc({ kind: 'custom', type: options.type, data: options.data, phase: 'runtime' });\n syncAnnotationToTest();\n },\n\n // Attachments\n attach: playwrightAttach,\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n syncAnnotationToTest();\n return token;\n },\n\n endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n }\n syncAnnotationToTest();\n },\n\n // Step wrappers\n fn,\n expect: storyExpect,\n};\n\nexport type Story = typeof story;\n"],"mappings":"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","names":[]}
package/dist/reporter.js CHANGED
@@ -35,6 +35,23 @@ var StoryReporter = class {
35
35
  if (!storyAnnotation?.description) return;
36
36
  try {
37
37
  const meta = JSON.parse(storyAnnotation.description);
38
+ const otelSpansAnnotation = test.annotations.find(
39
+ (a) => a.type === "otel-spans"
40
+ );
41
+ if (otelSpansAnnotation?.description) {
42
+ try {
43
+ const spans = JSON.parse(otelSpansAnnotation.description);
44
+ if (Array.isArray(spans) && spans.length > 0) {
45
+ const valid = spans.filter(
46
+ (s) => s != null && typeof s === "object" && typeof s.spanId === "string" && typeof s.name === "string"
47
+ );
48
+ if (valid.length > 0) {
49
+ meta.otelSpans = valid;
50
+ }
51
+ }
52
+ } catch {
53
+ }
54
+ }
38
55
  const sourceFile = test.location?.file ? toRelativePosix(test.location.file, this.projectRoot) : "unknown";
39
56
  const sourceLine = test.location?.line ?? 1;
40
57
  let error;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Playwright reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n */\n\nimport type {\n Reporter,\n FullConfig,\n TestCase,\n TestResult,\n FullResult,\n} from \"@playwright/test/reporter\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\ninterface CollectedScenario {\n meta: StoryMeta;\n sourceFile: string;\n sourceLine: number;\n status: \"passed\" | \"failed\" | \"skipped\" | \"timedOut\" | \"interrupted\";\n error?: string;\n errorStack?: string;\n durationMs: number;\n projectName?: string;\n retry: number;\n retries: number;\n attachments?: RawAttachment[];\n stepEvents?: RawStepEvent[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private scenarios: CollectedScenario[] = [];\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private projectRoot: string = process.cwd();\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onBegin(config: FullConfig): void {\n this.startTime = Date.now();\n this.projectRoot = config.rootDir ?? process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(this.projectRoot);\n this.gitSha = readGitSha(this.projectRoot);\n }\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n // Find story-meta annotation\n const storyAnnotation = test.annotations.find((a) => a.type === \"story-meta\");\n if (!storyAnnotation?.description) return;\n\n try {\n const meta: StoryMeta = JSON.parse(storyAnnotation.description);\n\n // Get source file and line for sorting\n const sourceFile = test.location?.file\n ? toRelativePosix(test.location.file, this.projectRoot)\n : \"unknown\";\n const sourceLine = (test.location as { line?: number })?.line ?? 1;\n\n // Get error message if failed\n let error: string | undefined;\n let errorStack: string | undefined;\n if (result.status === \"failed\" && result.errors?.length) {\n const err = result.errors[0];\n error = err.message || String(err);\n errorStack = err.stack;\n }\n\n // Map Playwright result.attachments → RawAttachment[]\n const attachments: RawAttachment[] = (result.attachments ?? []).map((a) => {\n let body: string | undefined;\n let encoding: \"BASE64\" | \"IDENTITY\" | undefined;\n if (a.body !== undefined) {\n if (typeof a.body === \"string\") {\n body = a.body;\n encoding = \"IDENTITY\";\n } else if (Buffer.isBuffer(a.body) || (a.body as unknown) instanceof Uint8Array) {\n body = Buffer.from(a.body as Buffer | Uint8Array).toString(\"base64\");\n encoding = \"BASE64\";\n }\n }\n return {\n name: a.name,\n mediaType: a.contentType,\n path: a.path,\n body,\n encoding,\n };\n });\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n this.scenarios.push({\n meta,\n sourceFile,\n sourceLine,\n status: result.status,\n error,\n errorStack,\n durationMs: result.duration,\n projectName: test.parent?.project()?.name,\n retry: result.retry,\n retries: test.retries,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n });\n } catch {\n // Ignore parse errors\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n if (this.scenarios.length === 0) return;\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = this.scenarios.map((scenario) => {\n // Map Playwright status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n timedOut: \"fail\",\n interrupted: \"fail\",\n };\n\n return {\n title: scenario.meta.scenario,\n titlePath: scenario.meta.suitePath\n ? [...scenario.meta.suitePath, scenario.meta.scenario]\n : [scenario.meta.scenario],\n story: scenario.meta,\n sourceFile: scenario.sourceFile,\n sourceLine: Math.max(1, scenario.sourceLine),\n status: statusMap[scenario.status] ?? \"unknown\",\n durationMs: scenario.durationMs,\n error: scenario.error\n ? { message: scenario.error, stack: scenario.errorStack }\n : undefined,\n projectName: scenario.projectName,\n retry: scenario.retry,\n retries: scenario.retries,\n attachments: scenario.attachments,\n stepEvents: scenario.stepEvents,\n };\n });\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: this.projectRoot,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(this.projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Playwright reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n */\n\nimport type {\n Reporter,\n FullConfig,\n TestCase,\n TestResult,\n FullResult,\n} from \"@playwright/test/reporter\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\ninterface CollectedScenario {\n meta: StoryMeta;\n sourceFile: string;\n sourceLine: number;\n status: \"passed\" | \"failed\" | \"skipped\" | \"timedOut\" | \"interrupted\";\n error?: string;\n errorStack?: string;\n durationMs: number;\n projectName?: string;\n retry: number;\n retries: number;\n attachments?: RawAttachment[];\n stepEvents?: RawStepEvent[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private scenarios: CollectedScenario[] = [];\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private projectRoot: string = process.cwd();\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onBegin(config: FullConfig): void {\n this.startTime = Date.now();\n this.projectRoot = config.rootDir ?? process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(this.projectRoot);\n this.gitSha = readGitSha(this.projectRoot);\n }\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n // Find story-meta annotation\n const storyAnnotation = test.annotations.find((a) => a.type === \"story-meta\");\n if (!storyAnnotation?.description) return;\n\n try {\n const meta: StoryMeta = JSON.parse(storyAnnotation.description);\n\n // Read autotel OTel spans from annotations\n const otelSpansAnnotation = test.annotations.find(\n (a) => a.type === \"otel-spans\",\n );\n if (otelSpansAnnotation?.description) {\n try {\n const spans = JSON.parse(otelSpansAnnotation.description);\n if (Array.isArray(spans) && spans.length > 0) {\n const valid = spans.filter(\n (s: unknown) =>\n s != null &&\n typeof s === \"object\" &&\n typeof (s as Record<string, unknown>).spanId === \"string\" &&\n typeof (s as Record<string, unknown>).name === \"string\",\n );\n if (valid.length > 0) {\n meta.otelSpans = valid;\n }\n }\n } catch {\n /* ignore parse errors */\n }\n }\n\n // Get source file and line for sorting\n const sourceFile = test.location?.file\n ? toRelativePosix(test.location.file, this.projectRoot)\n : \"unknown\";\n const sourceLine = (test.location as { line?: number })?.line ?? 1;\n\n // Get error message if failed\n let error: string | undefined;\n let errorStack: string | undefined;\n if (result.status === \"failed\" && result.errors?.length) {\n const err = result.errors[0];\n error = err.message || String(err);\n errorStack = err.stack;\n }\n\n // Map Playwright result.attachments → RawAttachment[]\n const attachments: RawAttachment[] = (result.attachments ?? []).map((a) => {\n let body: string | undefined;\n let encoding: \"BASE64\" | \"IDENTITY\" | undefined;\n if (a.body !== undefined) {\n if (typeof a.body === \"string\") {\n body = a.body;\n encoding = \"IDENTITY\";\n } else if (Buffer.isBuffer(a.body) || (a.body as unknown) instanceof Uint8Array) {\n body = Buffer.from(a.body as Buffer | Uint8Array).toString(\"base64\");\n encoding = \"BASE64\";\n }\n }\n return {\n name: a.name,\n mediaType: a.contentType,\n path: a.path,\n body,\n encoding,\n };\n });\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n this.scenarios.push({\n meta,\n sourceFile,\n sourceLine,\n status: result.status,\n error,\n errorStack,\n durationMs: result.duration,\n projectName: test.parent?.project()?.name,\n retry: result.retry,\n retries: test.retries,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n });\n } catch {\n // Ignore parse errors\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n if (this.scenarios.length === 0) return;\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = this.scenarios.map((scenario) => {\n // Map Playwright status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n timedOut: \"fail\",\n interrupted: \"fail\",\n };\n\n return {\n title: scenario.meta.scenario,\n titlePath: scenario.meta.suitePath\n ? [...scenario.meta.suitePath, scenario.meta.scenario]\n : [scenario.meta.scenario],\n story: scenario.meta,\n sourceFile: scenario.sourceFile,\n sourceLine: Math.max(1, scenario.sourceLine),\n status: statusMap[scenario.status] ?? \"unknown\",\n durationMs: scenario.durationMs,\n error: scenario.error\n ? { message: scenario.error, stack: scenario.errorStack }\n : undefined,\n projectName: scenario.projectName,\n retry: scenario.retry,\n retries: scenario.retries,\n attachments: scenario.attachments,\n stepEvents: scenario.stepEvents,\n };\n });\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: this.projectRoot,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(this.projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executable-stories-playwright",
3
- "version": "3.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "BDD-style executable stories for Playwright Test with documentation generation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -23,15 +23,16 @@
23
23
  ],
24
24
  "peerDependencies": {
25
25
  "@playwright/test": ">=1.58.2",
26
- "executable-stories-formatters": "^0.2.0"
26
+ "executable-stories-formatters": "^0.4.0"
27
27
  },
28
28
  "dependencies": {},
29
29
  "devDependencies": {
30
+ "@opentelemetry/api": "^1.9.0",
30
31
  "@playwright/test": "^1.58.1",
31
32
  "@types/node": "^25.2.3",
32
33
  "tsup": "^8.5.1",
33
34
  "typescript": "~5.9.3",
34
- "executable-stories-formatters": "0.2.0"
35
+ "executable-stories-formatters": "0.4.0"
35
36
  },
36
37
  "repository": {
37
38
  "type": "git",