executable-stories-playwright 8.5.7 → 8.6.1

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
@@ -1,7 +1,7 @@
1
1
  import * as _playwright_test from '@playwright/test';
2
- import { TestInfo, PlaywrightTestArgs, PlaywrightTestOptions, TestStepInfo } from '@playwright/test';
2
+ import { Page, TestInfo, PlaywrightTestArgs, PlaywrightTestOptions, TestStepInfo } from '@playwright/test';
3
3
  export { TestStepInfo } from '@playwright/test';
4
- import * as executable_stories_core from 'executable-stories-core';
4
+ import * as executable_stories_formatters from 'executable-stories-formatters';
5
5
  import { DocEntry, StepKeyword } from 'executable-stories-formatters';
6
6
  export { DocEntry, NormalizedTicket, StepKeyword, StoryMeta, StoryStep } from 'executable-stories-formatters';
7
7
 
@@ -48,8 +48,26 @@ interface MermaidOptions {
48
48
  title?: string;
49
49
  }
50
50
  interface ScreenshotOptions {
51
- path: string;
51
+ /**
52
+ * Path to an already-captured screenshot file. Required unless `page` is
53
+ * given. If both are given, the fresh capture is also written to this path
54
+ * (same as passing `path` to `page.screenshot()` directly).
55
+ */
56
+ path?: string;
52
57
  alt?: string;
58
+ /**
59
+ * Capture the screenshot directly instead of reading an existing file —
60
+ * pass the `page` fixture and story.screenshot() takes the screenshot
61
+ * itself. This removes the most common cause of a broken/"unavailable"
62
+ * screenshot in reports: a `page.screenshot({ path })` call that was
63
+ * forgotten, mistyped, or drifted out of sync with the `path` passed here.
64
+ * Only supported on the top-level `story.screenshot()` call, not inside
65
+ * inline step docs (`story.then(text, { screenshot: {...} })`), since those
66
+ * run synchronously.
67
+ */
68
+ page?: Page;
69
+ /** Forwarded to `page.screenshot()` when `page` is provided. Defaults to `true`. */
70
+ fullPage?: boolean;
53
71
  }
