executable-stories-e2e 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # executable-stories-e2e
2
2
 
3
- Living documentation from [e2e](https://github.com/tester-army/e2e) runs. A reporter turns e2e's `report-1` document into the RawRun JSON every executable-stories adapter writes, so `executable-stories format` renders it like any other suite.
3
+ Living documentation from [e2e](https://www.npmjs.com/package/e2e) runs. A reporter turns e2e's `report-1` document into the RawRun JSON every executable-stories adapter writes, so `executable-stories format` renders it like any other suite.
4
4
 
5
5
  ```bash
6
6
  npm i -D executable-stories-e2e executable-stories-formatters
@@ -13,44 +13,50 @@ import type { E2EConfig } from 'e2e';
13
13
  import { storyReporter } from 'executable-stories-e2e';
14
14
 
15
15
  export default {
16
- targets: [{ engine: web(), app: { url: 'localhost:3000' } }],
16
+ targets: [{ engine: web(), app: { url: 'http://localhost:3000' } }],
17
17
  reporters: ['list', storyReporter()],
18
18
  } satisfies E2EConfig;
19
19
  ```
20
20
 
21
21
  ```bash
22
- npx e2e
22
+ npx e2e run
23
23
  npx executable-stories format --format html,markdown # reads .executable-stories/raw-run.json
24
24
  ```
25
25
 
26
26
  Option: `storyReporter({ rawRunPath })` writes somewhere else, relative to the project root.
27
27
 
28
+ Works with e2e 0.15 and later. e2e 0.18 and later need Node.js `^22.22.3 || >=24.8.0`; fields a newer e2e adds are read when present.
29
+
28
30
  ## How a run maps
29
31
 
30
- | e2e | executable-stories |
31
- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
32
- | result (test × target) | scenario; `titlePath` gives describes → suite path |
33
- | `app`, `session`, `resource` step | Given |
34
- | `agent.act` and other agent steps, `locator`, `screen` | When |
35
- | `assertion` step, `agent.assert` | Then, `assertions: 1` when it reached a verdict |
36
- | repeated Given/When/Then | And |
37
- | step text | marker text; else agent instruction; else phrased locator/expect step, or `api label` when it cannot be phrased |
38
- | `passed` / `flaky` / `failed` / `skipped` | pass / pass (retry counted) / fail / skip |
39
- | `timed-out` / `interrupted` | timeout / interrupted |
40
- | step `blocked`, `timed-out` / `cancelled` | fail / skip (e2e error code kept in the message) |
41
- | attempt screenshot, video, trace, download, log | attachment; pinned to its step when a step produced it |
42
- | hosted video (`url`, no file) | video doc |
43
- | error `details.expected` / `observed` | expected / actual on the failure |
44
- | test tags | story tags |
45
- | agent step `explanation` | `Agent: …` note under the step, unless the error already says it |
46
-
47
- Only the final attempt is shown. Unselected results are left out, as e2e's JUnit reporter does. A `--last-failed` or tag-filtered run is marked `runScope: "filtered"` so it never retires scenarios it did not run.
48
-
49
- The reporter writes nothing for an `e2e explore` run or a run that reached no test (a device that failed to boot, an invalid config), so the last test run's `raw-run.json` stays in place. Explore findings are leads, not checked behaviour; the `bug-bash` skill turns them into failing scenarios.
32
+ | e2e | executable-stories |
33
+ | ------------------------------------------------------ | ----------------------------------------------------------------------- |
34
+ | result (test × target) | scenario; describes give the suite path |
35
+ | `app`, `session`, `resource` step | Given |
36
+ | `agent.act` and other agent steps, `locator`, `screen` | When |
37
+ | `assertion` step, `agent.assert` | Then, `assertions: 1` when it reached a verdict |
38
+ | repeated Given/When/Then | And |
39
+ | step text | marker text, the agent instruction, or a phrased locator or expect step |
40
+ | `passed` / `flaky` / `failed` / `skipped` | pass / pass with the retry counted / fail / skip |
41
+ | `timed-out` / `interrupted` | timeout / interrupted |
42
+ | step `blocked`, `timed-out` / `cancelled` | fail / skip, with e2e's error code in the message |
43
+ | attempt screenshot, video, trace, download, log | attachment on the step that produced it |
44
+ | hosted video (`url`, no file) | video doc |
45
+ | error `details.expected` / `observed` | expected / actual on the failure |
46
+ | test tags | story tags |
47
+ | agent step `explanation` | `Agent:` note under the step |
48
+ | `toHaveScreenshot` expected / actual / diff images | attachments on the step, paired in the report as a comparison |
49
+ | `trace.md` page (e2e 0.19+) | `trace` attachment (markdown) on the test |
50
+ | app log warning / error (e2e 0.19+) | `App <source> <level>:` note under the step that was running |
51
+ | skip reason on a failed attempt (e2e 0.19+) | `Skipped itself after a failure:` note on the scenario |
52
+
53
+ The report shows the final attempt. Unselected results stay out. A `--last-failed` or tag-filtered run is marked `runScope: "filtered"`, so it keeps the scenarios it did not run.
54
+
55
+ An `e2e explore` run, or a run that reached no test, leaves the last `raw-run.json` in place. The `bug-bash` skill turns explore findings into scenarios.
50
56
 
51
57
  ## Name steps in your own words
52
58
 
53
- Without markers, step text comes from what e2e records: the agent's instruction, or a locator or `expect` step phrased as `click the "Increment" button`. Add markers to say what a group of steps means:
59
+ Without markers, step text comes from what e2e records: the agent's instruction, or a locator or `expect` step phrased as `click the "Increment" button`. Markers name a group of steps:
54
60
 
55
61
  ```ts
56
62
  import { expect, test } from 'e2e';
@@ -67,6 +73,6 @@ test('pressing Increment twice reads 2', async ({ app, screen }) => {
67
73
  });
68
74
  ```
69
75
 
70
- Each marker becomes a step over the e2e steps that ran after it; those steps show beneath it. Its status is the worst of theirs and its assertion count is the number of recorded claims under it. With none, the count is left out rather than set to 0, because e2e records no step for a plain value matcher like `expect(2).toBe(2)`, so an unrecorded assertion may still have run.
76
+ Each marker renders as a step with the e2e steps that ran after it listed beneath. It takes the worst status of those steps and counts their recorded claims as assertions.
71
77
 
72
- e2e has no channel from a test to a reporter, so a marker appends its call site and time to `.executable-stories/e2e-markers/<pid>.jsonl`, and the reporter matches markers to results by file, line range and attempt time, then deletes the folder. Two repeats of one test running at the same moment in different workers cannot be told apart.
78
+ A marker appends its call site and time to `.executable-stories/e2e-markers/<pid>.jsonl`. The reporter matches markers to results by test file, line range and attempt time, then deletes the folder. Call markers in the test body, before the steps they describe.
package/dist/index.cjs CHANGED
@@ -225,14 +225,13 @@ function stepStatus(status) {
225
225
  switch (status) {
226
226
  case "passed":
227
227
  return "pass";
228
- // Lossy: our step model has no "blocked" (the agent could not reach a
229
- // verdict) or "timed-out"; both stopped the scenario, so both read as fail.
230
- // The step's error message keeps the distinction.
228
+ // Blocked and timed-out steps stopped the scenario, so they read as fail;
229
+ // the error message keeps e2e's code.
231
230
  case "failed":
232
231
  case "blocked":
233
232
  case "timed-out":
234
233
  return "fail";
235
- // Lossy: a cancelled step never finished; it reads as not run.
234
+ // A cancelled step did not finish, so it reads as not run.
236
235
  case "cancelled":
237
236
  return "skip";
238
237
  default:
@@ -314,7 +313,20 @@ function agentExplanation(step) {
314
313
  if (!why || step.error?.message.includes(why)) return [];
315
314
  return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
316
315
  }
317
- function toSteps(groups) {
316
+ var APP_LOG_SOURCE = {
317
+ console: "console",
318
+ error: "uncaught",
319
+ network: "network",
320
+ system: "system"
321
+ };
322
+ function appLogNotes(lines) {
323
+ return lines.filter((l) => l.level === "error" || l.level === "warning").map((l) => ({
324
+ kind: "note",
325
+ text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,
326
+ phase: "runtime"
327
+ }));
328
+ }
329
+ function toSteps(groups, appLog) {
318
330
  const out = [];
319
331
  groups.forEach((group, index) => {
320
332
  const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
@@ -329,7 +341,8 @@ function toSteps(groups) {
329
341
  phase: "runtime"
330
342
  }
331
343
  ] : [],
332
- ...agentExplanation(c)
344
+ ...agentExplanation(c),
345
+ ...appLogNotes(appLog.filter((l) => l.step === c.index))
333
346
  ]);
334
347
  out.push({
335
348
  id: `step-${index}`,
@@ -342,6 +355,10 @@ function toSteps(groups) {
342
355
  });
343
356
  return out;
344
357
  }
358
+ function artifactName(artifact) {
359
+ const file = path2.basename(artifact.path ?? "");
360
+ return /-(?:expected|actual|diff|previous)\.png$/.test(file) ? file : artifact.kind;
361
+ }
345
362
  function toMedia(attempt, groups, artifactsRoot) {
346
363
  const attachments = [];
347
364
  const docs = [];
@@ -358,7 +375,7 @@ function toMedia(attempt, groups, artifactsRoot) {
358
375
  )
359
376
  );
360
377
  attachments.push({
361
- name: artifact.kind,
378
+ name: artifactName(artifact),
362
379
  mediaType: artifact.mediaType,
363
380
  path: path2.join(artifactsRoot, artifact.path),
364
381
  fileName: path2.basename(artifact.path),
@@ -382,7 +399,9 @@ function finalAttempt(result, report) {
382
399
  startedAt: member?.startedAt ?? last.startedAt,
383
400
  durationMs: member?.durationMs ?? last.durationMs,
384
401
  artifacts: last.artifacts,
385
- steps: member?.steps ?? []
402
+ steps: member?.steps ?? [],
403
+ ...member?.appLog ? { appLog: member.appLog } : {},
404
+ ...member?.skip ? { skip: member.skip } : {}
386
405
  },
387
406
  count: attempts.length
388
407
  };
@@ -400,7 +419,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
400
419
  (m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
401
420
  ).sort((a, b) => a.at - b.at);
402
421
  }
403
- function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
422
+ function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
404
423
  const scenario = result.titlePath.at(-1) ?? result.testId;
405
424
  const suitePath = result.titlePath.slice(0, -1);
406
425
  const { attempt, count } = finalAttempt(result, report);
@@ -415,6 +434,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
415
434
  groups,
416
435
  artifactsRoot
417
436
  );
437
+ if (tracePage !== void 0) {
438
+ attachments.push({
439
+ name: "trace",
440
+ mediaType: "text/markdown",
441
+ path: path2.resolve(projectRoot, tracePage),
442
+ fileName: path2.basename(tracePage)
443
+ });
444
+ }
445
+ const appLog = attempt?.appLog ?? [];
446
+ docs.push(...appLogNotes(appLog.filter((l) => l.step === void 0)));
447
+ if (result.status !== "skipped" && attempt?.skip !== void 0) {
448
+ docs.push({
449
+ kind: "note",
450
+ text: `Skipped itself after a failure: ${attempt.skip.reason}`,
451
+ phase: "runtime"
452
+ });
453
+ }
418
454
  const error = attempt?.error;
419
455
  const details = error?.details;
420
456
  const stepEvents = groups.map((group, index) => {
@@ -434,7 +470,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
434
470
  titlePath: [...result.titlePath],
435
471
  story: {
436
472
  scenario,
437
- steps: toSteps(groups),
473
+ steps: toSteps(groups, appLog),
438
474
  ...result.tags.length > 0 ? { tags: [...result.tags] } : {},
439
475
  ...suitePath.length > 0 ? { suitePath } : {},
440
476
  ...docs.length > 0 ? { docs } : {}
@@ -462,7 +498,14 @@ function toRawRun(args) {
462
498
  const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
463
499
  return {
464
500
  testCases: results.map(
465
- (r) => toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot)
501
+ (r) => toTestCase(
502
+ r,
503
+ report,
504
+ artifactsRoot,
505
+ args.markers ?? [],
506
+ projectRoot,
507
+ args.traces?.get(r.id)
508
+ )
466
509
  ),
467
510
  startedAtMs: Date.parse(report.run.startedAt),
468
511
  finishedAtMs: Date.parse(report.run.finishedAt),
@@ -506,11 +549,13 @@ function storyReporter(options = {}) {
506
549
  await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
507
550
  return [{ label: "Stories", text: "skipped (explore run)" }];
508
551
  }
552
+ const traces = run.traces;
509
553
  const rawRun = toRawRun({
510
554
  report: run.report,
511
555
  projectRoot: run.projectRoot,
512
556
  artifactsRoot: run.artifactsRoot,
513
557
  markers: await readMarkers(markersDir),
558
+ ...traces ? { traces } : {},
514
559
  ...run.lastRun ? { lastRun: run.lastRun } : {}
515
560
  });
516
561
  await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Lossy: our step model has no \"blocked\" (the agent could not reach a\n // verdict) or \"timed-out\"; both stopped the scenario, so both read as fail.\n // The step's error message keeps the distinction.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // Lossy: a cancelled step never finished; it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nfunction toSteps(groups: readonly Group[]): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // Only recorded claims are visible: e2e's synchronous value matchers\n // (`expect(2).toBe(2)`) leave no step. So 0 seen is not 0 made, and the\n // count is omitted rather than graded as unasserted.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifact.kind,\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: Pick<\n ReportAttempt,\n 'error' | 'startedAt' | 'durationMs' | 'artifacts' | 'steps'\n >;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n // ponytail: two repeats of one test running at the same moment in different\n // workers cannot be told apart; markers would need the worker pid in the report.\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results are report-only, as e2e's own JUnit reporter treats them.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Markers from an earlier run must not leak into this one. Small and\n // synchronous, as onEvent must not block for long.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An `e2e explore` run has findings, not tests: unverified leads that\n // belong in a bug bash, not in living docs. Writing would also replace\n // the last test run's raw-run.json with an empty one.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that never reached a test (a device that failed to boot, an\n // invalid config) has nothing to say; an empty file would only replace\n // the last real run.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). That reads as code\n * in a living-docs page, so the common shapes are rephrased here. Anything\n * this does not recognise keeps the raw `api label`, so nothing is lost.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * e2e has no step API of its own and no channel from a test back to a\n * reporter, so a marker records where it was called and when. The reporter\n * then groups the e2e steps that ran after a marker under its text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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","names":["import_node_fs","path","noun"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n/**\n * One line of what the app did during an attempt (e2e 0.19+): console output,\n * an uncaught error, a failed request or a 4xx/5xx response, with the index of\n * the step that was running.\n */\ntype AppLogLine = NonNullable<ReportAttempt['appLog']>[number];\n\ntype FinalAttempt = Pick<\n ReportAttempt,\n | 'error'\n | 'startedAt'\n | 'durationMs'\n | 'artifacts'\n | 'steps'\n | 'appLog'\n | 'skip'\n>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Blocked and timed-out steps stopped the scenario, so they read as fail;\n // the error message keeps e2e's code.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // A cancelled step did not finish, so it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nconst APP_LOG_SOURCE: Readonly<Record<string, string>> = {\n console: 'console',\n error: 'uncaught',\n network: 'network',\n system: 'system',\n};\n\n/** Warnings and errors the app logged while a step ran; info lines are noise in docs. */\nfunction appLogNotes(lines: readonly AppLogLine[]): DocEntry[] {\n return lines\n .filter((l) => l.level === 'error' || l.level === 'warning')\n .map((l) => ({\n kind: 'note' as const,\n text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,\n phase: 'runtime' as const,\n }));\n}\n\nfunction toSteps(\n groups: readonly Group[],\n appLog: readonly AppLogLine[],\n): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // e2e records locator and agent claims as steps. A plain value matcher\n // such as `expect(2).toBe(2)` records none, so a group without a recorded\n // claim leaves the count out.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ...appLogNotes(appLog.filter((l) => l.step === c.index)),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * A failed `toHaveScreenshot` attaches `<stem>-expected.png`, `-actual.png`\n * and `-diff.png` to its step, all of kind `screenshot`. They keep their file\n * name, as Playwright names the same trio, so the report pairs them.\n */\nfunction artifactName(artifact: ReportArtifact): string {\n const file = path.basename(artifact.path ?? '');\n return /-(?:expected|actual|diff|previous)\\.png$/.test(file)\n ? file\n : artifact.kind;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifactName(artifact),\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: FinalAttempt;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n ...(member?.appLog ? { appLog: member.appLog } : {}),\n ...(member?.skip ? { skip: member.skip } : {}),\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n tracePage: string | undefined,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n // e2e 0.19 writes a markdown trace page per test that kept one (failed\n // tests by default): what each step did, read and logged.\n if (tracePage !== undefined) {\n attachments.push({\n name: 'trace',\n mediaType: 'text/markdown',\n path: path.resolve(projectRoot, tracePage),\n fileName: path.basename(tracePage),\n });\n }\n const appLog = attempt?.appLog ?? [];\n // Lines logged before the first step have no step to sit under.\n docs.push(...appLogNotes(appLog.filter((l) => l.step === undefined)));\n if (result.status !== 'skipped' && attempt?.skip !== undefined) {\n docs.push({\n kind: 'note',\n text: `Skipped itself after a failure: ${attempt.skip.reason}`,\n phase: 'runtime',\n });\n }\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups, appLog),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n /** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */\n traces?: ReadonlyMap<string, string>;\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results stay out of the docs.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(\n r,\n report,\n artifactsRoot,\n args.markers ?? [],\n projectRoot,\n args.traces?.get(r.id),\n ),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Start each run with an empty markers folder. The delete is small and\n // synchronous, which suits onEvent.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An explore run records findings, which the bug-bash skill turns into\n // scenarios. The last test run's raw-run.json stays as it is.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n // Added in e2e 0.19; undefined on an older runner.\n const traces = run.traces as FinishedRun['traces'] | undefined;\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(traces ? { traces } : {}),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that reached no test keeps the last test run's raw-run.json.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). This module\n * rephrases the common shapes for a living-docs page. Other shapes keep the\n * raw `api label`.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * A marker records where and when the test called it. The reporter groups\n * the e2e steps that ran after a marker under the marker's text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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","names":["import_node_fs","path","noun"]}
package/dist/index.d.cts CHANGED
@@ -4,9 +4,8 @@ import { RawRun } from 'executable-stories-core/types/raw';
4
4
  /**
5
5
  * Step markers for e2e tests.
6
6
  *
7
- * e2e has no step API of its own and no channel from a test back to a
8
- * reporter, so a marker records where it was called and when. The reporter
9
- * then groups the e2e steps that ran after a marker under its text:
7
+ * A marker records where and when the test called it. The reporter groups
8
+ * the e2e steps that ran after a marker under the marker's text:
10
9
  *
11
10
  * ```ts
12
11
  * test('incrementing shows the new count', async ({ app, screen }) => {
@@ -70,7 +69,10 @@ declare function toRawRun(args: {
70
69
  lastRun?: Report;
71
70
  /** Step markers the run's tests recorded with `story.given/when/then`. */
72
71
  markers?: readonly Marker[];
72
+ /** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */
73
+ traces?: ReadonlyMap<string, string>;
73
74
  }): RawRun;
75
+ /** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
74
76
  declare function storyReporter(options?: StoryReporterOptions): Reporter;
75
77
 
76
78
  export { type Marker, type MarkerKeyword, type StoryReporterOptions, phraseStep, story, storyReporter, toRawRun };
package/dist/index.d.ts CHANGED
@@ -4,9 +4,8 @@ import { RawRun } from 'executable-stories-core/types/raw';
4
4
  /**
5
5
  * Step markers for e2e tests.
6
6
  *
7
- * e2e has no step API of its own and no channel from a test back to a
8
- * reporter, so a marker records where it was called and when. The reporter
9
- * then groups the e2e steps that ran after a marker under its text:
7
+ * A marker records where and when the test called it. The reporter groups
8
+ * the e2e steps that ran after a marker under the marker's text:
10
9
  *
11
10
  * ```ts
12
11
  * test('incrementing shows the new count', async ({ app, screen }) => {
@@ -70,7 +69,10 @@ declare function toRawRun(args: {
70
69
  lastRun?: Report;
71
70
  /** Step markers the run's tests recorded with `story.given/when/then`. */
72
71
  markers?: readonly Marker[];
72
+ /** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */
73
+ traces?: ReadonlyMap<string, string>;
73
74
  }): RawRun;
75
+ /** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
74
76
  declare function storyReporter(options?: StoryReporterOptions): Reporter;
75
77
 
76
78
  export { type Marker, type MarkerKeyword, type StoryReporterOptions, phraseStep, story, storyReporter, toRawRun };
package/dist/index.js CHANGED
@@ -188,14 +188,13 @@ function stepStatus(status) {
188
188
  switch (status) {
189
189
  case "passed":
190
190
  return "pass";
191
- // Lossy: our step model has no "blocked" (the agent could not reach a
192
- // verdict) or "timed-out"; both stopped the scenario, so both read as fail.
193
- // The step's error message keeps the distinction.
191
+ // Blocked and timed-out steps stopped the scenario, so they read as fail;
192
+ // the error message keeps e2e's code.
194
193
  case "failed":
195
194
  case "blocked":
196
195
  case "timed-out":
197
196
  return "fail";
198
- // Lossy: a cancelled step never finished; it reads as not run.
197
+ // A cancelled step did not finish, so it reads as not run.
199
198
  case "cancelled":
200
199
  return "skip";
201
200
  default:
@@ -277,7 +276,20 @@ function agentExplanation(step) {
277
276
  if (!why || step.error?.message.includes(why)) return [];
278
277
  return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
279
278
  }
280
- function toSteps(groups) {
279
+ var APP_LOG_SOURCE = {
280
+ console: "console",
281
+ error: "uncaught",
282
+ network: "network",
283
+ system: "system"
284
+ };
285
+ function appLogNotes(lines) {
286
+ return lines.filter((l) => l.level === "error" || l.level === "warning").map((l) => ({
287
+ kind: "note",
288
+ text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,
289
+ phase: "runtime"
290
+ }));
291
+ }
292
+ function toSteps(groups, appLog) {
281
293
  const out = [];
282
294
  groups.forEach((group, index) => {
283
295
  const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
@@ -292,7 +304,8 @@ function toSteps(groups) {
292
304
  phase: "runtime"
293
305
  }
294
306
  ] : [],
295
- ...agentExplanation(c)
307
+ ...agentExplanation(c),
308
+ ...appLogNotes(appLog.filter((l) => l.step === c.index))
296
309
  ]);
297
310
  out.push({
298
311
  id: `step-${index}`,
@@ -305,6 +318,10 @@ function toSteps(groups) {
305
318
  });
306
319
  return out;
307
320
  }
321
+ function artifactName(artifact) {
322
+ const file = path2.basename(artifact.path ?? "");
323
+ return /-(?:expected|actual|diff|previous)\.png$/.test(file) ? file : artifact.kind;
324
+ }
308
325
  function toMedia(attempt, groups, artifactsRoot) {
309
326
  const attachments = [];
310
327
  const docs = [];
@@ -321,7 +338,7 @@ function toMedia(attempt, groups, artifactsRoot) {
321
338
  )
322
339
  );
323
340
  attachments.push({
324
- name: artifact.kind,
341
+ name: artifactName(artifact),
325
342
  mediaType: artifact.mediaType,
326
343
  path: path2.join(artifactsRoot, artifact.path),
327
344
  fileName: path2.basename(artifact.path),
@@ -345,7 +362,9 @@ function finalAttempt(result, report) {
345
362
  startedAt: member?.startedAt ?? last.startedAt,
346
363
  durationMs: member?.durationMs ?? last.durationMs,
347
364
  artifacts: last.artifacts,
348
- steps: member?.steps ?? []
365
+ steps: member?.steps ?? [],
366
+ ...member?.appLog ? { appLog: member.appLog } : {},
367
+ ...member?.skip ? { skip: member.skip } : {}
349
368
  },
350
369
  count: attempts.length
351
370
  };
@@ -363,7 +382,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
363
382
  (m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
364
383
  ).sort((a, b) => a.at - b.at);
365
384
  }
366
- function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
385
+ function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
367
386
  const scenario = result.titlePath.at(-1) ?? result.testId;
368
387
  const suitePath = result.titlePath.slice(0, -1);
369
388
  const { attempt, count } = finalAttempt(result, report);
@@ -378,6 +397,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
378
397
  groups,
379
398
  artifactsRoot
380
399
  );
400
+ if (tracePage !== void 0) {
401
+ attachments.push({
402
+ name: "trace",
403
+ mediaType: "text/markdown",
404
+ path: path2.resolve(projectRoot, tracePage),
405
+ fileName: path2.basename(tracePage)
406
+ });
407
+ }
408
+ const appLog = attempt?.appLog ?? [];
409
+ docs.push(...appLogNotes(appLog.filter((l) => l.step === void 0)));
410
+ if (result.status !== "skipped" && attempt?.skip !== void 0) {
411
+ docs.push({
412
+ kind: "note",
413
+ text: `Skipped itself after a failure: ${attempt.skip.reason}`,
414
+ phase: "runtime"
415
+ });
416
+ }
381
417
  const error = attempt?.error;
382
418
  const details = error?.details;
383
419
  const stepEvents = groups.map((group, index) => {
@@ -397,7 +433,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
397
433
  titlePath: [...result.titlePath],
398
434
  story: {
399
435
  scenario,
400
- steps: toSteps(groups),
436
+ steps: toSteps(groups, appLog),
401
437
  ...result.tags.length > 0 ? { tags: [...result.tags] } : {},
402
438
  ...suitePath.length > 0 ? { suitePath } : {},
403
439
  ...docs.length > 0 ? { docs } : {}
@@ -425,7 +461,14 @@ function toRawRun(args) {
425
461
  const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
426
462
  return {
427
463
  testCases: results.map(
428
- (r) => toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot)
464
+ (r) => toTestCase(
465
+ r,
466
+ report,
467
+ artifactsRoot,
468
+ args.markers ?? [],
469
+ projectRoot,
470
+ args.traces?.get(r.id)
471
+ )
429
472
  ),
430
473
  startedAtMs: Date.parse(report.run.startedAt),
431
474
  finishedAtMs: Date.parse(report.run.finishedAt),
@@ -469,11 +512,13 @@ function storyReporter(options = {}) {
469
512
  await rm(markersDir, { recursive: true, force: true });
470
513
  return [{ label: "Stories", text: "skipped (explore run)" }];
471
514
  }
515
+ const traces = run.traces;
472
516
  const rawRun = toRawRun({
473
517
  report: run.report,
474
518
  projectRoot: run.projectRoot,
475
519
  artifactsRoot: run.artifactsRoot,
476
520
  markers: await readMarkers(markersDir),
521
+ ...traces ? { traces } : {},
477
522
  ...run.lastRun ? { lastRun: run.lastRun } : {}
478
523
  });
479
524
  await rm(markersDir, { recursive: true, force: true });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Lossy: our step model has no \"blocked\" (the agent could not reach a\n // verdict) or \"timed-out\"; both stopped the scenario, so both read as fail.\n // The step's error message keeps the distinction.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // Lossy: a cancelled step never finished; it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nfunction toSteps(groups: readonly Group[]): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // Only recorded claims are visible: e2e's synchronous value matchers\n // (`expect(2).toBe(2)`) leave no step. So 0 seen is not 0 made, and the\n // count is omitted rather than graded as unasserted.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifact.kind,\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: Pick<\n ReportAttempt,\n 'error' | 'startedAt' | 'durationMs' | 'artifacts' | 'steps'\n >;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n // ponytail: two repeats of one test running at the same moment in different\n // workers cannot be told apart; markers would need the worker pid in the report.\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results are report-only, as e2e's own JUnit reporter treats them.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Markers from an earlier run must not leak into this one. Small and\n // synchronous, as onEvent must not block for long.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An `e2e explore` run has findings, not tests: unverified leads that\n // belong in a bug bash, not in living docs. Writing would also replace\n // the last test run's raw-run.json with an empty one.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that never reached a test (a device that failed to boot, an\n // invalid config) has nothing to say; an empty file would only replace\n // the last real run.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). That reads as code\n * in a living-docs page, so the common shapes are rephrased here. Anything\n * this does not recognise keeps the raw `api label`, so nothing is lost.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * e2e has no step API of its own and no channel from a test back to a\n * reporter, so a marker records where it was called and when. The reporter\n * then groups the e2e steps that ran after a marker under its text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":";AASA,SAAS,cAAc;AACvB,SAAS,OAAO,SAAS,UAAU,IAAI,iBAAiB;AACxD,YAAYA,WAAU;;;ACFtB,IAAM,aAAqC;AAAA,EACzC,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,KAAK;AACP;AAGA,IAAM,eACJ;AAEF,SAAS,QAAQ,MAAsB;AACrC,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,eAAe,OAAmC;AAChE,QAAM,QAAQ,aAAa,KAAK,KAAK;AACrC,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,EAAE,MAAM,UAAU,OAAO,IAAI;AACpC,QAAM,QAAQ,QAAQ,QAAS;AAC/B,QAAM,OAAO,YAAY,SAAY,SAAY,QAAQ,OAAO;AAChE,UAAQ,MAAM;AAAA,IACZ,KAAK,QAAQ;AACX,YAAM,OAAO,WAAW,KAAK,KAAK;AAClC,aAAO,SAAS,SAAY,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,IAAI;AAAA,IACnE;AAAA,IACA,KAAK;AACH,aAAO,aAAa,KAAK;AAAA,IAC3B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,QAAQ,KAAK;AAAA,IACtB,KAAK;AACH,aAAO,QAAQ,KAAK;AAAA,IACtB,KAAK;AAAA,IACL,KAAK;AACH,aAAO,QAAQ,KAAK;AAAA,IACtB;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,UAAkC;AAAA,EACtC,OAAO;AAAA,EACP,KAAK;AAAA,EACL,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,OAAO;AAAA,EACP,OAAO;AACT;AAGA,IAAM,SAA+D;AAAA,EACnE,aAAa,CAAC,cAAc,gBAAgB;AAAA,EAC5C,YAAY,CAAC,aAAa,eAAe;AAAA,EACzC,aAAa,CAAC,cAAc,gBAAgB;AAAA,EAC5C,cAAc,CAAC,eAAe,iBAAiB;AAAA,EAC/C,aAAa,CAAC,cAAc,gBAAgB;AAAA,EAC5C,aAAa,CAAC,cAAc,gBAAgB;AAAA,EAC5C,cAAc,CAAC,eAAe,iBAAiB;AAAA,EAC/C,cAAc,CAAC,eAAe,iBAAiB;AAAA,EAC/C,cAAc,CAAC,kBAAkB,oBAAoB;AAAA,EACrD,YAAY,CAAC,2BAA2B,8BAA8B;AAAA,EACtE,eAAe;AAAA,IACb;AAAA,IACA;AAAA,EACF;AAAA,EACA,aAAa,CAAC,0BAA0B,+BAA+B;AAAA,EACvE,aAAa;AAAA,IACX;AAAA,IACA;AAAA,EACF;AAAA,EACA,sBAAsB;AAAA,IACpB;AAAA,IACA;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA,IACf;AAAA,IACA;AAAA,EACF;AACF;AAEA,IAAM,MAA8B;AAAA,EAClC,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAiB;AACnB;AAGO,SAAS,WAAW,KAAa,OAAuB;AAC7D,QAAM,MAAM,QAAQ,GAAG,GAAG,IAAI,KAAK,KAAK;AAExC,MAAI,QAAQ,WAAY,QAAO,QAAQ,QAAQ,KAAK,KAAK;AACzD,MAAI,QAAQ;AACV,WAAO,QAAQ,sBAAsB,KAAK,KAAK;AACjD,MAAI,QAAQ;AACV,WAAO,QAAQ,uBAAuB,KAAK,KAAK;AAClD,MAAI,IAAI,GAAG,EAAG,QAAO,IAAI,GAAG;AAE5B,QAAM,SAAS,mBAAmB,KAAK,GAAG,IAAI,CAAC;AAC/C,MAAI,WAAW,QAAW;AACxB,QAAI,WAAW,WAAW;AAExB,YAAM,CAAC,QAAQ,KAAK,IAAI,MAAM,MAAM,UAAK;AACzC,YAAMC,QAAO,WAAW,SAAY,SAAY,eAAe,MAAM;AACrE,aAAOA,SAAQ,QAAQ,cAAcA,KAAI,OAAO,KAAK,KAAK;AAAA,IAC5D;AACA,UAAM,OAAO,QAAQ,MAAM;AAC3B,UAAM,OAAO,eAAe,KAAK;AACjC,WAAO,QAAQ,OAAO,GAAG,IAAI,IAAI,IAAI,KAAK;AAAA,EAC5C;AAEA,QAAM,QAAQ,0BAA0B,KAAK,GAAG;AAChD,MAAI,OAAO;AACT,UAAM,QAAQ,OAAO,MAAM,CAAC,CAAE;AAC9B,UAAM,OAAO,eAAe,KAAK;AACjC,WAAO,SAAS,OAAO,GAAG,IAAI,IAAI,MAAM,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK;AAAA,EAChE;AAEA,SAAO;AACT;;;AC5HA,SAAS,gBAAgB,iBAAiB;AAC1C,YAAY,UAAU;AACtB,SAAS,qBAAqB;AAkBvB,IAAM,cAAc;AAE3B,IAAM,QAAQ;AAGd,SAAS,WACoD;AAC3D,QAAM,UAAU,IAAI,MAAM,EAAE,SAAS,IAAI,MAAM,IAAI,EAAE,MAAM,CAAC;AAC5D,MAAI;AACJ,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,CAAC;AACrC,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM,CAAC,EAAG,WAAW,SAAS,IACvC,cAAc,MAAM,CAAC,CAAE,IACvB,MAAM,CAAC;AACX,YAAQ;AACR,QAAI,SAAS;AACX,aAAO,EAAE,MAAM,MAAM,OAAO,MAAM,CAAC,CAAC,GAAG,QAAQ,OAAO,MAAM,CAAC,CAAC,EAAE;AAAA,EACpE;AACA,SAAO;AACT;AAEA,SAAS,KAAK,SAAwB,MAAoB;AACxD,QAAM,OAAO,SAAS;AACtB,MAAI,SAAS,OAAW;AACxB,QAAM,SAAiB,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE;AAChE,QAAM,MAAW,aAAQ,WAAW;AACpC,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAElC;AAAA,IACO,UAAK,KAAK,GAAG,QAAQ,GAAG,QAAQ;AAAA,IACrC,GAAG,KAAK,UAAU,MAAM,CAAC;AAAA;AAAA,EAC3B;AACF;AAGO,IAAM,QAAQ;AAAA,EACnB,OAAO,CAAC,SAAuB,KAAK,SAAS,IAAI;AAAA,EACjD,MAAM,CAAC,SAAuB,KAAK,QAAQ,IAAI;AAAA,EAC/C,MAAM,CAAC,SAAuB,KAAK,QAAQ,IAAI;AAAA,EAC/C,KAAK,CAAC,SAAuB,KAAK,OAAO,IAAI;AAAA,EAC7C,KAAK,CAAC,SAAuB,KAAK,OAAO,IAAI;AAC/C;;;AFnCA,SAAS,aAAgB,QAAe,UAAgB;AACtD,SAAO;AACT;AAEA,SAAS,aAAa,QAA2C;AAC/D,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,aAAa,QAAQ,SAAS;AAAA,EACzC;AACF;AAEA,SAAS,WAAW,QAAyC;AAC3D,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO;AAAA;AAAA;AAAA;AAAA,IAIT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA;AAAA,IAET,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,aAAa,QAAQ,SAAS;AAAA,EACzC;AACF;AAGA,SAAS,eAAe,MAA6C;AACnE,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,KAAK,QAAQ,iBAAiB,SAAS;AAAA,IAChD;AACE,aAAO,aAAa,KAAK,MAAM,MAAM;AAAA,EACzC;AACF;AAGA,SAAS,SAAS,MAA0B;AAC1C,MAAI,KAAK,SAAS,WAAW,KAAK,MAAO,QAAO,KAAK;AACrD,SAAO,WAAW,KAAK,KAAK,KAAK,KAAK;AACxC;AAEA,SAAS,aAAa,OAA4B;AAChD,SAAO,GAAG,MAAM,IAAI,KAAK,MAAM,OAAO;AACxC;AAaA,SAAS,SACP,OACA,SACA,aACS;AACT,QAAM,SAAkB,CAAC;AACzB,MAAI,UAAU;AACd,MAAI;AAKJ,QAAM,SAAS,CAAC,GAAW,SAA0C;AACnE,QAAI,SAAS,OAAW,QAAO;AAC/B,UAAM,KAAK,KAAK,MAAM,KAAK,SAAS;AACpC,QAAI,EAAE,OAAO,GAAI,QAAO,EAAE,KAAK;AAC/B,QAAI,EAAE,SAAc,cAAQ,aAAa,KAAK,OAAO,IAAI,EAAG,QAAO;AACnE,WACE,EAAE,OAAO,KAAK,OAAO,QACpB,EAAE,SAAS,KAAK,OAAO,SAAS,EAAE,UAAU,KAAK,KAAK,OAAO;AAAA,EAElE;AACA,QAAM,oBAAoB,CAAC,SAAuC;AAChE,WAAO,UAAU,QAAQ,UAAU,OAAO,QAAQ,OAAO,GAAI,IAAI,GAAG;AAClE,YAAM,IAAI,QAAQ,SAAS;AAC3B,gBAAU;AAAA,QACR,SAAS,EAAE;AAAA,QACX,MAAM,EAAE;AAAA,QACR,QAAQ;AAAA,QACR,UAAU,CAAC;AAAA,MACb;AACA,aAAO,KAAK,OAAO;AAAA,IACrB;AAAA,EACF;AACA,aAAW,QAAQ,OAAO;AACxB,sBAAkB,IAAI;AACtB,QAAI,QAAS,SAAQ,SAAS,KAAK,IAAI;AAAA;AAErC,aAAO,KAAK;AAAA,QACV,SAAS,eAAe,IAAI;AAAA,QAC5B,MAAM,SAAS,IAAI;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,CAAC,IAAI;AAAA,MACjB,CAAC;AAAA,EACL;AACA,oBAAkB,MAAS;AAC3B,SAAO;AACT;AAGA,SAAS,gBAAgB,MAA2B;AAClD,SACE,eAAe,IAAI,MAAM,WACxB,KAAK,WAAW,YAAY,KAAK,WAAW;AAEjD;AAEA,SAAS,YAAY,OAAyB;AAC5C,QAAM,WAAW,MAAM,SAAS,IAAI,CAAC,MAAM,WAAW,EAAE,MAAM,CAAC;AAC/D,aAAW,SAAS,CAAC,QAAQ,WAAW,MAAM;AAC5C,QAAI,SAAS,SAAS,KAAK,EAAG,QAAO;AACvC,SAAO;AACT;AAGA,SAAS,iBAAiB,MAA8B;AACtD,QAAM,MAAM,KAAK,aAAa,KAAK;AACnC,MAAI,CAAC,OAAO,KAAK,OAAO,QAAQ,SAAS,GAAG,EAAG,QAAO,CAAC;AACvD,SAAO,CAAC,EAAE,MAAM,QAAQ,MAAM,UAAU,GAAG,IAAI,OAAO,UAAU,CAAC;AACnE;AAEA,SAAS,QAAQ,QAAuC;AACtD,QAAM,MAAmB,CAAC;AAC1B,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAM,SACJ,MAAM,YAAY,SAClB,MAAM,YAAY,SAClB,IAAI,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,OAAO;AAC7C,UAAM,UAAuB,SAAS,QAAQ,MAAM;AACpD,UAAM,SAAS,MAAM,SAAS,OAAO,eAAe,EAAE;AAItD,UAAM,aAAa,SAAS,IAAI,EAAE,YAAY,OAAO,IAAI,CAAC;AAG1D,UAAM,OAAmB,MAAM,SAAS,QAAQ,CAAC,MAAkB;AAAA,MACjE,GAAI,MAAM,SACN;AAAA,QACE;AAAA,UACE,MAAM;AAAA,UACN,MAAM,SAAS,CAAC;AAAA,UAChB,OAAO;AAAA,QACT;AAAA,MACF,IACA,CAAC;AAAA,MACL,GAAG,iBAAiB,CAAC;AAAA,IACvB,CAAC;AACD,QAAI,KAAK;AAAA,MACP,IAAI,QAAQ,KAAK;AAAA,MACjB;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAAA,MACnE,GAAG;AAAA,MACH,GAAI,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACpC,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAOA,SAAS,QACP,SAIA,QACA,eACoD;AACpD,QAAM,cAA+B,CAAC;AACtC,QAAM,OAAmB,CAAC;AAC1B,aAAW,YAAY,QAAQ,WAAW;AACxC,QAAI,SAAS,SAAS,QAAW;AAC/B,UACE,SAAS,SAAS,WAClB,SAAS,QAAQ,UACjB,eAAe,KAAK,SAAS,GAAG,GAChC;AACA,aAAK,KAAK,EAAE,MAAM,SAAS,MAAM,SAAS,KAAK,OAAO,UAAU,CAAC;AAAA,MACnE;AACA;AAAA,IACF;AACA,UAAM,YAAY,OAAO;AAAA,MAAU,CAAC,UAClC,MAAM,SAAS;AAAA,QACb,CAAC,SACC,KAAK,UAAU,SAAS,SAAS,EAAE,KAClC,SAAS,UAAU,SAAS,UAC3B,SAAS,SAAS,WAAW,KAAK;AAAA,MACxC;AAAA,IACF;AACA,gBAAY,KAAK;AAAA,MACf,MAAM,SAAS;AAAA,MACf,WAAW,SAAS;AAAA,MACpB,MAAW,WAAK,eAAe,SAAS,IAAI;AAAA,MAC5C,UAAe,eAAS,SAAS,IAAI;AAAA,MACrC,GAAI,SAAS,SAAS,SAAY,EAAE,YAAY,SAAS,KAAK,IAAI,CAAC;AAAA,MACnE,GAAI,aAAa,IAAI,EAAE,WAAW,QAAQ,QAAQ,SAAS,GAAG,IAAI,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AACA,SAAO,EAAE,aAAa,KAAK;AAC7B;AAGA,SAAS,aACP,QACA,QAOA;AACA,MAAI,OAAO,kBAAkB,QAAW;AACtC,WAAO,EAAE,SAAS,OAAO,SAAS,GAAG,EAAE,GAAG,OAAO,OAAO,SAAS,OAAO;AAAA,EAC1E;AACA,QAAM,WACJ,OAAO,IAAI,aAAa,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,aAAa,GAC7D,YAAY,CAAC;AACnB,QAAM,OAAO,SAAS,GAAG,EAAE;AAC3B,QAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO,MAAM;AACnE,MAAI,SAAS,OAAW,QAAO,EAAE,OAAO,EAAE;AAC1C,SAAO;AAAA,IACL,SAAS;AAAA,MACP,OAAO,QAAQ,SAAS,KAAK;AAAA,MAC7B,WAAW,QAAQ,aAAa,KAAK;AAAA,MACrC,YAAY,QAAQ,cAAc,KAAK;AAAA,MACvC,WAAW,KAAK;AAAA,MAChB,OAAO,QAAQ,SAAS,CAAC;AAAA,IAC3B;AAAA,IACA,OAAO,SAAS;AAAA,EAClB;AACF;AAMA,SAAS,WACP,QACA,SACA,QACA,SACA,aACU;AACV,MAAI,YAAY,UAAa,QAAQ,WAAW,EAAG,QAAO,CAAC;AAC3D,QAAM,OAAY,cAAQ,aAAa,OAAO,IAAI;AAClD,QAAM,OAAO,OAAO,OAAO;AAC3B,QAAM,KAAK,KAAK;AAAA,IACd,GAAG,OAAO,IAAI,QACX,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,OAAO,OAAO,IAAI,EAC5D,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI;AAAA,EAC7B;AACA,QAAM,QAAQ,KAAK,MAAM,QAAQ,SAAS;AAC1C,QAAM,MAAM,QAAQ,QAAQ,aAAa;AAGzC,SAAO,QACJ;AAAA,IACC,CAAC,MACC,EAAE,SAAS,QACX,EAAE,QAAQ,QACV,EAAE,OAAO,MACT,EAAE,MAAM,SACR,EAAE,MAAM;AAAA,EACZ,EACC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;AAC/B;AAEA,SAAS,WACP,QACA,QACA,eACA,SACA,aACa;AACb,QAAM,WAAW,OAAO,UAAU,GAAG,EAAE,KAAK,OAAO;AACnD,QAAM,YAAY,OAAO,UAAU,MAAM,GAAG,EAAE;AAC9C,QAAM,EAAE,SAAS,MAAM,IAAI,aAAa,QAAQ,MAAM;AACtD,QAAM,QAAQ,SAAS,SAAS,CAAC;AACjC,QAAM,SAAS;AAAA,IACb;AAAA,IACA,WAAW,QAAQ,SAAS,QAAQ,SAAS,WAAW;AAAA,IACxD;AAAA,EACF;AACA,QAAM,EAAE,aAAa,KAAK,IAAI;AAAA,IAC5B,EAAE,WAAW,SAAS,aAAa,CAAC,GAAG,MAAM;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AACA,QAAM,QAAQ,SAAS;AACvB,QAAM,UAAU,OAAO;AACvB,QAAM,aAA6B,OAAO,IAAI,CAAC,OAAO,UAAU;AAC9D,UAAM,SAAS,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,MAAS;AAC/D,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,QAAQ,KAAK;AAAA,MACrB,OAAO,MAAM;AAAA,MACb,QAAQ,YAAY,KAAK;AAAA,MACzB,YAAY,MAAM,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AAAA,MACnE,GAAI,QAAQ,QAAQ,EAAE,cAAc,aAAa,OAAO,KAAK,EAAE,IAAI,CAAC;AAAA,IACtE;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL,YAAY,OAAO;AAAA,IACnB,OAAO;AAAA,IACP,WAAW,CAAC,GAAG,OAAO,SAAS;AAAA,IAC/B,OAAO;AAAA,MACL;AAAA,MACA,OAAO,QAAQ,MAAM;AAAA,MACrB,GAAI,OAAO,KAAK,SAAS,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,IAAI,EAAE,IAAI,CAAC;AAAA,MAC3D,GAAI,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,MAC5C,GAAI,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACpC;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,YAAY,KAAK,IAAI,GAAG,OAAO,OAAO,IAAI;AAAA,IAC1C,QAAQ,aAAa,OAAO,MAAM;AAAA,IAClC,YAAY,SAAS,cAAc;AAAA,IACnC,GAAI,QACA;AAAA,MACE,OAAO;AAAA,QACL,SAAS,aAAa,KAAK;AAAA,QAC3B,GAAI,SAAS,aAAa,SACtB,EAAE,UAAU,QAAQ,SAAS,IAC7B,CAAC;AAAA,QACL,GAAI,SAAS,aAAa,SACtB,EAAE,QAAQ,QAAQ,SAAS,IAC3B,CAAC;AAAA,MACP;AAAA,IACF,IACA,CAAC;AAAA,IACL,GAAI,WAAW,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,IAC9C,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;AAAA,IAChD,OAAO,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA,IAC5B,aAAa,OAAO;AAAA,EACtB;AACF;AAMO,SAAS,SAAS,MAQd;AACT,QAAM,EAAE,QAAQ,aAAa,cAAc,IAAI;AAE/C,QAAM,UAAU,OAAO,IAAI,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,KAAK;AACrE,QAAM,WACJ,KAAK,YAAY,UAAa,QAAQ,SAAS,OAAO,IAAI,QAAQ;AACpE,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,MAAI,CAAC,MACtB,WAAW,GAAG,QAAQ,eAAe,KAAK,WAAW,CAAC,GAAG,WAAW;AAAA,IACtE;AAAA,IACA,aAAa,KAAK,MAAM,OAAO,IAAI,SAAS;AAAA,IAC5C,cAAc,KAAK,MAAM,OAAO,IAAI,UAAU;AAAA,IAC9C;AAAA;AAAA;AAAA,IAGA,GAAI,WAAW,EAAE,UAAU,WAAoB,IAAI,CAAC;AAAA,IACpD,oBAAoB,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK;AAAA,IAClE,GAAI,OAAO,IAAI,KAAK,SAAS,EAAE,QAAQ,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC;AAAA,EACpE;AACF;AAGA,eAAe,YAAY,KAAgC;AACzD,QAAM,QAAQ,MAAM,QAAQ,GAAG,EAAE,MAAM,MAAM,CAAC,CAAa;AAC3D,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,CAAC,GAAG;AAC5D,eAAW,SAAS,MAAM,SAAc,WAAK,KAAK,IAAI,GAAG,MAAM,GAAG;AAAA,MAChE;AAAA,IACF,GAAG;AACD,UAAI,CAAC,KAAM;AACX,UAAI;AACF,gBAAQ,KAAK,KAAK,MAAM,IAAI,CAAW;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,cAAc,UAAgC,CAAC,GAAa;AAC1E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,OAAO;AAGb,UAAI,MAAM,SAAS;AACjB,eAAY,cAAQ,MAAM,aAAa,WAAW,GAAG;AAAA,UACnD,WAAW;AAAA,UACX,OAAO;AAAA,QACT,CAAC;AAAA,IACL;AAAA,IACA,MAAM,cAAc,KAA4C;AAC9D,YAAM,aAAkB,cAAQ,IAAI,aAAa,WAAW;AAI5D,UAAI,IAAI,OAAO,IAAI,YAAY,QAAW;AACxC,cAAM,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,eAAO,CAAC,EAAE,OAAO,WAAW,MAAM,wBAAwB,CAAC;AAAA,MAC7D;AACA,YAAM,SAAS,SAAS;AAAA,QACtB,QAAQ,IAAI;AAAA,QACZ,aAAa,IAAI;AAAA,QACjB,eAAe,IAAI;AAAA,QACnB,SAAS,MAAM,YAAY,UAAU;AAAA,QACrC,GAAI,IAAI,UAAU,EAAE,SAAS,IAAI,QAAQ,IAAI,CAAC;AAAA,MAChD,CAAC;AACD,YAAM,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAIrD,UAAI,OAAO,UAAU,WAAW,GAAG;AACjC,eAAO,CAAC,EAAE,OAAO,WAAW,MAAM,yBAAyB,CAAC;AAAA,MAC9D;AACA,YAAM,SAAc;AAAA,QAClB,IAAI;AAAA,QACJ,QAAQ,cAAc;AAAA,MACxB;AACA,YAAM,MAAW,cAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,YAAM;AAAA,QACJ;AAAA,QACA,KAAK,UAAU,EAAE,eAAe,GAAG,GAAG,OAAO,GAAG,MAAM,CAAC;AAAA,QACvD;AAAA,MACF;AACA,aAAO;AAAA,QACL,EAAE,OAAO,WAAW,MAAW,eAAS,IAAI,aAAa,MAAM,EAAE;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACF;","names":["path","noun"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n/**\n * One line of what the app did during an attempt (e2e 0.19+): console output,\n * an uncaught error, a failed request or a 4xx/5xx response, with the index of\n * the step that was running.\n */\ntype AppLogLine = NonNullable<ReportAttempt['appLog']>[number];\n\ntype FinalAttempt = Pick<\n ReportAttempt,\n | 'error'\n | 'startedAt'\n | 'durationMs'\n | 'artifacts'\n | 'steps'\n | 'appLog'\n | 'skip'\n>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Blocked and timed-out steps stopped the scenario, so they read as fail;\n // the error message keeps e2e's code.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // A cancelled step did not finish, so it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nconst APP_LOG_SOURCE: Readonly<Record<string, string>> = {\n console: 'console',\n error: 'uncaught',\n network: 'network',\n system: 'system',\n};\n\n/** Warnings and errors the app logged while a step ran; info lines are noise in docs. */\nfunction appLogNotes(lines: readonly AppLogLine[]): DocEntry[] {\n return lines\n .filter((l) => l.level === 'error' || l.level === 'warning')\n .map((l) => ({\n kind: 'note' as const,\n text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,\n phase: 'runtime' as const,\n }));\n}\n\nfunction toSteps(\n groups: readonly Group[],\n appLog: readonly AppLogLine[],\n): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // e2e records locator and agent claims as steps. A plain value matcher\n // such as `expect(2).toBe(2)` records none, so a group without a recorded\n // claim leaves the count out.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ...appLogNotes(appLog.filter((l) => l.step === c.index)),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * A failed `toHaveScreenshot` attaches `<stem>-expected.png`, `-actual.png`\n * and `-diff.png` to its step, all of kind `screenshot`. They keep their file\n * name, as Playwright names the same trio, so the report pairs them.\n */\nfunction artifactName(artifact: ReportArtifact): string {\n const file = path.basename(artifact.path ?? '');\n return /-(?:expected|actual|diff|previous)\\.png$/.test(file)\n ? file\n : artifact.kind;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifactName(artifact),\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: FinalAttempt;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n ...(member?.appLog ? { appLog: member.appLog } : {}),\n ...(member?.skip ? { skip: member.skip } : {}),\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n tracePage: string | undefined,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n // e2e 0.19 writes a markdown trace page per test that kept one (failed\n // tests by default): what each step did, read and logged.\n if (tracePage !== undefined) {\n attachments.push({\n name: 'trace',\n mediaType: 'text/markdown',\n path: path.resolve(projectRoot, tracePage),\n fileName: path.basename(tracePage),\n });\n }\n const appLog = attempt?.appLog ?? [];\n // Lines logged before the first step have no step to sit under.\n docs.push(...appLogNotes(appLog.filter((l) => l.step === undefined)));\n if (result.status !== 'skipped' && attempt?.skip !== undefined) {\n docs.push({\n kind: 'note',\n text: `Skipped itself after a failure: ${attempt.skip.reason}`,\n phase: 'runtime',\n });\n }\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups, appLog),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n /** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */\n traces?: ReadonlyMap<string, string>;\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results stay out of the docs.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(\n r,\n report,\n artifactsRoot,\n args.markers ?? [],\n projectRoot,\n args.traces?.get(r.id),\n ),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Start each run with an empty markers folder. The delete is small and\n // synchronous, which suits onEvent.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An explore run records findings, which the bug-bash skill turns into\n // scenarios. The last test run's raw-run.json stays as it is.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n // Added in e2e 0.19; undefined on an older runner.\n const traces = run.traces as FinishedRun['traces'] | undefined;\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(traces ? { traces } : {}),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that reached no test keeps the last test run's raw-run.json.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). This module\n * rephrases the common shapes for a living-docs page. Other shapes keep the\n * raw `api label`.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * A marker records where and when the test called it. The reporter groups\n * the e2e steps that ran after a marker under the marker's text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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","names":["path","noun"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executable-stories-e2e",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "executable-stories reporter for the e2e agentic test runner: turns its report-1 document into story docs",
6
6
  "type": "module",
@@ -23,17 +23,17 @@
23
23
  "e2e": ">=0.15.0"
24
24
  },
25
25
  "dependencies": {
26
- "executable-stories-core": "0.28.0"
26
+ "executable-stories-core": "0.28.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^26.6.1",
30
- "e2e": "0.15.1",
30
+ "e2e": "0.20.0",
31
31
  "tsup": "^8.5.1",
32
32
  "typescript": "~6.0.3",
33
33
  "vitest": "^5.0.1",
34
- "executable-stories-formatters": "1.22.0",
35
- "executable-stories-vitest": "8.10.0",
36
- "eslint-config-executable-stories": "0.2.0"
34
+ "eslint-config-executable-stories": "0.2.0",
35
+ "executable-stories-formatters": "1.24.1",
36
+ "executable-stories-vitest": "8.10.3"
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=22.12.0"