executable-stories-playwright 4.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Reporter, FullConfig, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
1
+ import { Reporter, FullConfig, TestCase, TestResult, TestStep, FullResult } from '@playwright/test/reporter';
2
2
  import { FormatterOptions } from 'executable-stories-formatters';
3
3
  export { ColocatedStyle, FormatterOptions, OutputFormat, OutputMode, OutputRule } from 'executable-stories-formatters';
4
4
 
@@ -18,8 +18,14 @@ declare class StoryReporter implements Reporter {
18
18
  private packageVersion;
19
19
  private gitSha;
20
20
  private projectRoot;
21
+ private autotel;
22
+ private testSpans;
23
+ private stepSpanStacks;
21
24
  constructor(options?: StoryReporterOptions);
22
25
  onBegin(config: FullConfig): void;
26
+ onTestBegin(test: TestCase): void;
27
+ onStepBegin(test: TestCase, _result: TestResult, step: TestStep): void;
28
+ onStepEnd(test: TestCase, _result: TestResult, step: TestStep): void;
23
29
  onTestEnd(test: TestCase, result: TestResult): void;
24
30
  onEnd(_result: FullResult): Promise<void>;
25
31
  }
package/dist/reporter.js CHANGED
@@ -1,12 +1,104 @@
1
1
  // src/reporter.ts
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+
5
+ // src/otel-reporter-spans.ts
6
+ import { createRequire } from "module";
7
+ function tryLoadAutotel() {
8
+ try {
9
+ const reqUrl = import.meta.url ?? (typeof __filename !== "undefined" ? `file://${__filename}` : void 0);
10
+ if (!reqUrl) return null;
11
+ const req = createRequire(reqUrl);
12
+ const autotel = req("autotel");
13
+ if (typeof autotel?.span !== "function") return null;
14
+ return autotel;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ function shouldInstrumentStep(step) {
20
+ return step.category === "test.step" || isStoryStep(step);
21
+ }
22
+ function isStoryStep(step) {
23
+ if (!step.title) return false;
24
+ return /^(Given|When|Then|And|But|Arrange|Act|Assert)\s/.test(step.title);
25
+ }
26
+ function mapToSpanStatus(status, SpanStatusCode) {
27
+ switch (status) {
28
+ case "passed":
29
+ case "skipped":
30
+ return { code: SpanStatusCode.UNSET };
31
+ case "failed":
32
+ case "timedOut":
33
+ case "interrupted":
34
+ return { code: SpanStatusCode.ERROR, message: status };
35
+ default:
36
+ return { code: SpanStatusCode.UNSET };
37
+ }
38
+ }
39
+ function createTestSpan(args, deps) {
40
+ let captured;
41
+ deps.autotel.span(`test: ${args.testTitle}`, (s) => {
42
+ captured = s;
43
+ s.setAttribute("test.name", args.testTitle);
44
+ if (args.suitePath?.length) {
45
+ s.setAttribute("test.suite", args.suitePath.join(" > "));
46
+ }
47
+ if (args.sourceFile) {
48
+ s.setAttribute("code.filepath", args.sourceFile);
49
+ }
50
+ if (args.sourceLine !== void 0) {
51
+ s.setAttribute("code.lineno", args.sourceLine);
52
+ }
53
+ });
54
+ const span = captured;
55
+ return {
56
+ endSpan(status, errorMessage) {
57
+ span.setAttribute("test.status", status);
58
+ const spanStatus = mapToSpanStatus(status, deps.autotel.SpanStatusCode);
59
+ if (errorMessage) {
60
+ spanStatus.message = errorMessage;
61
+ }
62
+ span.setStatus(spanStatus);
63
+ span.end();
64
+ }
65
+ };
66
+ }
67
+ function createStepSpan(args, deps) {
68
+ let captured;
69
+ deps.autotel.span(`step: ${args.stepTitle}`, (s) => {
70
+ captured = s;
71
+ s.setAttribute("test.step.name", args.stepTitle);
72
+ if (args.stepCategory) {
73
+ s.setAttribute("test.step.category", args.stepCategory);
74
+ }
75
+ });
76
+ const span = captured;
77
+ return {
78
+ endSpan(errorMessage) {
79
+ if (errorMessage) {
80
+ span.setStatus({
81
+ code: deps.autotel.SpanStatusCode.ERROR,
82
+ message: errorMessage
83
+ });
84
+ }
85
+ span.end();
86
+ }
87
+ };
88
+ }
89
+
90
+ // src/reporter.ts
4
91
  import {
5
92
  ReportGenerator,
6
93
  canonicalizeRun,
7
94
  readGitSha,
8
95
  readPackageVersion,
9
- detectCI
96
+ detectCI,
97
+ sendNotifications,
98
+ toCIInfo,
99
+ loadHistory,
100
+ updateHistory,
101
+ saveHistory
10
102
  } from "executable-stories-formatters";
11
103
  function toRelativePosix(absolutePath, projectRoot) {
12
104
  return path.relative(projectRoot, absolutePath).split(path.sep).join("/");
@@ -18,6 +110,9 @@ var StoryReporter = class {
18
110
  packageVersion;
19
111
  gitSha;
20
112
  projectRoot = process.cwd();
113
+ autotel = null;
114
+ testSpans = /* @__PURE__ */ new Map();
115
+ stepSpanStacks = /* @__PURE__ */ new Map();
21
116
  constructor(options = {}) {
22
117
  this.options = options;
23
118
  }
@@ -29,12 +124,82 @@ var StoryReporter = class {
29
124
  this.packageVersion = readPackageVersion(this.projectRoot);
30
125
  this.gitSha = readGitSha(this.projectRoot);
31
126
  }
127
+ this.autotel = tryLoadAutotel();
128
+ }
129
+ onTestBegin(test) {
130
+ if (!this.autotel) return;
131
+ const sourceFile = test.location?.file;
132
+ const sourceLine = test.location?.line;
133
+ const titlePath = test.titlePath();
134
+ const suitePath = titlePath.slice(1, -1);
135
+ const testTitle = titlePath[titlePath.length - 1] ?? test.title;
136
+ const handle = createTestSpan(
137
+ { testTitle, suitePath, sourceFile, sourceLine },
138
+ { autotel: this.autotel }
139
+ );
140
+ this.testSpans.set(test.id, handle);
141
+ this.stepSpanStacks.set(test.id, []);
142
+ }
143
+ onStepBegin(test, _result, step) {
144
+ if (!this.autotel) return;
145
+ if (!shouldInstrumentStep({ category: step.category, title: step.title }))
146
+ return;
147
+ const handle = createStepSpan(
148
+ { stepTitle: step.title, stepCategory: step.category },
149
+ { autotel: this.autotel }
150
+ );
151
+ const stack = this.stepSpanStacks.get(test.id);
152
+ if (stack) {
153
+ stack.push(handle);
154
+ }
155
+ }
156
+ onStepEnd(test, _result, step) {
157
+ if (!this.autotel) return;
158
+ if (!shouldInstrumentStep({ category: step.category, title: step.title }))
159
+ return;
160
+ const stack = this.stepSpanStacks.get(test.id);
161
+ if (stack && stack.length > 0) {
162
+ const handle = stack.pop();
163
+ handle.endSpan(step.error?.message);
164
+ }
32
165
  }
33
166
  onTestEnd(test, result) {
167
+ if (this.autotel) {
168
+ const stack = this.stepSpanStacks.get(test.id);
169
+ if (stack) {
170
+ while (stack.length > 0) {
171
+ const handle = stack.pop();
172
+ handle.endSpan("interrupted test");
173
+ }
174
+ this.stepSpanStacks.delete(test.id);
175
+ }
176
+ const testHandle = this.testSpans.get(test.id);
177
+ if (testHandle) {
178
+ testHandle.endSpan(result.status, result.errors?.[0]?.message);
179
+ this.testSpans.delete(test.id);
180
+ }
181
+ }
34
182
  const storyAnnotation = test.annotations.find((a) => a.type === "story-meta");
35
183
  if (!storyAnnotation?.description) return;
36
184
  try {
37
185
  const meta = JSON.parse(storyAnnotation.description);
186
+ const otelSpansAnnotation = test.annotations.find(
187
+ (a) => a.type === "otel-spans"
188
+ );
189
+ if (otelSpansAnnotation?.description) {
190
+ try {
191
+ const spans = JSON.parse(otelSpansAnnotation.description);
192
+ if (Array.isArray(spans) && spans.length > 0) {
193
+ const valid = spans.filter(
194
+ (s) => s != null && typeof s === "object" && typeof s.spanId === "string" && typeof s.name === "string"
195
+ );
196
+ if (valid.length > 0) {
197
+ meta.otelSpans = valid;
198
+ }
199
+ }
200
+ } catch {
201
+ }
202
+ }
38
203
  const sourceFile = test.location?.file ? toRelativePosix(test.location.file, this.projectRoot) : "unknown";
39
204
  const sourceLine = test.location?.line ?? 1;
40
205
  let error;
@@ -136,6 +301,44 @@ var StoryReporter = class {
136
301
  } catch (err) {
137
302
  console.error("Failed to generate reports:", err);
138
303
  }
304
+ try {
305
+ const histOpts = this.options.history;
306
+ if (histOpts?.filePath) {
307
+ const historyPath = path.isAbsolute(histOpts.filePath) ? histOpts.filePath : path.join(this.projectRoot, histOpts.filePath);
308
+ const store = loadHistory(
309
+ { filePath: historyPath },
310
+ {
311
+ readFile: (p) => {
312
+ try {
313
+ return fs.readFileSync(p, "utf8");
314
+ } catch {
315
+ return void 0;
316
+ }
317
+ },
318
+ logger: console
319
+ }
320
+ );
321
+ const updated = updateHistory({ store, run: canonicalRun, maxRuns: histOpts.maxRuns ?? 10 });
322
+ const dir = path.dirname(historyPath);
323
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
324
+ saveHistory(
325
+ { filePath: historyPath, store: updated },
326
+ { writeFile: (p, c) => fs.writeFileSync(p, c, "utf8") }
327
+ );
328
+ }
329
+ } catch (err) {
330
+ console.error("Failed to update history:", err);
331
+ }
332
+ try {
333
+ if (this.options.notification) {
334
+ await sendNotifications(
335
+ { run: canonicalRun, notification: this.options.notification },
336
+ { fetch: globalThis.fetch, logger: console, toCIInfo }
337
+ );
338
+ }
339
+ } catch (err) {
340
+ console.error("Failed to send notifications:", err);
341
+ }
139
342
  }
140
343
  };
141
344
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Playwright reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n */\n\nimport type {\n Reporter,\n FullConfig,\n TestCase,\n TestResult,\n FullResult,\n} from \"@playwright/test/reporter\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\ninterface CollectedScenario {\n meta: StoryMeta;\n sourceFile: string;\n sourceLine: number;\n status: \"passed\" | \"failed\" | \"skipped\" | \"timedOut\" | \"interrupted\";\n error?: string;\n errorStack?: string;\n durationMs: number;\n projectName?: string;\n retry: number;\n retries: number;\n attachments?: RawAttachment[];\n stepEvents?: RawStepEvent[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private scenarios: CollectedScenario[] = [];\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private projectRoot: string = process.cwd();\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onBegin(config: FullConfig): void {\n this.startTime = Date.now();\n this.projectRoot = config.rootDir ?? process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(this.projectRoot);\n this.gitSha = readGitSha(this.projectRoot);\n }\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n // Find story-meta annotation\n const storyAnnotation = test.annotations.find((a) => a.type === \"story-meta\");\n if (!storyAnnotation?.description) return;\n\n try {\n const meta: StoryMeta = JSON.parse(storyAnnotation.description);\n\n // Get source file and line for sorting\n const sourceFile = test.location?.file\n ? toRelativePosix(test.location.file, this.projectRoot)\n : \"unknown\";\n const sourceLine = (test.location as { line?: number })?.line ?? 1;\n\n // Get error message if failed\n let error: string | undefined;\n let errorStack: string | undefined;\n if (result.status === \"failed\" && result.errors?.length) {\n const err = result.errors[0];\n error = err.message || String(err);\n errorStack = err.stack;\n }\n\n // Map Playwright result.attachments → RawAttachment[]\n const attachments: RawAttachment[] = (result.attachments ?? []).map((a) => {\n let body: string | undefined;\n let encoding: \"BASE64\" | \"IDENTITY\" | undefined;\n if (a.body !== undefined) {\n if (typeof a.body === \"string\") {\n body = a.body;\n encoding = \"IDENTITY\";\n } else if (Buffer.isBuffer(a.body) || (a.body as unknown) instanceof Uint8Array) {\n body = Buffer.from(a.body as Buffer | Uint8Array).toString(\"base64\");\n encoding = \"BASE64\";\n }\n }\n return {\n name: a.name,\n mediaType: a.contentType,\n path: a.path,\n body,\n encoding,\n };\n });\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n this.scenarios.push({\n meta,\n sourceFile,\n sourceLine,\n status: result.status,\n error,\n errorStack,\n durationMs: result.duration,\n projectName: test.parent?.project()?.name,\n retry: result.retry,\n retries: test.retries,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n });\n } catch {\n // Ignore parse errors\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n if (this.scenarios.length === 0) return;\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = this.scenarios.map((scenario) => {\n // Map Playwright status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n timedOut: \"fail\",\n interrupted: \"fail\",\n };\n\n return {\n title: scenario.meta.scenario,\n titlePath: scenario.meta.suitePath\n ? [...scenario.meta.suitePath, scenario.meta.scenario]\n : [scenario.meta.scenario],\n story: scenario.meta,\n sourceFile: scenario.sourceFile,\n sourceLine: Math.max(1, scenario.sourceLine),\n status: statusMap[scenario.status] ?? \"unknown\",\n durationMs: scenario.durationMs,\n error: scenario.error\n ? { message: scenario.error, stack: scenario.errorStack }\n : undefined,\n projectName: scenario.projectName,\n retry: scenario.retry,\n retries: scenario.retries,\n attachments: scenario.attachments,\n stepEvents: scenario.stepEvents,\n };\n });\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: this.projectRoot,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(this.projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/reporter.ts","../src/otel-reporter-spans.ts"],"sourcesContent":["/**\n * Playwright reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n */\n\nimport type {\n Reporter,\n FullConfig,\n TestCase,\n TestResult,\n FullResult,\n TestStep,\n} from \"@playwright/test/reporter\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\nimport {\n tryLoadAutotel,\n shouldInstrumentStep,\n createTestSpan,\n createStepSpan,\n type AutotelApi,\n} from \"./otel-reporter-spans.js\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n sendNotifications,\n toCIInfo,\n loadHistory,\n updateHistory,\n saveHistory,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\ninterface CollectedScenario {\n meta: StoryMeta;\n sourceFile: string;\n sourceLine: number;\n status: \"passed\" | \"failed\" | \"skipped\" | \"timedOut\" | \"interrupted\";\n error?: string;\n errorStack?: string;\n durationMs: number;\n projectName?: string;\n retry: number;\n retries: number;\n attachments?: RawAttachment[];\n stepEvents?: RawStepEvent[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter implements Reporter {\n private options: StoryReporterOptions;\n private scenarios: CollectedScenario[] = [];\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n private projectRoot: string = process.cwd();\n private autotel: AutotelApi | null = null;\n private testSpans = new Map<\n string,\n { endSpan: (status: string, errorMessage?: string) => void }\n >();\n private stepSpanStacks = new Map<\n string,\n Array<{ endSpan: (errorMessage?: string) => void }>\n >();\n\n constructor(options: StoryReporterOptions = {}) {\n this.options = options;\n }\n\n onBegin(config: FullConfig): void {\n this.startTime = Date.now();\n this.projectRoot = config.rootDir ?? process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(this.projectRoot);\n this.gitSha = readGitSha(this.projectRoot);\n }\n this.autotel = tryLoadAutotel();\n }\n\n onTestBegin(test: TestCase): void {\n if (!this.autotel) return;\n const sourceFile = test.location?.file;\n const sourceLine = (test.location as { line?: number })?.line;\n const titlePath = test.titlePath();\n // titlePath: [projectName, ...describes, testTitle]\n const suitePath = titlePath.slice(1, -1);\n const testTitle = titlePath[titlePath.length - 1] ?? test.title;\n\n const handle = createTestSpan(\n { testTitle, suitePath, sourceFile, sourceLine },\n { autotel: this.autotel },\n );\n this.testSpans.set(test.id, handle);\n this.stepSpanStacks.set(test.id, []);\n }\n\n onStepBegin(test: TestCase, _result: TestResult, step: TestStep): void {\n if (!this.autotel) return;\n if (!shouldInstrumentStep({ category: step.category, title: step.title }))\n return;\n\n const handle = createStepSpan(\n { stepTitle: step.title, stepCategory: step.category },\n { autotel: this.autotel },\n );\n const stack = this.stepSpanStacks.get(test.id);\n if (stack) {\n stack.push(handle);\n }\n }\n\n onStepEnd(test: TestCase, _result: TestResult, step: TestStep): void {\n if (!this.autotel) return;\n if (!shouldInstrumentStep({ category: step.category, title: step.title }))\n return;\n\n const stack = this.stepSpanStacks.get(test.id);\n if (stack && stack.length > 0) {\n const handle = stack.pop()!;\n handle.endSpan(step.error?.message);\n }\n }\n\n onTestEnd(test: TestCase, result: TestResult): void {\n // Defensive: unwind leftover step spans (interrupted/crash)\n if (this.autotel) {\n const stack = this.stepSpanStacks.get(test.id);\n if (stack) {\n while (stack.length > 0) {\n const handle = stack.pop()!;\n handle.endSpan(\"interrupted test\");\n }\n this.stepSpanStacks.delete(test.id);\n }\n // End test span\n const testHandle = this.testSpans.get(test.id);\n if (testHandle) {\n testHandle.endSpan(result.status, result.errors?.[0]?.message);\n this.testSpans.delete(test.id);\n }\n }\n\n // Find story-meta annotation\n const storyAnnotation = test.annotations.find((a) => a.type === \"story-meta\");\n if (!storyAnnotation?.description) return;\n\n try {\n const meta: StoryMeta = JSON.parse(storyAnnotation.description);\n\n // Read autotel OTel spans from annotations\n const otelSpansAnnotation = test.annotations.find(\n (a) => a.type === \"otel-spans\",\n );\n if (otelSpansAnnotation?.description) {\n try {\n const spans = JSON.parse(otelSpansAnnotation.description);\n if (Array.isArray(spans) && spans.length > 0) {\n const valid = spans.filter(\n (s: unknown) =>\n s != null &&\n typeof s === \"object\" &&\n typeof (s as Record<string, unknown>).spanId === \"string\" &&\n typeof (s as Record<string, unknown>).name === \"string\",\n );\n if (valid.length > 0) {\n meta.otelSpans = valid;\n }\n }\n } catch {\n /* ignore parse errors */\n }\n }\n\n // Get source file and line for sorting\n const sourceFile = test.location?.file\n ? toRelativePosix(test.location.file, this.projectRoot)\n : \"unknown\";\n const sourceLine = (test.location as { line?: number })?.line ?? 1;\n\n // Get error message if failed\n let error: string | undefined;\n let errorStack: string | undefined;\n if (result.status === \"failed\" && result.errors?.length) {\n const err = result.errors[0];\n error = err.message || String(err);\n errorStack = err.stack;\n }\n\n // Map Playwright result.attachments → RawAttachment[]\n const attachments: RawAttachment[] = (result.attachments ?? []).map((a) => {\n let body: string | undefined;\n let encoding: \"BASE64\" | \"IDENTITY\" | undefined;\n if (a.body !== undefined) {\n if (typeof a.body === \"string\") {\n body = a.body;\n encoding = \"IDENTITY\";\n } else if (Buffer.isBuffer(a.body) || (a.body as unknown) instanceof Uint8Array) {\n body = Buffer.from(a.body as Buffer | Uint8Array).toString(\"base64\");\n encoding = \"BASE64\";\n }\n }\n return {\n name: a.name,\n mediaType: a.contentType,\n path: a.path,\n body,\n encoding,\n };\n });\n\n // Extract step events (timing) from story steps\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n this.scenarios.push({\n meta,\n sourceFile,\n sourceLine,\n status: result.status,\n error,\n errorStack,\n durationMs: result.duration,\n projectName: test.parent?.project()?.name,\n retry: result.retry,\n retries: test.retries,\n attachments: attachments.length > 0 ? attachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n });\n } catch {\n // Ignore parse errors\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n if (this.scenarios.length === 0) return;\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = this.scenarios.map((scenario) => {\n // Map Playwright status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n skipped: \"skip\",\n timedOut: \"fail\",\n interrupted: \"fail\",\n };\n\n return {\n title: scenario.meta.scenario,\n titlePath: scenario.meta.suitePath\n ? [...scenario.meta.suitePath, scenario.meta.scenario]\n : [scenario.meta.scenario],\n story: scenario.meta,\n sourceFile: scenario.sourceFile,\n sourceLine: Math.max(1, scenario.sourceLine),\n status: statusMap[scenario.status] ?? \"unknown\",\n durationMs: scenario.durationMs,\n error: scenario.error\n ? { message: scenario.error, stack: scenario.errorStack }\n : undefined,\n projectName: scenario.projectName,\n retry: scenario.retry,\n retries: scenario.retries,\n attachments: scenario.attachments,\n stepEvents: scenario.stepEvents,\n };\n });\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: this.projectRoot,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(this.projectRoot, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // 1. Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n\n // 2. Update history (independent of report generation)\n try {\n const histOpts = this.options.history;\n if (histOpts?.filePath) {\n const historyPath = path.isAbsolute(histOpts.filePath)\n ? histOpts.filePath\n : path.join(this.projectRoot, histOpts.filePath);\n const store = loadHistory(\n { filePath: historyPath },\n {\n readFile: (p: string) => { try { return fs.readFileSync(p, \"utf8\"); } catch { return undefined; } },\n logger: console,\n },\n );\n const updated = updateHistory({ store, run: canonicalRun, maxRuns: histOpts.maxRuns ?? 10 });\n const dir = path.dirname(historyPath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n saveHistory(\n { filePath: historyPath, store: updated },\n { writeFile: (p: string, c: string) => fs.writeFileSync(p, c, \"utf8\") },\n );\n }\n } catch (err) {\n console.error(\"Failed to update history:\", err);\n }\n\n // 3. Send notifications (independent of both above)\n try {\n if (this.options.notification) {\n await sendNotifications(\n { run: canonicalRun, notification: this.options.notification },\n { fetch: globalThis.fetch, logger: console, toCIInfo },\n );\n }\n } catch (err) {\n console.error(\"Failed to send notifications:\", err);\n }\n }\n}\n","/**\n * OTel span generation helpers for the Playwright reporter.\n *\n * Uses autotel for span creation with the same lazy-loading pattern\n * as story-api.ts (createRequire). All helpers follow the fn(args, deps)\n * convention for explicit dependency injection.\n */\n\nimport { createRequire } from \"node:module\";\n\n// ============================================================================\n// Autotel API surface\n// ============================================================================\n\n/** OTel span handle returned from autotel callback */\nexport interface AutotelSpan {\n end: () => void;\n setStatus: (status: { code: number; message?: string }) => void;\n setAttribute: (key: string, value: unknown) => void;\n}\n\n/** Minimal autotel API surface we use */\nexport interface AutotelApi {\n span: (\n name: string,\n fn: (span: AutotelSpan) => void,\n ) => void;\n SpanStatusCode: { UNSET: number; ERROR: number };\n}\n\n// ============================================================================\n// Lazy loader\n// ============================================================================\n\n/**\n * Lazy-load autotel. Returns null if unavailable.\n * Same createRequire pattern as story-api.ts.\n */\nexport function tryLoadAutotel(): AutotelApi | null {\n try {\n const reqUrl =\n import.meta.url ??\n (typeof __filename !== \"undefined\" ? `file://${__filename}` : undefined);\n if (!reqUrl) return null;\n const req = createRequire(reqUrl);\n const autotel = req(\"autotel\");\n if (typeof autotel?.span !== \"function\") return null;\n return autotel as AutotelApi;\n } catch {\n return null;\n }\n}\n\n// ============================================================================\n// Step filtering\n// ============================================================================\n\n/**\n * Step filtering — explicit, heavily tested.\n * Returns true for test.step category and story step keywords.\n */\nexport function shouldInstrumentStep(step: {\n category?: string;\n title?: string;\n}): boolean {\n return step.category === \"test.step\" || isStoryStep(step);\n}\n\n/**\n * Check if a step title starts with a story keyword.\n * Documented tradeoff: may match non-story steps starting with these words\n * (e.g. \"And this works\"). Acceptable for v1.\n */\nfunction isStoryStep(step: { title?: string }): boolean {\n if (!step.title) return false;\n return /^(Given|When|Then|And|But|Arrange|Act|Assert)\\s/.test(step.title);\n}\n\n// ============================================================================\n// Status mapping\n// ============================================================================\n\n/**\n * Map test/step status to OTel SpanStatusCode.\n * Single helper used by both test and step spans.\n *\n * \"passed\"/\"skipped\" -> UNSET\n * \"failed\"/\"timedOut\"/\"interrupted\" -> ERROR\n */\nfunction mapToSpanStatus(\n status: string,\n SpanStatusCode: { UNSET: number; ERROR: number },\n): { code: number; message?: string } {\n switch (status) {\n case \"passed\":\n case \"skipped\":\n return { code: SpanStatusCode.UNSET };\n case \"failed\":\n case \"timedOut\":\n case \"interrupted\":\n return { code: SpanStatusCode.ERROR, message: status };\n default:\n return { code: SpanStatusCode.UNSET };\n }\n}\n\n// ============================================================================\n// Test span\n// ============================================================================\n\n/**\n * Create a test-level span.\n *\n * Attribute naming convention:\n * - code.filepath, code.lineno -- OTel code conventions\n * - test.name, test.suite, test.status -- test attributes\n * - story.scenario, story.tags, story.tickets -- story-specific\n */\nexport function createTestSpan(\n args: {\n testTitle: string;\n suitePath?: string[];\n sourceFile?: string;\n sourceLine?: number;\n },\n deps: { autotel: AutotelApi },\n): { endSpan: (status: string, errorMessage?: string) => void } {\n let captured: AutotelSpan | undefined;\n deps.autotel.span(`test: ${args.testTitle}`, (s) => {\n captured = s;\n s.setAttribute(\"test.name\", args.testTitle);\n if (args.suitePath?.length) {\n s.setAttribute(\"test.suite\", args.suitePath.join(\" > \"));\n }\n if (args.sourceFile) {\n s.setAttribute(\"code.filepath\", args.sourceFile);\n }\n if (args.sourceLine !== undefined) {\n s.setAttribute(\"code.lineno\", args.sourceLine);\n }\n });\n const span = captured!;\n\n return {\n endSpan(status: string, errorMessage?: string) {\n span.setAttribute(\"test.status\", status);\n const spanStatus = mapToSpanStatus(status, deps.autotel.SpanStatusCode);\n if (errorMessage) {\n spanStatus.message = errorMessage;\n }\n span.setStatus(spanStatus);\n span.end();\n },\n };\n}\n\n// ============================================================================\n// Step span\n// ============================================================================\n\n/**\n * Create a step-level span.\n *\n * Attribute naming:\n * - test.step.name -- step title\n * - test.step.category -- step category\n */\nexport function createStepSpan(\n args: {\n stepTitle: string;\n stepCategory?: string;\n },\n deps: { autotel: AutotelApi },\n): { endSpan: (errorMessage?: string) => void } {\n let captured: AutotelSpan | undefined;\n deps.autotel.span(`step: ${args.stepTitle}`, (s) => {\n captured = s;\n s.setAttribute(\"test.step.name\", args.stepTitle);\n if (args.stepCategory) {\n s.setAttribute(\"test.step.category\", args.stepCategory);\n }\n });\n const span = captured!;\n\n return {\n endSpan(errorMessage?: string) {\n if (errorMessage) {\n span.setStatus({\n code: deps.autotel.SpanStatusCode.ERROR,\n message: errorMessage,\n });\n }\n span.end();\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executable-stories-playwright",
3
- "version": "4.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "BDD-style executable stories for Playwright Test with documentation generation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -23,7 +23,13 @@
23
23
  ],
24
24
  "peerDependencies": {
25
25
  "@playwright/test": ">=1.58.2",
26
- "executable-stories-formatters": "^0.3.0"
26
+ "executable-stories-formatters": "^0.5.0",
27
+ "autotel": ">=0.1.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "autotel": {
31
+ "optional": true
32
+ }
27
33
  },
28
34
  "dependencies": {},
29
35
  "devDependencies": {
@@ -32,7 +38,7 @@
32
38
  "@types/node": "^25.2.3",
33
39
  "tsup": "^8.5.1",
34
40
  "typescript": "~5.9.3",
35
- "executable-stories-formatters": "0.3.0"
41
+ "executable-stories-formatters": "0.5.0"
36
42
  },
37
43
  "repository": {
38
44
  "type": "git",