executable-stories-playwright 8.2.6 → 8.2.8
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/reporter.js +20 -1
- package/dist/reporter.js.map +1 -1
- package/package.json +5 -5
package/dist/reporter.js
CHANGED
|
@@ -254,6 +254,15 @@ var StoryReporter = class {
|
|
|
254
254
|
}
|
|
255
255
|
async onEnd(_result) {
|
|
256
256
|
if (this.scenarios.length === 0) return;
|
|
257
|
+
if (this.scenarios.length > 0) {
|
|
258
|
+
const sampleScenario = this.scenarios[0];
|
|
259
|
+
if ("tags" in sampleScenario) {
|
|
260
|
+
console.error("[Reporter Debug] tags found at scenario level! Keys:", Object.keys(sampleScenario));
|
|
261
|
+
}
|
|
262
|
+
if (sampleScenario.meta && "tags" in sampleScenario.meta) {
|
|
263
|
+
console.error("[Reporter Debug] tags found inside meta (correct). meta.tags:", sampleScenario.meta.tags);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
257
266
|
const rawTestCases = this.scenarios.map((scenario) => {
|
|
258
267
|
const statusMap = {
|
|
259
268
|
passed: "pass",
|
|
@@ -262,7 +271,7 @@ var StoryReporter = class {
|
|
|
262
271
|
timedOut: "fail",
|
|
263
272
|
interrupted: "fail"
|
|
264
273
|
};
|
|
265
|
-
|
|
274
|
+
const testCase = {
|
|
266
275
|
title: scenario.meta.scenario,
|
|
267
276
|
titlePath: scenario.meta.suitePath ? [...scenario.meta.suitePath, scenario.meta.scenario] : [scenario.meta.scenario],
|
|
268
277
|
story: scenario.meta,
|
|
@@ -277,7 +286,17 @@ var StoryReporter = class {
|
|
|
277
286
|
attachments: scenario.attachments,
|
|
278
287
|
stepEvents: scenario.stepEvents
|
|
279
288
|
};
|
|
289
|
+
return testCase;
|
|
280
290
|
});
|
|
291
|
+
if (rawTestCases.length > 0) {
|
|
292
|
+
const sample = rawTestCases[0];
|
|
293
|
+
if ("tags" in sample) {
|
|
294
|
+
console.error("[Reporter Debug] tags found at rawTestCase level! Keys:", Object.keys(sample));
|
|
295
|
+
}
|
|
296
|
+
if (sample.story && "tags" in sample.story) {
|
|
297
|
+
console.error("[Reporter Debug] tags found inside story (correct).");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
281
300
|
const rawRun = {
|
|
282
301
|
testCases: rawTestCases,
|
|
283
302
|
startedAtMs: this.startTime,
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 allAttachments: 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 // Deduplicate video attachments by name — Playwright may attach\n // multiple video files per test (e.g. video.webm and video-1.webm).\n // Keep only the last video attachment per name, which is the real recording.\n const attachments = deduplicateVideoAttachments(allAttachments);\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/**\n * Deduplicate video attachments by name.\n *\n * Playwright with `video: \"on\"` may produce multiple video files per test\n * in the output directory (e.g. `video.webm` and `video-1.webm`), attaching\n * all of them to the test result. This leads to duplicate videos in reports.\n *\n * For each unique video attachment name, keep only the last occurrence —\n * Playwright appends the real recording after any stubs.\n * Non-video attachments are always preserved.\n */\nexport function deduplicateVideoAttachments(\n attachments: RawAttachment[],\n): RawAttachment[] {\n // Find the last index for each video attachment name\n const lastVideoIndex = new Map<string, number>();\n for (let i = 0; i < attachments.length; i++) {\n if (attachments[i].mediaType.startsWith(\"video/\")) {\n lastVideoIndex.set(attachments[i].name, i);\n }\n }\n\n // Keep non-video attachments and only the last video per name\n return attachments.filter((att, i) => {\n if (!att.mediaType.startsWith(\"video/\")) return true;\n return lastVideoIndex.get(att.name) === i;\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":[]}
|
|
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 allAttachments: 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 // Deduplicate video attachments by name — Playwright may attach\n // multiple video files per test (e.g. video.webm and video-1.webm).\n // Keep only the last video attachment per name, which is the real recording.\n const attachments = deduplicateVideoAttachments(allAttachments);\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 // Debug: check if this.scenarios objects have tags at wrong level\n if (this.scenarios.length > 0) {\n const sampleScenario = this.scenarios[0];\n if ('tags' in sampleScenario) {\n console.error('[Reporter Debug] tags found at scenario level! Keys:', Object.keys(sampleScenario));\n }\n if (sampleScenario.meta && 'tags' in sampleScenario.meta) {\n console.error('[Reporter Debug] tags found inside meta (correct). meta.tags:', sampleScenario.meta.tags);\n }\n }\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 const testCase = {\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 return testCase;\n });\n\n // Debug: check if tags is at wrong level in rawTestCases\n if (rawTestCases.length > 0) {\n const sample = rawTestCases[0];\n if ('tags' in sample) {\n console.error('[Reporter Debug] tags found at rawTestCase level! Keys:', Object.keys(sample));\n }\n if (sample.story && 'tags' in sample.story) {\n console.error('[Reporter Debug] tags found inside story (correct).');\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/**\n * Deduplicate video attachments by name.\n *\n * Playwright with `video: \"on\"` may produce multiple video files per test\n * in the output directory (e.g. `video.webm` and `video-1.webm`), attaching\n * all of them to the test result. This leads to duplicate videos in reports.\n *\n * For each unique video attachment name, keep only the last occurrence —\n * Playwright appends the real recording after any stubs.\n * Non-video attachments are always preserved.\n */\nexport function deduplicateVideoAttachments(\n attachments: RawAttachment[],\n): RawAttachment[] {\n // Find the last index for each video attachment name\n const lastVideoIndex = new Map<string, number>();\n for (let i = 0; i < attachments.length; i++) {\n if (attachments[i].mediaType.startsWith(\"video/\")) {\n lastVideoIndex.set(attachments[i].name, i);\n }\n }\n\n // Keep non-video attachments and only the last video per name\n return attachments.filter((att, i) => {\n if (!att.mediaType.startsWith(\"video/\")) return true;\n return lastVideoIndex.get(att.name) === i;\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": "8.2.
|
|
3
|
+
"version": "8.2.8",
|
|
4
4
|
"description": "BDD-style executable stories for Playwright Test with documentation generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@playwright/test": ">=1.59.1",
|
|
29
|
-
"autotel": ">=
|
|
29
|
+
"autotel": ">=3.0.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependenciesMeta": {
|
|
32
32
|
"autotel": {
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"executable-stories-formatters": "0.7.
|
|
37
|
+
"executable-stories-formatters": "0.7.11"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@opentelemetry/api": "^1.9.1",
|
|
41
|
-
"@playwright/test": "^1.
|
|
41
|
+
"@playwright/test": "^1.59.1",
|
|
42
42
|
"@types/node": "^25.6.0",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
|
-
"typescript": "~6.0.
|
|
44
|
+
"typescript": "~6.0.3"
|
|
45
45
|
},
|
|
46
46
|
"repository": {
|
|
47
47
|
"type": "git",
|