54
72
  interface VideoOptions {
55
73
  /** Path to the video file, relative to the report output (or an http(s) URL). */
@@ -183,6 +201,23 @@ declare function fn<T>(keyword: StepKeyword, text: string, body: (fixtures: Play
183
201
  */
184
202
  declare function storyExpect<T>(text: string, body: () => T): T;
185
203
  declare function playwrightAttach(options: AttachmentOptions): void;
204
+ /**
205
+ * Two ways to attach a screenshot:
206
+ * - `story.screenshot({ page, alt })` captures a fresh screenshot and inlines
207
+ * it directly from the in-memory buffer — no filesystem round-trip, so
208
+ * there's no path to fall out of sync and nothing for Playwright's output
209
+ * cleanup to delete before the report is built. Prefer this form.
210
+ * - `story.screenshot({ path, alt })` attaches a screenshot that already
211
+ * exists on disk (e.g. one taken earlier for another purpose). The caller
212
+ * is responsible for making sure something wrote a file to `path` first.
213
+ */
214
+ declare function screenshotImpl(options: ScreenshotOptions & {
215
+ page: Page;
216
+ }, children?: DocEntry[]): Promise<DocEntry>;
217
+ declare function screenshotImpl(options: ScreenshotOptions & {
218
+ path: string;
219
+ page?: undefined;
220
+ }, children?: DocEntry[]): DocEntry;
186
221
  declare const story: {
187
222
  init: typeof init;
188
223
  given: {
@@ -259,7 +294,7 @@ declare const story: {
259
294
  link(options: LinkOptions, children?: DocEntry[]): DocEntry;
260
295
  section(options: SectionOptions, children?: DocEntry[]): DocEntry;
261
296
  mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry;
262
- screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry;
297
+ screenshot: typeof screenshotImpl;
263
298
  video(options: VideoOptions, children?: DocEntry[]): DocEntry;
264
299
  /**
265
300
  * Embed HTML in a sandboxed iframe in the current step or story-level docs.
@@ -310,27 +345,27 @@ type Story = typeof story;
310
345
 
311
346
  declare const given: {
312
347
  (text: string, docs?: StoryDocs): void;
313
- (text: string, children: executable_stories_core.DocEntry[]): void;
348
+ (text: string, children: executable_stories_formatters.DocEntry[]): void;
314
349
  <T>(text: string, body: (fixtures: _playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & Record<string, unknown>, step?: _playwright_test.TestStepInfo) => T): T;
315
350
  };
316
351
  declare const when: {
317
352
  (text: string, docs?: StoryDocs): void;
318
- (text: string, children: executable_stories_core.DocEntry[]): void;
353
+ (text: string, children: executable_stories_formatters.DocEntry[]): void;
319
354
  <T>(text: string, body: (fixtures: _playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & Record<string, unknown>, step?: _playwright_test.TestStepInfo) => T): T;
320
355
  };
321
356
  declare const then: {
322
357
  (text: string, docs?: StoryDocs): void;
323
- (text: string, children: executable_stories_core.DocEntry[]): void;
358
+ (text: string, children: executable_stories_formatters.DocEntry[]): void;
324
359
  <T>(text: string, body: (fixtures: _playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & Record<string, unknown>, step?: _playwright_test.TestStepInfo) => T): T;
325
360
  };
326
361
  declare const and: {
327
362
  (text: string, docs?: StoryDocs): void;
328
- (text: string, children: executable_stories_core.DocEntry[]): void;
363
+ (text: string, children: executable_stories_formatters.DocEntry[]): void;
329
364
  <T>(text: string, body: (fixtures: _playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & Record<string, unknown>, step?: _playwright_test.TestStepInfo) => T): T;
330
365
  };
331
366
  declare const but: {
332
367
  (text: string, docs?: StoryDocs): void;
333
- (text: string, children: executable_stories_core.DocEntry[]): void;
368
+ (text: string, children: executable_stories_formatters.DocEntry[]): void;
334
369
  <T>(text: string, body: (fixtures: _playwright_test.PlaywrightTestArgs & _playwright_test.PlaywrightTestOptions & Record<string, unknown>, step?: _playwright_test.TestStepInfo) => T): T;
335
370
  };
336
371
 
package/dist/index.js CHANGED
@@ -124,9 +124,21 @@ function convertStoryDocsToEntries(docs) {
124
124
  });
125
125
  }
126
126
  if (docs.screenshot) {
127
+ if (docs.screenshot.page) {
128
+ throw new Error(
129
+ "story.screenshot({ page }) is not supported inside inline step docs (e.g. story.then(text, { screenshot: { page } })) because capturing a screenshot is asynchronous and step markers run synchronously. Call story.screenshot({ page, alt }) as its own statement instead."
130
+ );
131
+ }
132
+ if (!docs.screenshot.path) {
133
+ throw new Error("story docs screenshot requires a `path` (or use story.screenshot({ page }) instead).");
134
+ }
127
135
  entries.push({
128
136
  kind: "screenshot",
129
- path: docs.screenshot.path,
137
+ // Inline file bytes as a `data:` URI here too — this is the same
138
+ // capture-vs-cleanup race that story.screenshot() guards against, and
139
+ // this path used to skip it, so a screenshot passed via inline step
140
+ // docs would silently never embed no matter how the file was captured.
141
+ path: inlineScreenshotIfPossible(docs.screenshot.path),
130
142
  alt: docs.screenshot.alt,
131
143
  phase: "runtime"
132
144
  });
@@ -178,17 +190,35 @@ var SCREENSHOT_MIME_BY_EXT = {
178
190
  };
179
191
  function inlineScreenshotIfPossible(filePath) {
180
192
  if (/^(?:https?:|data:)/i.test(filePath)) return filePath;
193
+ const ext = path.extname(filePath).slice(1).toLowerCase();
194
+ const mime = SCREENSHOT_MIME_BY_EXT[ext];
195
+ if (!mime) return filePath;
181
196
  try {
182
- const ext = path.extname(filePath).slice(1).toLowerCase();
183
- const mime = SCREENSHOT_MIME_BY_EXT[ext];
184
- if (!mime) return filePath;
185
- if (!fs.existsSync(filePath)) return filePath;
197
+ if (!fs.existsSync(filePath)) {
198
+ warnScreenshotUnavailable(filePath);
199
+ return filePath;
200
+ }
186
201
  const buf = fs.readFileSync(filePath);
187
202
  return `data:${mime};base64,${buf.toString("base64")}`;
188
- } catch {
203
+ } catch (err) {
204
+ warnScreenshotUnavailable(filePath, err);
189
205
  return filePath;
190
206
  }
191
207
  }
208
+ function warnScreenshotUnavailable(filePath, cause) {
209
+ const causeMessage = cause instanceof Error ? cause.message : cause ? String(cause) : void 0;
210
+ console.warn(
211
+ `[executable-stories-playwright] story.screenshot(): could not read "${filePath}" \u2014 the report will show a "Screenshot unavailable" placeholder instead of the image. Make sure a screenshot is written to this exact path before story.screenshot() runs (e.g. \`await page.screenshot({ path })\`), or capture it directly with \`story.screenshot({ page, alt })\`.` + (causeMessage ? ` Cause: ${causeMessage}` : "")
212
+ );
213
+ }
214
+ function warnIfAbsoluteVideoPathMissing(filePath) {
215
+ if (/^(?:https?:|data:)/i.test(filePath)) return;
216
+ if (!path.isAbsolute(filePath)) return;
217
+ if (fs.existsSync(filePath)) return;
218
+ console.warn(
219
+ `[executable-stories-playwright] story.video(): "${filePath}" does not exist yet. Video bytes are never inlined, so this exact path must resolve to a real file by the time the report is built, or the report will show a "Video unavailable" placeholder instead of the clip.`
220
+ );
221
+ }
192
222
  function attachDoc(entry, children) {
193
223
  const ctx = getContext();
194
224
  if (children && children.length > 0) {
@@ -458,6 +488,19 @@ function playwrightAttach(options) {
458
488
  activeTestInfo.attach(options.name, attachOptions);
459
489
  }
460
490
  }
491
+ function screenshotImpl(options, children) {
492
+ if (options.page) {
493
+ return options.page.screenshot(options.path ? { path: options.path, fullPage: options.fullPage ?? true } : { fullPage: options.fullPage ?? true }).then((buffer) => {
494
+ const dataUri = `data:image/png;base64,${buffer.toString("base64")}`;
495
+ return attachDoc({ kind: "screenshot", path: dataUri, alt: options.alt, phase: "runtime" }, children);
496
+ });
497
+ }
498
+ if (!options.path) {
499
+ throw new Error("story.screenshot() requires either `path` (an existing file) or `page` (to capture one).");
500
+ }
501
+ const resolvedPath = inlineScreenshotIfPossible(options.path);
502
+ return attachDoc({ kind: "screenshot", path: resolvedPath, alt: options.alt, phase: "runtime" }, children);
503
+ }
461
504
  var story = {
462
505
  init,
463
506
  // BDD step markers
@@ -506,11 +549,9 @@ var story = {
506
549
  mermaid(options, children) {
507
550
  return attachDoc({ kind: "mermaid", code: options.code, title: options.title, phase: "runtime" }, children);
508
551
  },
509
- screenshot(options, children) {
510
- const resolvedPath = inlineScreenshotIfPossible(options.path);
511
- return attachDoc({ kind: "screenshot", path: resolvedPath, alt: options.alt, phase: "runtime" }, children);
512
- },
552
+ screenshot: screenshotImpl,
513
553
  video(options, children) {
554
+ warnIfAbsoluteVideoPathMissing(options.path);
514
555
  return attachDoc(
515
556
  { kind: "video", path: options.path, caption: options.caption, poster: options.poster, phase: "runtime" },
516
557
  children
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/story-api.ts","../src/step-runner.ts","../src/index.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 * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { TestInfo, PlaywrightTestArgs, PlaywrightTestOptions } from '@playwright/test';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n buildHtmlDocEntry,\n} from 'executable-stories-formatters';\nimport type {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n NormalizedTicket,\n TicketInput,\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 VideoOptions,\n HtmlOptions,\n CustomOptions,\n ConsoleOptions,\n ObservePageErrorsOptions,\n} from './types';\nimport { runStep, isAsyncFunction } from './step-runner';\nimport type { TestStepInfo } from './step-runner';\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\n/** Fixture type for step callbacks: Playwright test args + options; custom extend() fixtures as unknown. */\ntype PlaywrightFixtures = PlaywrightTestArgs & PlaywrightTestOptions & Record<string, unknown>;\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 fixtures?: Record<string, unknown>;\n /** Trace-link URL template, captured at init for later use by attachSpans. */\n traceUrlTemplate?: string;\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(\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.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(buildHtmlEntry(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 * Capture-time inlining for the html doc kind. Playwright is the only adapter\n * that pre-resolves a source before building the entry; the exactly-one-of rule\n * and the entry shape come from the canonical `buildHtmlDocEntry`.\n *\n * An *absolute* local path (e.g. `/home/runner/work/.../x.html`, or anything\n * under `testInfo.outputPath()`) is read now and inlined as iframe srcdoc: that\n * path 404s once the HTML report is downloaded as a CI artifact and opened on\n * another machine, so we capture the bytes while they exist — the same\n * reasoning as screenshot data-URI inlining.\n *\n * A *relative* path is left as-is so it behaves exactly like the Vitest adapter:\n * the formatter reads it at format time (default), or copies it as a hashed\n * asset under `assetMode: \"copy\"`. Inlining relative paths early would needlessly\n * foreclose copy mode. The embedded HTML must be self-contained (single-file):\n * relative references to sibling CSS/JS are not rewritten (directory bundling is planned).\n */\nfunction buildHtmlEntry(options: HtmlOptions): DocEntry {\n let { path: htmlPath, content } = options;\n // Only inline when `path` is the lone source: that keeps an invalid\n // multi-source call (e.g. path + content) reaching the canonical validator\n // instead of being silently \"fixed\" by overwriting content from disk. Listing\n // `htmlPath !== undefined` first in the chain narrows it for the rest.\n if (\n htmlPath !== undefined &&\n options.url === undefined &&\n content === undefined &&\n !/^https?:/i.test(htmlPath) &&\n path.isAbsolute(htmlPath)\n ) {\n try {\n if (fs.existsSync(htmlPath)) {\n content = fs.readFileSync(htmlPath, 'utf8');\n htmlPath = undefined;\n }\n } catch {\n // keep the path — the formatter retries at format time\n }\n }\n return buildHtmlDocEntry({ ...options, path: htmlPath, content });\n}\n\nconst SCREENSHOT_MIME_BY_EXT: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n avif: 'image/avif',\n bmp: 'image/bmp',\n};\n\n/**\n * Read a screenshot file and return a `data:` URI; fall back to the original\n * path on any failure (remote URL, missing file, unknown extension).\n */\nfunction inlineScreenshotIfPossible(filePath: string): string {\n if (/^(?:https?:|data:)/i.test(filePath)) return filePath;\n try {\n const ext = path.extname(filePath).slice(1).toLowerCase();\n const mime = SCREENSHOT_MIME_BY_EXT[ext];\n if (!mime) return filePath;\n if (!fs.existsSync(filePath)) return filePath;\n const buf = fs.readFileSync(filePath);\n return `data:${mime};base64,${buf.toString('base64')}`;\n } catch {\n return filePath;\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 syncAnnotationToTest();\n return entry;\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 function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: (fixtures: PlaywrightFixtures, step?: TestStepInfo) => T): T;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | ((...args: any[]) => 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 ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\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 syncAnnotationToTest();\n return;\n }\n\n if (!isCallback) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const body = docsOrBody as (fixtures?: PlaywrightFixtures, stepInfo?: TestStepInfo) => T;\n const label = `${step.keyword}: ${text}`;\n const start = performance.now();\n\n // ── Async or stepInfo-aware callbacks: route through runStep() for Playwright-native integrations ──\n // Integrations: screencast chapters (v1.59), test.step/TestStepInfo (v1.51),\n // tracing.group (v1.49). Activated when fixtures are available AND either:\n // 1. callback is an async function, OR\n // 2. callback expects TestStepInfo (arity >= 2)\n if (ctx.fixtures !== undefined && (isAsyncFunction(body) || body.length >= 2)) {\n const fixtures = ctx.fixtures as Record<string, unknown>;\n const result = runStep(\n label,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n body as unknown as (fixtures: Record<string, unknown>, step?: TestStepInfo) => Promise<any>,\n fixtures,\n );\n return result.then(\n (val: T) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); return val; },\n (err: unknown) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); throw err; },\n ) as T;\n }\n\n // ── Sync callbacks or no-fixture context: existing behaviour ─────────────\n try {\n const result = ctx.fixtures !== undefined ? body(ctx.fixtures as PlaywrightFixtures) : body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); throw err; },\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 return stepMarker;\n}\n\n// ============================================================================\n// story.init() - Playwright-specific\n// ============================================================================\n\nfunction isTestInfo(x: unknown): x is TestInfo {\n return (\n typeof x === 'object' &&\n x !== null &&\n 'title' in x &&\n 'annotations' in x &&\n Array.isArray((x as TestInfo).annotations)\n );\n}\n\n/** init(testInfo) or init(fixtures, testInfo) or init(testInfo, { fixtures }). */\nfunction init(\n first: TestInfo | unknown,\n second?: StoryOptions | TestInfo,\n third?: StoryOptions,\n): void {\n let testInfo: TestInfo;\n let options: StoryOptions | undefined;\n let fixtures: unknown;\n\n if (second !== undefined && isTestInfo(second)) {\n fixtures = first;\n testInfo = second;\n options = third;\n } else {\n testInfo = first as TestInfo;\n options = second;\n fixtures = options?.fixtures;\n }\n\n const meta: StoryMeta = {\n scenario: testInfo.title,\n steps: [],\n suitePath: extractSuitePath(testInfo),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n covers: options?.covers,\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // Flag the scenario so the reporter promotes the Playwright screen recording\n // into a featured inline video doc entry (see reporter onTestEnd).\n if (options?.featureVideo) {\n meta.meta = { ...meta.meta, featureVideo: true };\n }\n\n // OTel bridge: detect active span, flow data bidirectionally\n const traceUrlTemplate =\n options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId + docs from the active span.\n applyTraceToMeta(meta, otelCtx.traceId, otelCtx.spanId, traceUrlTemplate);\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.map((t) => typeof t === 'string' ? t : t.id));\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 // ── Feature: Tag sync (v1.43) ─────────────────────────────────────────────\n // Sync story tags to Playwright's native annotation system so they appear in\n // UI Mode tag filters and the HTML reporter's tag display.\n for (const tag of options?.tags ?? []) {\n testInfo.annotations.push({ type: 'tag', description: tag });\n }\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n fixtures: fixtures as Record<string, unknown> | undefined,\n traceUrlTemplate,\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 * Bridge an OTel trace into the story's meta + docs: structured `otel` meta, a\n * \"Trace ID\" key-value, and a \"View Trace\" link when the template resolves.\n * Idempotent — once a traceId is recorded it is not overwritten, so the\n * active-span path in init() and the explicit path in attachSpans() compose\n * without duplicating entries.\n */\nfunction applyTraceToMeta(\n meta: StoryMeta,\n traceId: string,\n spanId: string | undefined,\n template: string | undefined,\n): void {\n const existing = (meta.meta as { otel?: { traceId?: string } } | undefined)\n ?.otel;\n if (existing?.traceId) return;\n\n meta.meta = { ...meta.meta, otel: { traceId, spanId } };\n meta.docs = meta.docs ?? [];\n meta.docs.push({\n kind: 'kv',\n label: 'Trace ID',\n value: traceId,\n phase: 'runtime',\n });\n const url = resolveTraceUrl(template, traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\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: (fixtures: PlaywrightFixtures) => T): T;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction fn<T>(keyword: StepKeyword, text: string, body: (...args: any[]) => T): T {\n const ctx = getContext();\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 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 syncAnnotationToTest();\n\n const start = performance.now();\n try {\n const result = ctx.fixtures !== undefined ? body(ctx.fixtures as PlaywrightFixtures) : 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, 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 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 // Inline file bytes as a `data:` URI so the screenshot survives Playwright's\n // per-test outputDir cleanup (passing tests have their `test-results/<test>/`\n // directory deleted before the formatter runs). Falls back to the original\n // path for remote URLs or unreadable files.\n const resolvedPath = inlineScreenshotIfPossible(options.path);\n return attachDoc({ kind: 'screenshot', path: resolvedPath, alt: options.alt, phase: 'runtime' }, children);\n },\n\n video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n // Unlike screenshots, video bytes are never inlined as a data URI — they're\n // too large. The path is kept as-is so the formatter's asset bundler copies\n // the file into the report/docs site. Pass a path relative to the output, or\n // use `featureVideo: true` on story.init() to auto-promote the Playwright\n // recording instead.\n return attachDoc(\n { kind: 'video', path: options.path, caption: options.caption, poster: options.poster, phase: 'runtime' },\n children,\n );\n },\n\n /**\n * Embed HTML in a sandboxed iframe in the current step or story-level docs.\n * Exactly one of path/url/content is required. Local files are inlined so\n * the report stays self-contained — the HTML must therefore be\n * self-contained too (use your tool's single-file mode); relative sub-asset\n * references won't resolve.\n * @example story.html({ path: './coverage/index.html', title: 'Coverage' })\n * @example story.html({ url: 'https://dash.example.com/run/42', height: 600 })\n * @example story.html({ content: chartHtml, title: 'Latency chart' })\n */\n html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlEntry(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 // ── Feature: Console capture (v1.56) ────────────────────────────────────\n /**\n * Snapshot the current page console messages (and optionally page errors)\n * and attach them as a code doc entry.\n *\n * Uses page.consoleMessages() and page.pageErrors() introduced in Playwright v1.56.\n * Safe to call on any Playwright version – silently produces empty output if the\n * APIs are not present.\n *\n * @example\n * story.when('the form is submitted', async ({ page }) => {\n * await page.click('#submit');\n * story.console({ page, label: 'Submit console output' });\n * });\n */\n console(options: ConsoleOptions, children?: DocEntry[]): DocEntry {\n const p = options.page as {\n consoleMessages?: () => Array<{ type(): string; text(): string }>;\n pageErrors?: () => Error[];\n };\n\n const lines: string[] = [];\n\n if (typeof p?.consoleMessages === 'function') {\n for (const msg of p.consoleMessages()) {\n lines.push(`[${msg.type()}] ${msg.text()}`);\n }\n }\n\n if (options.includeErrors === true && typeof p?.pageErrors === 'function') {\n for (const err of p.pageErrors()) {\n lines.push(`[error] ${err.message}`);\n }\n }\n\n return attachDoc(\n {\n kind: 'code',\n label: options.label ?? 'Console',\n content: lines.length > 0 ? lines.join('\\n') : '(no console output)',\n lang: 'log',\n phase: 'runtime',\n },\n children,\n );\n },\n\n /**\n * Capture current page runtime errors and attach as a structured doc entry.\n *\n * Collects from Playwright v1.56+ page.pageErrors() and page.consoleMessages().\n * This is intentionally snapshot-based so tests can call it at critical points\n * (after submit, after navigation) and keep evidence near relevant steps.\n */\n observePageErrors(options: ObservePageErrorsOptions, children?: DocEntry[]): DocEntry {\n const p = options.page as {\n consoleMessages?: () => Array<{ type(): string; text(): string }>;\n pageErrors?: () => Error[];\n };\n const ignore = options.ignore ?? [];\n const lines: string[] = [];\n\n if (typeof p?.pageErrors === 'function') {\n for (const err of p.pageErrors()) {\n const msg = err?.message ?? String(err);\n if (!ignore.some((rx) => rx.test(msg))) lines.push(`[pageerror] ${msg}`);\n }\n }\n if (typeof p?.consoleMessages === 'function') {\n for (const msg of p.consoleMessages()) {\n if (msg.type() !== 'error') continue;\n const text = msg.text();\n if (!ignore.some((rx) => rx.test(text))) lines.push(`[console.error] ${text}`);\n }\n }\n\n return attachDoc(\n {\n kind: 'code',\n label: options.label ?? 'Browser Runtime Errors',\n content: lines.length > 0 ? lines.join('\\n') : '(no runtime errors observed)',\n lang: 'log',\n phase: 'runtime',\n },\n children,\n );\n },\n\n // Attachments\n attach: playwrightAttach,\n\n // OTel span attachment\n attachSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n options?: { traceId?: string; spanId?: string },\n ): void {\n if (!activeTestInfo) return;\n const existing = activeTestInfo.annotations.find(\n (a) => a.type === 'story-otel-spans',\n );\n const description = JSON.stringify(spans);\n if (existing) {\n existing.description = description;\n } else {\n activeTestInfo.annotations.push({\n type: 'story-otel-spans',\n description,\n });\n }\n // Capture-then-attach: wire the trace badge + \"View Trace\" link when the\n // trace was created after init() (so the init-time bridge couldn't see it).\n if (options?.traceId && activeContext) {\n applyTraceToMeta(\n activeContext.meta,\n options.traceId,\n options.spanId,\n activeContext.traceUrlTemplate,\n );\n syncAnnotationToTest();\n }\n },\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n 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","/**\n * Playwright-native step execution helper.\n *\n * Centralises the cross-cutting concerns for fixture-aware step callbacks:\n * 1. page.screencast.showChapter() – narrated chapter markers in video recordings (v1.59)\n * 2. test.step() – TestStepInfo access + trace/report visibility (v1.51)\n * 3. context.tracing.group() – BDD phase grouping in trace viewer (v1.49)\n *\n * Used for callbacks that are either async functions or expect TestStepInfo.\n * Sync callbacks that don't need TestStepInfo follow the faster sync path.\n * All three integrations degrade gracefully if the API is absent.\n */\n\nimport { test } from '@playwright/test';\nimport type { TestStepInfo } from '@playwright/test';\n\nexport type { TestStepInfo };\n\n/** Async step callback that optionally receives TestStepInfo as a second argument. */\nexport type AsyncStepCallback<T = unknown> = (\n fixtures: Record<string, unknown>,\n step?: TestStepInfo,\n) => Promise<T>;\n\n/**\n * Execute an async step callback with full Playwright-native integration.\n *\n * Call order:\n * 1. page.screencast.showChapter(label) – sets chapter in the recording before the step runs\n * 2. test.step(label, …) – wraps execution for trace/report visibility\n * 3. context.tracing.group(label, …) – groups trace actions under the step label\n * 4. body(fixtures, stepInfo) – user callback with injected TestStepInfo\n */\nexport async function runStep<T>(\n label: string,\n body: AsyncStepCallback<T>,\n fixtures: Record<string, unknown>,\n): Promise<T> {\n const page = fixtures.page as Record<string, unknown> | undefined;\n // Derive context from fixtures or from page.context() (Playwright sync method)\n const context =\n (fixtures.context as Record<string, unknown> | undefined) ??\n (typeof (page as { context?: () => Record<string, unknown> })?.context === 'function'\n ? (page as { context: () => Record<string, unknown> }).context()\n : undefined);\n\n // ── Feature 1: Screencast chapter (v1.59) ─────────────────────────────────\n // Show the chapter BEFORE the step body runs so the recording reflects the\n // BDD step title at the right moment. Silently skipped on older Playwright.\n const screencast = page?.screencast as\n | { showChapter?: (label: string) => Promise<void> }\n | undefined;\n if (screencast?.showChapter) {\n try {\n await screencast.showChapter(label);\n } catch {\n // Graceful degradation: screencast not started or API unavailable\n }\n }\n\n // ── Feature 2: test.step (v1.51) + Feature 3: tracing.group (v1.49) ──────\n // test.step provides TestStepInfo for the callback and makes the step visible\n // in the Playwright trace viewer and HTML report as a named action.\n // tracing.group inside it groups the step's child actions under the label.\n return test.step(label, async (stepInfo) => {\n const tracing = context?.tracing as\n | { group?: <R>(label: string, fn: () => Promise<R>) => Promise<R> }\n | undefined;\n\n if (tracing?.group) {\n // Track whether body was invoked to avoid double-execution\n // when body throws inside tracing.group\n let bodyInvoked = false;\n try {\n return await tracing.group(label, async () => {\n bodyInvoked = true;\n return body(fixtures, stepInfo);\n });\n } catch (e) {\n // If body was invoked, it threw - re-throw (don't retry)\n if (bodyInvoked) throw e;\n // Otherwise, tracing.group itself threw (e.g., tracing not recording)\n // Fall back to calling body without tracing.group\n return body(fixtures, stepInfo);\n }\n }\n\n return body(fixtures, stepInfo);\n });\n}\n\n/**\n * Returns true if fn is an async function (declared with `async`).\n * Used along with callback arity to decide whether to route a step callback through runStep().\n *\n * Callbacks are routed through runStep() if they are async functions OR if they\n * have arity >= 2 (meaning they expect TestStepInfo as the second argument).\n */\nexport function isAsyncFunction(fn: unknown): boolean {\n return (\n typeof fn === 'function' &&\n (fn as { constructor?: { name?: string } }).constructor?.name === 'AsyncFunction'\n );\n}\n","/**\n * Playwright Executable Stories\n *\n * BDD-style executable documentation for Playwright Test.\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\n// Story API\nimport { story } from './story-api';\nexport { story };\nexport type { Story } from './story-api';\n\n// Top-level step helpers (framework contract)\nexport const given = story.given;\nexport const when = story.when;\nexport const then = story.then;\nexport const and = story.and;\nexport const but = story.but;\n\n// Re-export types from local types module\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n TicketInput,\n NormalizedTicket,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n VideoOptions,\n CustomOptions,\n ConsoleOptions,\n ObservePageErrorsOptions,\n AttachmentOptions,\n} from './types';\n\n// TestStepInfo re-exported from @playwright/test for consumer convenience\nexport type { TestStepInfo } from './step-runner';\n"],"mappings":"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","names":["fn"]}
1
+ {"version":3,"sources":["../src/story-api.ts","../src/step-runner.ts","../src/index.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 * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { TestInfo, PlaywrightTestArgs, PlaywrightTestOptions, Page } from '@playwright/test';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n buildHtmlDocEntry,\n} from 'executable-stories-formatters';\nimport type {\n StepKeyword,\n StoryMeta,\n StoryStep,\n DocEntry,\n NormalizedTicket,\n TicketInput,\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 VideoOptions,\n HtmlOptions,\n CustomOptions,\n ConsoleOptions,\n ObservePageErrorsOptions,\n} from './types';\nimport { runStep, isAsyncFunction } from './step-runner';\nimport type { TestStepInfo } from './step-runner';\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\n/** Fixture type for step callbacks: Playwright test args + options; custom extend() fixtures as unknown. */\ntype PlaywrightFixtures = PlaywrightTestArgs & PlaywrightTestOptions & Record<string, unknown>;\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 fixtures?: Record<string, unknown>;\n /** Trace-link URL template, captured at init for later use by attachSpans. */\n traceUrlTemplate?: string;\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(\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.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 if (docs.screenshot.page) {\n throw new Error(\n 'story.screenshot({ page }) is not supported inside inline step docs ' +\n '(e.g. story.then(text, { screenshot: { page } })) because capturing a ' +\n 'screenshot is asynchronous and step markers run synchronously. Call ' +\n 'story.screenshot({ page, alt }) as its own statement instead.',\n );\n }\n if (!docs.screenshot.path) {\n throw new Error('story docs screenshot requires a `path` (or use story.screenshot({ page }) instead).');\n }\n entries.push({\n kind: 'screenshot',\n // Inline file bytes as a `data:` URI here too — this is the same\n // capture-vs-cleanup race that story.screenshot() guards against, and\n // this path used to skip it, so a screenshot passed via inline step\n // docs would silently never embed no matter how the file was captured.\n path: inlineScreenshotIfPossible(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(buildHtmlEntry(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 * Capture-time inlining for the html doc kind. Playwright is the only adapter\n * that pre-resolves a source before building the entry; the exactly-one-of rule\n * and the entry shape come from the canonical `buildHtmlDocEntry`.\n *\n * An *absolute* local path (e.g. `/home/runner/work/.../x.html`, or anything\n * under `testInfo.outputPath()`) is read now and inlined as iframe srcdoc: that\n * path 404s once the HTML report is downloaded as a CI artifact and opened on\n * another machine, so we capture the bytes while they exist — the same\n * reasoning as screenshot data-URI inlining.\n *\n * A *relative* path is left as-is so it behaves exactly like the Vitest adapter:\n * the formatter reads it at format time (default), or copies it as a hashed\n * asset under `assetMode: \"copy\"`. Inlining relative paths early would needlessly\n * foreclose copy mode. The embedded HTML must be self-contained (single-file):\n * relative references to sibling CSS/JS are not rewritten (directory bundling is planned).\n */\nfunction buildHtmlEntry(options: HtmlOptions): DocEntry {\n let { path: htmlPath, content } = options;\n // Only inline when `path` is the lone source: that keeps an invalid\n // multi-source call (e.g. path + content) reaching the canonical validator\n // instead of being silently \"fixed\" by overwriting content from disk. Listing\n // `htmlPath !== undefined` first in the chain narrows it for the rest.\n if (\n htmlPath !== undefined &&\n options.url === undefined &&\n content === undefined &&\n !/^https?:/i.test(htmlPath) &&\n path.isAbsolute(htmlPath)\n ) {\n try {\n if (fs.existsSync(htmlPath)) {\n content = fs.readFileSync(htmlPath, 'utf8');\n htmlPath = undefined;\n }\n } catch {\n // keep the path — the formatter retries at format time\n }\n }\n return buildHtmlDocEntry({ ...options, path: htmlPath, content });\n}\n\nconst SCREENSHOT_MIME_BY_EXT: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n avif: 'image/avif',\n bmp: 'image/bmp',\n};\n\n/**\n * Read a screenshot file and return a `data:` URI; fall back to the original\n * path on any failure (remote URL, missing file, unknown extension).\n *\n * A missing/unreadable local path almost always means the screenshot was\n * never taken at that path — e.g. `story.screenshot({ path })` called without\n * a preceding `page.screenshot({ path })`, or the two paths drifted apart.\n * That failure otherwise surfaces minutes later as a broken image in a PR\n * comment or a \"Screenshot unavailable\" placeholder in the HTML report, far\n * from the line that caused it — so warn immediately, at the call site.\n */\nfunction inlineScreenshotIfPossible(filePath: string): string {\n if (/^(?:https?:|data:)/i.test(filePath)) return filePath;\n const ext = path.extname(filePath).slice(1).toLowerCase();\n const mime = SCREENSHOT_MIME_BY_EXT[ext];\n if (!mime) return filePath;\n try {\n if (!fs.existsSync(filePath)) {\n warnScreenshotUnavailable(filePath);\n return filePath;\n }\n const buf = fs.readFileSync(filePath);\n return `data:${mime};base64,${buf.toString('base64')}`;\n } catch (err) {\n warnScreenshotUnavailable(filePath, err);\n return filePath;\n }\n}\n\nfunction warnScreenshotUnavailable(filePath: string, cause?: unknown): void {\n const causeMessage = cause instanceof Error ? cause.message : cause ? String(cause) : undefined;\n console.warn(\n `[executable-stories-playwright] story.screenshot(): could not read \"${filePath}\" — ` +\n 'the report will show a \"Screenshot unavailable\" placeholder instead of the image. ' +\n 'Make sure a screenshot is written to this exact path before story.screenshot() runs ' +\n '(e.g. `await page.screenshot({ path })`), or capture it directly with ' +\n '`story.screenshot({ page, alt })`.' +\n (causeMessage ? ` Cause: ${causeMessage}` : ''),\n );\n}\n\n/**\n * story.video() never reads/inlines the file (video bytes are too large for\n * a `data:` URI) — it always defers to the formatter's asset bundler, which\n * runs later, at report-generation time. A *relative* path is legitimately\n * unresolvable yet (the bundler resolves it against the report output), so\n * this only warns for an *absolute* local path missing right now — that can\n * never resolve later either, since it's not relative to anything the\n * bundler controls. The most common cause: passing a path nothing has\n * written to (e.g. a typo, or the video hasn't finished flushing to disk —\n * see Playwright's `page.video()?.path()`, which resolves only after\n * `page.close()`).\n */\nfunction warnIfAbsoluteVideoPathMissing(filePath: string): void {\n if (/^(?:https?:|data:)/i.test(filePath)) return;\n if (!path.isAbsolute(filePath)) return;\n if (fs.existsSync(filePath)) return;\n console.warn(\n `[executable-stories-playwright] story.video(): \"${filePath}\" does not exist yet. ` +\n 'Video bytes are never inlined, so this exact path must resolve to a real file by the time the ' +\n 'report is built, or the report will show a \"Video unavailable\" placeholder instead of the clip.',\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 syncAnnotationToTest();\n return entry;\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 function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: (fixtures: PlaywrightFixtures, step?: TestStepInfo) => T): T;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | ((...args: any[]) => 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 ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncAnnotationToTest();\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 syncAnnotationToTest();\n return;\n }\n\n if (!isCallback) return;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const body = docsOrBody as (fixtures?: PlaywrightFixtures, stepInfo?: TestStepInfo) => T;\n const label = `${step.keyword}: ${text}`;\n const start = performance.now();\n\n // ── Async or stepInfo-aware callbacks: route through runStep() for Playwright-native integrations ──\n // Integrations: screencast chapters (v1.59), test.step/TestStepInfo (v1.51),\n // tracing.group (v1.49). Activated when fixtures are available AND either:\n // 1. callback is an async function, OR\n // 2. callback expects TestStepInfo (arity >= 2)\n if (ctx.fixtures !== undefined && (isAsyncFunction(body) || body.length >= 2)) {\n const fixtures = ctx.fixtures as Record<string, unknown>;\n const result = runStep(\n label,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n body as unknown as (fixtures: Record<string, unknown>, step?: TestStepInfo) => Promise<any>,\n fixtures,\n );\n return result.then(\n (val: T) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); return val; },\n (err: unknown) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); throw err; },\n ) as T;\n }\n\n // ── Sync callbacks or no-fixture context: existing behaviour ─────────────\n try {\n const result = ctx.fixtures !== undefined ? body(ctx.fixtures as PlaywrightFixtures) : body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncAnnotationToTest(); throw err; },\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 return stepMarker;\n}\n\n// ============================================================================\n// story.init() - Playwright-specific\n// ============================================================================\n\nfunction isTestInfo(x: unknown): x is TestInfo {\n return (\n typeof x === 'object' &&\n x !== null &&\n 'title' in x &&\n 'annotations' in x &&\n Array.isArray((x as TestInfo).annotations)\n );\n}\n\n/** init(testInfo) or init(fixtures, testInfo) or init(testInfo, { fixtures }). */\nfunction init(\n first: TestInfo | unknown,\n second?: StoryOptions | TestInfo,\n third?: StoryOptions,\n): void {\n let testInfo: TestInfo;\n let options: StoryOptions | undefined;\n let fixtures: unknown;\n\n if (second !== undefined && isTestInfo(second)) {\n fixtures = first;\n testInfo = second;\n options = third;\n } else {\n testInfo = first as TestInfo;\n options = second;\n fixtures = options?.fixtures;\n }\n\n const meta: StoryMeta = {\n scenario: testInfo.title,\n steps: [],\n suitePath: extractSuitePath(testInfo),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n covers: options?.covers,\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // Flag the scenario so the reporter promotes the Playwright screen recording\n // into a featured inline video doc entry (see reporter onTestEnd).\n if (options?.featureVideo) {\n meta.meta = { ...meta.meta, featureVideo: true };\n }\n\n // OTel bridge: detect active span, flow data bidirectionally\n const traceUrlTemplate =\n options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId + docs from the active span.\n applyTraceToMeta(meta, otelCtx.traceId, otelCtx.spanId, traceUrlTemplate);\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.map((t) => typeof t === 'string' ? t : t.id));\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 // ── Feature: Tag sync (v1.43) ─────────────────────────────────────────────\n // Sync story tags to Playwright's native annotation system so they appear in\n // UI Mode tag filters and the HTML reporter's tag display.\n for (const tag of options?.tags ?? []) {\n testInfo.annotations.push({ type: 'tag', description: tag });\n }\n\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n fixtures: fixtures as Record<string, unknown> | undefined,\n traceUrlTemplate,\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 * Bridge an OTel trace into the story's meta + docs: structured `otel` meta, a\n * \"Trace ID\" key-value, and a \"View Trace\" link when the template resolves.\n * Idempotent — once a traceId is recorded it is not overwritten, so the\n * active-span path in init() and the explicit path in attachSpans() compose\n * without duplicating entries.\n */\nfunction applyTraceToMeta(\n meta: StoryMeta,\n traceId: string,\n spanId: string | undefined,\n template: string | undefined,\n): void {\n const existing = (meta.meta as { otel?: { traceId?: string } } | undefined)\n ?.otel;\n if (existing?.traceId) return;\n\n meta.meta = { ...meta.meta, otel: { traceId, spanId } };\n meta.docs = meta.docs ?? [];\n meta.docs.push({\n kind: 'kv',\n label: 'Trace ID',\n value: traceId,\n phase: 'runtime',\n });\n const url = resolveTraceUrl(template, traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\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: (fixtures: PlaywrightFixtures) => T): T;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction fn<T>(keyword: StepKeyword, text: string, body: (...args: any[]) => T): T {\n const ctx = getContext();\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 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 syncAnnotationToTest();\n\n const start = performance.now();\n try {\n const result = ctx.fixtures !== undefined ? body(ctx.fixtures as PlaywrightFixtures) : 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// story.screenshot() — capture-and-attach, or attach-an-existing-file\n// ============================================================================\n\n/**\n * Two ways to attach a screenshot:\n * - `story.screenshot({ page, alt })` captures a fresh screenshot and inlines\n * it directly from the in-memory buffer — no filesystem round-trip, so\n * there's no path to fall out of sync and nothing for Playwright's output\n * cleanup to delete before the report is built. Prefer this form.\n * - `story.screenshot({ path, alt })` attaches a screenshot that already\n * exists on disk (e.g. one taken earlier for another purpose). The caller\n * is responsible for making sure something wrote a file to `path` first.\n */\nfunction screenshotImpl(options: ScreenshotOptions & { page: Page }, children?: DocEntry[]): Promise<DocEntry>;\nfunction screenshotImpl(\n options: ScreenshotOptions & { path: string; page?: undefined },\n children?: DocEntry[],\n): DocEntry;\nfunction screenshotImpl(options: ScreenshotOptions, children?: DocEntry[]): DocEntry | Promise<DocEntry> {\n if (options.page) {\n return options.page\n .screenshot(options.path ? { path: options.path, fullPage: options.fullPage ?? true } : { fullPage: options.fullPage ?? true })\n .then((buffer) => {\n const dataUri = `data:image/png;base64,${buffer.toString('base64')}`;\n return attachDoc({ kind: 'screenshot', path: dataUri, alt: options.alt, phase: 'runtime' }, children);\n });\n }\n if (!options.path) {\n throw new Error('story.screenshot() requires either `path` (an existing file) or `page` (to capture one).');\n }\n // Inline file bytes as a `data:` URI so the screenshot survives Playwright's\n // per-test outputDir cleanup (passing tests have their `test-results/<test>/`\n // directory deleted before the formatter runs). Falls back to the original\n // path for remote URLs or unreadable files.\n const resolvedPath = inlineScreenshotIfPossible(options.path);\n return attachDoc({ kind: 'screenshot', path: resolvedPath, alt: options.alt, phase: 'runtime' }, children);\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, 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 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: screenshotImpl,\n\n video(options: VideoOptions, children?: DocEntry[]): DocEntry {\n // Unlike screenshots, video bytes are never inlined as a data URI — they're\n // too large. The path is kept as-is so the formatter's asset bundler copies\n // the file into the report/docs site. Pass a path relative to the output, or\n // use `featureVideo: true` on story.init() to auto-promote the Playwright\n // recording instead.\n warnIfAbsoluteVideoPathMissing(options.path);\n return attachDoc(\n { kind: 'video', path: options.path, caption: options.caption, poster: options.poster, phase: 'runtime' },\n children,\n );\n },\n\n /**\n * Embed HTML in a sandboxed iframe in the current step or story-level docs.\n * Exactly one of path/url/content is required. Local files are inlined so\n * the report stays self-contained — the HTML must therefore be\n * self-contained too (use your tool's single-file mode); relative sub-asset\n * references won't resolve.\n * @example story.html({ path: './coverage/index.html', title: 'Coverage' })\n * @example story.html({ url: 'https://dash.example.com/run/42', height: 600 })\n * @example story.html({ content: chartHtml, title: 'Latency chart' })\n */\n html(options: HtmlOptions, children?: DocEntry[]): DocEntry {\n return attachDoc(buildHtmlEntry(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 // ── Feature: Console capture (v1.56) ────────────────────────────────────\n /**\n * Snapshot the current page console messages (and optionally page errors)\n * and attach them as a code doc entry.\n *\n * Uses page.consoleMessages() and page.pageErrors() introduced in Playwright v1.56.\n * Safe to call on any Playwright version – silently produces empty output if the\n * APIs are not present.\n *\n * @example\n * story.when('the form is submitted', async ({ page }) => {\n * await page.click('#submit');\n * story.console({ page, label: 'Submit console output' });\n * });\n */\n console(options: ConsoleOptions, children?: DocEntry[]): DocEntry {\n const p = options.page as {\n consoleMessages?: () => Array<{ type(): string; text(): string }>;\n pageErrors?: () => Error[];\n };\n\n const lines: string[] = [];\n\n if (typeof p?.consoleMessages === 'function') {\n for (const msg of p.consoleMessages()) {\n lines.push(`[${msg.type()}] ${msg.text()}`);\n }\n }\n\n if (options.includeErrors === true && typeof p?.pageErrors === 'function') {\n for (const err of p.pageErrors()) {\n lines.push(`[error] ${err.message}`);\n }\n }\n\n return attachDoc(\n {\n kind: 'code',\n label: options.label ?? 'Console',\n content: lines.length > 0 ? lines.join('\\n') : '(no console output)',\n lang: 'log',\n phase: 'runtime',\n },\n children,\n );\n },\n\n /**\n * Capture current page runtime errors and attach as a structured doc entry.\n *\n * Collects from Playwright v1.56+ page.pageErrors() and page.consoleMessages().\n * This is intentionally snapshot-based so tests can call it at critical points\n * (after submit, after navigation) and keep evidence near relevant steps.\n */\n observePageErrors(options: ObservePageErrorsOptions, children?: DocEntry[]): DocEntry {\n const p = options.page as {\n consoleMessages?: () => Array<{ type(): string; text(): string }>;\n pageErrors?: () => Error[];\n };\n const ignore = options.ignore ?? [];\n const lines: string[] = [];\n\n if (typeof p?.pageErrors === 'function') {\n for (const err of p.pageErrors()) {\n const msg = err?.message ?? String(err);\n if (!ignore.some((rx) => rx.test(msg))) lines.push(`[pageerror] ${msg}`);\n }\n }\n if (typeof p?.consoleMessages === 'function') {\n for (const msg of p.consoleMessages()) {\n if (msg.type() !== 'error') continue;\n const text = msg.text();\n if (!ignore.some((rx) => rx.test(text))) lines.push(`[console.error] ${text}`);\n }\n }\n\n return attachDoc(\n {\n kind: 'code',\n label: options.label ?? 'Browser Runtime Errors',\n content: lines.length > 0 ? lines.join('\\n') : '(no runtime errors observed)',\n lang: 'log',\n phase: 'runtime',\n },\n children,\n );\n },\n\n // Attachments\n attach: playwrightAttach,\n\n // OTel span attachment\n attachSpans(\n spans: ReadonlyArray<Record<string, unknown>>,\n options?: { traceId?: string; spanId?: string },\n ): void {\n if (!activeTestInfo) return;\n const existing = activeTestInfo.annotations.find(\n (a) => a.type === 'story-otel-spans',\n );\n const description = JSON.stringify(spans);\n if (existing) {\n existing.description = description;\n } else {\n activeTestInfo.annotations.push({\n type: 'story-otel-spans',\n description,\n });\n }\n // Capture-then-attach: wire the trace badge + \"View Trace\" link when the\n // trace was created after init() (so the init-time bridge couldn't see it).\n if (options?.traceId && activeContext) {\n applyTraceToMeta(\n activeContext.meta,\n options.traceId,\n options.spanId,\n activeContext.traceUrlTemplate,\n );\n syncAnnotationToTest();\n }\n },\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n 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","/**\n * Playwright-native step execution helper.\n *\n * Centralises the cross-cutting concerns for fixture-aware step callbacks:\n * 1. page.screencast.showChapter() – narrated chapter markers in video recordings (v1.59)\n * 2. test.step() – TestStepInfo access + trace/report visibility (v1.51)\n * 3. context.tracing.group() – BDD phase grouping in trace viewer (v1.49)\n *\n * Used for callbacks that are either async functions or expect TestStepInfo.\n * Sync callbacks that don't need TestStepInfo follow the faster sync path.\n * All three integrations degrade gracefully if the API is absent.\n */\n\nimport { test } from '@playwright/test';\nimport type { TestStepInfo } from '@playwright/test';\n\nexport type { TestStepInfo };\n\n/** Async step callback that optionally receives TestStepInfo as a second argument. */\nexport type AsyncStepCallback<T = unknown> = (\n fixtures: Record<string, unknown>,\n step?: TestStepInfo,\n) => Promise<T>;\n\n/**\n * Execute an async step callback with full Playwright-native integration.\n *\n * Call order:\n * 1. page.screencast.showChapter(label) – sets chapter in the recording before the step runs\n * 2. test.step(label, …) – wraps execution for trace/report visibility\n * 3. context.tracing.group(label, …) – groups trace actions under the step label\n * 4. body(fixtures, stepInfo) – user callback with injected TestStepInfo\n */\nexport async function runStep<T>(\n label: string,\n body: AsyncStepCallback<T>,\n fixtures: Record<string, unknown>,\n): Promise<T> {\n const page = fixtures.page as Record<string, unknown> | undefined;\n // Derive context from fixtures or from page.context() (Playwright sync method)\n const context =\n (fixtures.context as Record<string, unknown> | undefined) ??\n (typeof (page as { context?: () => Record<string, unknown> })?.context === 'function'\n ? (page as { context: () => Record<string, unknown> }).context()\n : undefined);\n\n // ── Feature 1: Screencast chapter (v1.59) ─────────────────────────────────\n // Show the chapter BEFORE the step body runs so the recording reflects the\n // BDD step title at the right moment. Silently skipped on older Playwright.\n const screencast = page?.screencast as\n | { showChapter?: (label: string) => Promise<void> }\n | undefined;\n if (screencast?.showChapter) {\n try {\n await screencast.showChapter(label);\n } catch {\n // Graceful degradation: screencast not started or API unavailable\n }\n }\n\n // ── Feature 2: test.step (v1.51) + Feature 3: tracing.group (v1.49) ──────\n // test.step provides TestStepInfo for the callback and makes the step visible\n // in the Playwright trace viewer and HTML report as a named action.\n // tracing.group inside it groups the step's child actions under the label.\n return test.step(label, async (stepInfo) => {\n const tracing = context?.tracing as\n | { group?: <R>(label: string, fn: () => Promise<R>) => Promise<R> }\n | undefined;\n\n if (tracing?.group) {\n // Track whether body was invoked to avoid double-execution\n // when body throws inside tracing.group\n let bodyInvoked = false;\n try {\n return await tracing.group(label, async () => {\n bodyInvoked = true;\n return body(fixtures, stepInfo);\n });\n } catch (e) {\n // If body was invoked, it threw - re-throw (don't retry)\n if (bodyInvoked) throw e;\n // Otherwise, tracing.group itself threw (e.g., tracing not recording)\n // Fall back to calling body without tracing.group\n return body(fixtures, stepInfo);\n }\n }\n\n return body(fixtures, stepInfo);\n });\n}\n\n/**\n * Returns true if fn is an async function (declared with `async`).\n * Used along with callback arity to decide whether to route a step callback through runStep().\n *\n * Callbacks are routed through runStep() if they are async functions OR if they\n * have arity >= 2 (meaning they expect TestStepInfo as the second argument).\n */\nexport function isAsyncFunction(fn: unknown): boolean {\n return (\n typeof fn === 'function' &&\n (fn as { constructor?: { name?: string } }).constructor?.name === 'AsyncFunction'\n );\n}\n","/**\n * Playwright Executable Stories\n *\n * BDD-style executable documentation for Playwright Test.\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\n// Story API\nimport { story } from './story-api';\nexport { story };\nexport type { Story } from './story-api';\n\n// Top-level step helpers (framework contract)\nexport const given = story.given;\nexport const when = story.when;\nexport const then = story.then;\nexport const and = story.and;\nexport const but = story.but;\n\n// Re-export types from local types module\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n TicketInput,\n NormalizedTicket,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n VideoOptions,\n CustomOptions,\n ConsoleOptions,\n ObservePageErrorsOptions,\n AttachmentOptions,\n} from './types';\n\n// TestStepInfo re-exported from @playwright/test for consumer convenience\nexport type { TestStepInfo } from './step-runner';\n"],"mappings":";AA4BA,SAAS,qBAAqB;AAC9B,YAAY,QAAQ;AACpB,YAAY,UAAU;AAEtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACvBP,SAAS,YAAY;AAoBrB,eAAsB,QACpB,OACA,MACA,UACY;AACZ,QAAM,OAAO,SAAS;AAEtB,QAAM,UACH,SAAS,YACT,OAAQ,MAAsD,YAAY,aACtE,KAAoD,QAAQ,IAC7D;AAKN,QAAM,aAAa,MAAM;AAGzB,MAAI,YAAY,aAAa;AAC3B,QAAI;AACF,YAAM,WAAW,YAAY,KAAK;AAAA,IACpC,QAAQ;AAAA,IAER;AAAA,EACF;AAMA,SAAO,KAAK,KAAK,OAAO,OAAO,aAAa;AAC1C,UAAM,UAAU,SAAS;AAIzB,QAAI,SAAS,OAAO;AAGlB,UAAI,cAAc;AAClB,UAAI;AACF,eAAO,MAAM,QAAQ,MAAM,OAAO,YAAY;AAC5C,wBAAc;AACd,iBAAO,KAAK,UAAU,QAAQ;AAAA,QAChC,CAAC;AAAA,MACH,SAAS,GAAG;AAEV,YAAI,YAAa,OAAM;AAGvB,eAAO,KAAK,UAAU,QAAQ;AAAA,MAChC;AAAA,IACF;AAEA,WAAO,KAAK,UAAU,QAAQ;AAAA,EAChC,CAAC;AACH;AASO,SAAS,gBAAgBA,KAAsB;AACpD,SACE,OAAOA,QAAO,cACbA,IAA2C,aAAa,SAAS;AAEtE;;;ADMA,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,iBACP,QACgC;AAChC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAAM,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AACpD,SAAO,IAAI,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,EAAE,IAAI,EAAE,IAAI,CAAE;AAC/D;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,QAAI,KAAK,WAAW,MAAM;AACxB,YAAM,IAAI;AAAA,QACR;AAAA,MAIF;AAAA,IACF;AACA,QAAI,CAAC,KAAK,WAAW,MAAM;AACzB,YAAM,IAAI,MAAM,sFAAsF;AAAA,IACxG;AACA,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAKN,MAAM,2BAA2B,KAAK,WAAW,IAAI;AAAA,MACrD,KAAK,KAAK,WAAW;AAAA,MACrB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,OAAO;AACd,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,KAAK,MAAM;AAAA,MACjB,SAAS,KAAK,MAAM;AAAA,MACpB,QAAQ,KAAK,MAAM;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,KAAK,MAAM;AACb,YAAQ,KAAK,eAAe,KAAK,IAAI,CAAC;AAAA,EACxC;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;AAmBA,SAAS,eAAe,SAAgC;AACtD,MAAI,EAAE,MAAM,UAAU,QAAQ,IAAI;AAKlC,MACE,aAAa,UACb,QAAQ,QAAQ,UAChB,YAAY,UACZ,CAAC,YAAY,KAAK,QAAQ,KACrB,gBAAW,QAAQ,GACxB;AACA,QAAI;AACF,UAAO,cAAW,QAAQ,GAAG;AAC3B,kBAAa,gBAAa,UAAU,MAAM;AAC1C,mBAAW;AAAA,MACb;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,kBAAkB,EAAE,GAAG,SAAS,MAAM,UAAU,QAAQ,CAAC;AAClE;AAEA,IAAM,yBAAiD;AAAA,EACrD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AACP;AAaA,SAAS,2BAA2B,UAA0B;AAC5D,MAAI,sBAAsB,KAAK,QAAQ,EAAG,QAAO;AACjD,QAAM,MAAW,aAAQ,QAAQ,EAAE,MAAM,CAAC,EAAE,YAAY;AACxD,QAAM,OAAO,uBAAuB,GAAG;AACvC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,QAAI,CAAI,cAAW,QAAQ,GAAG;AAC5B,gCAA0B,QAAQ;AAClC,aAAO;AAAA,IACT;AACA,UAAM,MAAS,gBAAa,QAAQ;AACpC,WAAO,QAAQ,IAAI,WAAW,IAAI,SAAS,QAAQ,CAAC;AAAA,EACtD,SAAS,KAAK;AACZ,8BAA0B,UAAU,GAAG;AACvC,WAAO;AAAA,EACT;AACF;AAEA,SAAS,0BAA0B,UAAkB,OAAuB;AAC1E,QAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,QAAQ,OAAO,KAAK,IAAI;AACtF,UAAQ;AAAA,IACN,uEAAuE,QAAQ,iSAK5E,eAAe,WAAW,YAAY,KAAK;AAAA,EAChD;AACF;AAcA,SAAS,+BAA+B,UAAwB;AAC9D,MAAI,sBAAsB,KAAK,QAAQ,EAAG;AAC1C,MAAI,CAAM,gBAAW,QAAQ,EAAG;AAChC,MAAO,cAAW,QAAQ,EAAG;AAC7B,UAAQ;AAAA,IACN,mDAAmD,QAAQ;AAAA,EAG7D;AACF;AAEA,SAAS,UAAU,OAAiB,UAAiC;AACnE,QAAM,MAAM,WAAW;AACvB,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,UAAM,WAAW;AACjB,UAAM,WAAW,IAAI,IAAc,QAAQ;AAC3C,UAAM,aAAa,CAAC,SAAqB,KAAK,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAE5E,QAAI,KAAK,OAAO,WAAW,IAAI,KAAK,QAAQ,CAAC,CAAC;AAC9C,eAAW,QAAQ,IAAI,KAAK,OAAO;AACjC,UAAI,KAAK,KAAM,MAAK,OAAO,WAAW,KAAK,IAAI;AAAA,IACjD;AAAA,EACF;AACA,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;AACA,uBAAqB;AACrB,SAAO;AACT;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;AAK9C,WAAS,WAAc,MAAc,YAAyE;AAC5G,UAAM,MAAM,WAAW;AACvB,UAAM,aAAa,OAAO,eAAe;AACzC,UAAM,kBAAkB,MAAM,QAAQ,UAAU;AAEhD,UAAM,mBACH,YAAY,WAAW,YAAY,UAAU,YAAY,WAC1D,IAAI,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,IAC5C,QACA;AAEN,QAAI,WAAuB,CAAC;AAC5B,QAAI,CAAC,cAAc,CAAC,mBAAmB,YAAY;AACjD,iBAAW,0BAA0B,UAAuB;AAAA,IAC9D;AAEA,UAAM,OAAkB;AAAA,MACtB,IAAI,QAAQ,IAAI,aAAa;AAAA,MAC7B,SAAS;AAAA,MACT;AAAA,MACA,MAAM;AAAA,MACN,GAAI,aAAa,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,IACxC;AAEA,QAAI,KAAK,MAAM,KAAK,IAAI;AACxB,QAAI,cAAc;AAClB,yBAAqB;AAGrB,QAAI,iBAAiB;AACnB,YAAM,WAAW;AACjB,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,WAAW,IAAI,IAAc,QAAQ;AAE3C,YAAI,KAAK,QAAQ,IAAI,KAAK,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAEpE,mBAAW,YAAY,IAAI,KAAK,OAAO;AACrC,cAAI,aAAa,QAAQ,SAAS,MAAM;AACtC,qBAAS,OAAO,SAAS,KAAK,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,CAAC;AAAA,UAC9D;AAAA,QACF;AACA,aAAK,OAAO,CAAC,GAAI,KAAK,QAAQ,CAAC,GAAI,GAAG,QAAQ;AAAA,MAChD;AACA,2BAAqB;AACrB;AAAA,IACF;AAEA,QAAI,CAAC,WAAY;AAGjB,UAAM,OAAO;AACb,UAAM,QAAQ,GAAG,KAAK,OAAO,KAAK,IAAI;AACtC,UAAM,QAAQ,YAAY,IAAI;AAO9B,QAAI,IAAI,aAAa,WAAc,gBAAgB,IAAI,KAAK,KAAK,UAAU,IAAI;AAC7E,YAAM,WAAW,IAAI;AACrB,YAAM,SAAS;AAAA,QACb;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,MACF;AACA,aAAO,OAAO;AAAA,QACZ,CAAC,QAAW;AAAE,eAAK,aAAa,YAAY,IAAI,IAAI;AAAO,+BAAqB;AAAG,iBAAO;AAAA,QAAK;AAAA,QAC/F,CAAC,QAAiB;AAAE,eAAK,aAAa,YAAY,IAAI,IAAI;AAAO,+BAAqB;AAAG,gBAAM;AAAA,QAAK;AAAA,MACtG;AAAA,IACF;AAGA,QAAI;AACF,YAAM,SAAS,IAAI,aAAa,SAAY,KAAK,IAAI,QAA8B,IAAI,KAAK;AAC5F,UAAI,kBAAkB,SAAS;AAC7B,eAAO,OAAO;AAAA,UACZ,CAAC,QAAQ;AAAE,iBAAK,aAAa,YAAY,IAAI,IAAI;AAAO,iCAAqB;AAAG,mBAAO;AAAA,UAAK;AAAA,UAC5F,CAAC,QAAQ;AAAE,iBAAK,aAAa,YAAY,IAAI,IAAI;AAAO,iCAAqB;AAAG,kBAAM;AAAA,UAAK;AAAA,QAC7F;AAAA,MACF;AACA,WAAK,aAAa,YAAY,IAAI,IAAI;AACtC,2BAAqB;AACrB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,aAAa,YAAY,IAAI,IAAI;AACtC,2BAAqB;AACrB,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,WAAW,GAA2B;AAC7C,SACE,OAAO,MAAM,YACb,MAAM,QACN,WAAW,KACX,iBAAiB,KACjB,MAAM,QAAS,EAAe,WAAW;AAE7C;AAGA,SAAS,KACP,OACA,QACA,OACM;AACN,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,MAAI,WAAW,UAAa,WAAW,MAAM,GAAG;AAC9C,eAAW;AACX,eAAW;AACX,cAAU;AAAA,EACZ,OAAO;AACL,eAAW;AACX,cAAU;AACV,eAAW,SAAS;AAAA,EACtB;AAEA,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,QAAQ,SAAS;AAAA,IACjB,MAAM,SAAS;AAAA,IACf,aAAa;AAAA,EACf;AAIA,MAAI,SAAS,cAAc;AACzB,SAAK,OAAO,EAAE,GAAG,KAAK,MAAM,cAAc,KAAK;AAAA,EACjD;AAGA,QAAM,mBACJ,SAAS,oBAAoB,QAAQ,IAAI;AAC3C,QAAM,UAAU,wBAAwB;AACxC,MAAI,SAAS;AAEX,qBAAiB,MAAM,QAAQ,SAAS,QAAQ,QAAQ,gBAAgB;AAGxE,QAAI;AAEF,YAAM,SAAS,YAAY,QACrB,OAAO,eAAe,cAAc,UAAU,UAAU,KAAK;AACnE,YAAM,MAAM,cAAc,MAAO;AACjC,YAAM,MAAM,IAAI,oBAAoB;AACpC,YAAM,OAAO,IAAI,OAAO,gBAAgB;AACxC,UAAI,MAAM;AACR,aAAK,aAAa,kBAAkB,SAAS,KAAK;AAClD,YAAI,SAAS,MAAM,OAAQ,MAAK,aAAa,cAAc,QAAQ,IAAI;AACvE,YAAI,SAAS,QAAQ;AACnB,gBAAM,UAAU,MAAM,QAAQ,QAAQ,MAAM,IAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM;AAChF,eAAK,aAAa,iBAAiB,QAAQ,IAAI,CAAC,MAAM,OAAO,MAAM,WAAW,IAAI,EAAE,EAAE,CAAC;AAAA,QACzF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAA2B;AAAA,EACrC;AAEA,WAAS,YAAY,KAAK;AAAA,IACxB,MAAM;AAAA,IACN,aAAa,KAAK,UAAU,IAAI;AAAA,EAClC,CAAC;AAKD,aAAW,OAAO,SAAS,QAAQ,CAAC,GAAG;AACrC,aAAS,YAAY,KAAK,EAAE,MAAM,OAAO,aAAa,IAAI,CAAC;AAAA,EAC7D;AAEA,kBAAgB;AAAA,IACd;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb,aAAa,CAAC;AAAA,IACd,cAAc,oBAAI,IAAI;AAAA,IACtB,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF;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;AASA,SAAS,iBACP,MACA,SACA,QACA,UACM;AACN,QAAM,WAAY,KAAK,MACnB;AACJ,MAAI,UAAU,QAAS;AAEvB,OAAK,OAAO,EAAE,GAAG,KAAK,MAAM,MAAM,EAAE,SAAS,OAAO,EAAE;AACtD,OAAK,OAAO,KAAK,QAAQ,CAAC;AAC1B,OAAK,KAAK,KAAK;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT,CAAC;AACD,QAAM,MAAM,gBAAgB,UAAU,OAAO;AAC7C,MAAI,KAAK;AACP,SAAK,KAAK,KAAK,EAAE,MAAM,QAAQ,OAAO,cAAc,KAAK,OAAO,UAAU,CAAC;AAAA,EAC7E;AACF;AAYA,SAAS,GAAM,SAAsB,MAAc,MAAgC;AACjF,QAAM,MAAM,WAAW;AACvB,QAAM,mBACH,YAAY,WAAW,YAAY,UAAU,YAAY,WAC1D,IAAI,KAAK,MAAM,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,IAC5C,QACA;AACN,QAAM,OAAkB;AAAA,IACtB,IAAI,QAAQ,IAAI,aAAa;AAAA,IAC7B,SAAS;AAAA,IACT;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,IAAI,aAAa,SAAY,KAAK,IAAI,QAA8B,IAAI,KAAK;AAC5F,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;AAqBA,SAAS,eAAe,SAA4B,UAAqD;AACvG,MAAI,QAAQ,MAAM;AAChB,WAAO,QAAQ,KACZ,WAAW,QAAQ,OAAO,EAAE,MAAM,QAAQ,MAAM,UAAU,QAAQ,YAAY,KAAK,IAAI,EAAE,UAAU,QAAQ,YAAY,KAAK,CAAC,EAC7H,KAAK,CAAC,WAAW;AAChB,YAAM,UAAU,yBAAyB,OAAO,SAAS,QAAQ,CAAC;AAClE,aAAO,UAAU,EAAE,MAAM,cAAc,MAAM,SAAS,KAAK,QAAQ,KAAK,OAAO,UAAU,GAAG,QAAQ;AAAA,IACtG,CAAC;AAAA,EACL;AACA,MAAI,CAAC,QAAQ,MAAM;AACjB,UAAM,IAAI,MAAM,0FAA0F;AAAA,EAC5G;AAKA,QAAM,eAAe,2BAA2B,QAAQ,IAAI;AAC5D,SAAO,UAAU,EAAE,MAAM,cAAc,MAAM,cAAc,KAAK,QAAQ,KAAK,OAAO,UAAU,GAAG,QAAQ;AAC3G;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,MAAc,UAAiC;AAClD,WAAO,UAAU,EAAE,MAAM,QAAQ,MAAM,OAAO,UAAU,GAAG,QAAQ;AAAA,EACrE;AAAA,EAEA,IAAI,MAAyB,UAAiC;AAC5D,UAAM,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAChD,WAAO,UAAU,EAAE,MAAM,OAAO,OAAO,OAAO,UAAU,GAAG,QAAQ;AAAA,EACrE;AAAA,EAEA,GAAG,SAAoB,UAAiC;AACtD,WAAO,UAAU,EAAE,MAAM,MAAM,OAAO,QAAQ,OAAO,OAAO,QAAQ,OAAO,OAAO,UAAU,GAAG,QAAQ;AAAA,EACzG;AAAA,EAEA,KAAK,SAAsB,UAAiC;AAC1D,UAAM,UAAU,KAAK,UAAU,QAAQ,OAAO,MAAM,CAAC;AACrD,WAAO,UAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,SAAS,MAAM,QAAQ,OAAO,UAAU,GAAG,QAAQ;AAAA,EAC5G;AAAA,EAEA,KAAK,SAAsB,UAAiC;AAC1D,WAAO,UAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ,MAAM,OAAO,UAAU,GAAG,QAAQ;AAAA,EACnI;AAAA,EAEA,MAAM,SAAuB,UAAiC;AAC5D,WAAO,UAAU,EAAE,MAAM,SAAS,OAAO,QAAQ,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ,MAAM,OAAO,UAAU,GAAG,QAAQ;AAAA,EACpI;AAAA,EAEA,KAAK,SAAsB,UAAiC;AAC1D,WAAO,UAAU,EAAE,MAAM,QAAQ,OAAO,QAAQ,OAAO,KAAK,QAAQ,KAAK,OAAO,UAAU,GAAG,QAAQ;AAAA,EACvG;AAAA,EAEA,QAAQ,SAAyB,UAAiC;AAChE,WAAO,UAAU,EAAE,MAAM,WAAW,OAAO,QAAQ,OAAO,UAAU,QAAQ,UAAU,OAAO,UAAU,GAAG,QAAQ;AAAA,EACpH;AAAA,EAEA,QAAQ,SAAyB,UAAiC;AAChE,WAAO,UAAU,EAAE,MAAM,WAAW,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,OAAO,UAAU,GAAG,QAAQ;AAAA,EAC5G;AAAA,EAEA,YAAY;AAAA,EAEZ,MAAM,SAAuB,UAAiC;AAM5D,mCAA+B,QAAQ,IAAI;AAC3C,WAAO;AAAA,MACL,EAAE,MAAM,SAAS,MAAM,QAAQ,MAAM,SAAS,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,OAAO,UAAU;AAAA,MACxG;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,KAAK,SAAsB,UAAiC;AAC1D,WAAO,UAAU,eAAe,OAAO,GAAG,QAAQ;AAAA,EACpD;AAAA,EAEA,OAAO,SAAwB,UAAiC;AAC9D,WAAO,UAAU,EAAE,MAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,QAAQ,MAAM,OAAO,UAAU,GAAG,QAAQ;AAAA,EACzG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,QAAQ,SAAyB,UAAiC;AAChE,UAAM,IAAI,QAAQ;AAKlB,UAAM,QAAkB,CAAC;AAEzB,QAAI,OAAO,GAAG,oBAAoB,YAAY;AAC5C,iBAAW,OAAO,EAAE,gBAAgB,GAAG;AACrC,cAAM,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,EAAE;AAAA,MAC5C;AAAA,IACF;AAEA,QAAI,QAAQ,kBAAkB,QAAQ,OAAO,GAAG,eAAe,YAAY;AACzE,iBAAW,OAAO,EAAE,WAAW,GAAG;AAChC,cAAM,KAAK,WAAW,IAAI,OAAO,EAAE;AAAA,MACrC;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,OAAO,QAAQ,SAAS;AAAA,QACxB,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,QAC/C,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAkB,SAAmC,UAAiC;AACpF,UAAM,IAAI,QAAQ;AAIlB,UAAM,SAAS,QAAQ,UAAU,CAAC;AAClC,UAAM,QAAkB,CAAC;AAEzB,QAAI,OAAO,GAAG,eAAe,YAAY;AACvC,iBAAW,OAAO,EAAE,WAAW,GAAG;AAChC,cAAM,MAAM,KAAK,WAAW,OAAO,GAAG;AACtC,YAAI,CAAC,OAAO,KAAK,CAAC,OAAO,GAAG,KAAK,GAAG,CAAC,EAAG,OAAM,KAAK,eAAe,GAAG,EAAE;AAAA,MACzE;AAAA,IACF;AACA,QAAI,OAAO,GAAG,oBAAoB,YAAY;AAC5C,iBAAW,OAAO,EAAE,gBAAgB,GAAG;AACrC,YAAI,IAAI,KAAK,MAAM,QAAS;AAC5B,cAAM,OAAO,IAAI,KAAK;AACtB,YAAI,CAAC,OAAO,KAAK,CAAC,OAAO,GAAG,KAAK,IAAI,CAAC,EAAG,OAAM,KAAK,mBAAmB,IAAI,EAAE;AAAA,MAC/E;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,OAAO,QAAQ,SAAS;AAAA,QACxB,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,QAC/C,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,QAAQ;AAAA;AAAA,EAGR,YACE,OACA,SACM;AACN,QAAI,CAAC,eAAgB;AACrB,UAAM,WAAW,eAAe,YAAY;AAAA,MAC1C,CAAC,MAAM,EAAE,SAAS;AAAA,IACpB;AACA,UAAM,cAAc,KAAK,UAAU,KAAK;AACxC,QAAI,UAAU;AACZ,eAAS,cAAc;AAAA,IACzB,OAAO;AACL,qBAAe,YAAY,KAAK;AAAA,QAC9B,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,SAAS,WAAW,eAAe;AACrC;AAAA,QACE,cAAc;AAAA,QACd,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB;AACA,2BAAqB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAGA,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;;;AE3/BO,IAAM,QAAQ,MAAM;AACpB,IAAM,OAAO,MAAM;AACnB,IAAM,OAAO,MAAM;AACnB,IAAM,MAAM,MAAM;AAClB,IAAM,MAAM,MAAM;","names":["fn"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executable-stories-playwright",
3
- "version": "8.5.7",
3
+ "version": "8.6.1",
4
4
  "description": "BDD-style executable stories for Playwright Test with documentation generation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -33,7 +33,7 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "executable-stories-formatters": "1.0.0"
36
+ "executable-stories-formatters": "1.1.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@opentelemetry/api": "^1.9.1",