executable-stories-e2e 0.1.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 +6 -0
- package/dist/index.cjs +53 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -7
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ npx executable-stories format --format html,markdown # reads .executable-stori
|
|
|
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
32
|
| e2e | executable-stories |
|
|
@@ -43,6 +45,10 @@ Option: `storyReporter({ rawRunPath })` writes somewhere else, relative to the p
|
|
|
43
45
|
| error `details.expected` / `observed` | expected / actual on the failure |
|
|
44
46
|
| test tags | story tags |
|
|
45
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 |
|
|
46
52
|
|
|
47
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.
|
|
48
54
|
|
package/dist/index.cjs
CHANGED
|
@@ -313,7 +313,20 @@ function agentExplanation(step) {
|
|
|
313
313
|
if (!why || step.error?.message.includes(why)) return [];
|
|
314
314
|
return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
|
|
315
315
|
}
|
|
316
|
-
|
|
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) {
|
|
317
330
|
const out = [];
|
|
318
331
|
groups.forEach((group, index) => {
|
|
319
332
|
const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
|
|
@@ -328,7 +341,8 @@ function toSteps(groups) {
|
|
|
328
341
|
phase: "runtime"
|
|
329
342
|
}
|
|
330
343
|
] : [],
|
|
331
|
-
...agentExplanation(c)
|
|
344
|
+
...agentExplanation(c),
|
|
345
|
+
...appLogNotes(appLog.filter((l) => l.step === c.index))
|
|
332
346
|
]);
|
|
333
347
|
out.push({
|
|
334
348
|
id: `step-${index}`,
|
|
@@ -341,6 +355,10 @@ function toSteps(groups) {
|
|
|
341
355
|
});
|
|
342
356
|
return out;
|
|
343
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
|
+
}
|
|
344
362
|
function toMedia(attempt, groups, artifactsRoot) {
|
|
345
363
|
const attachments = [];
|
|
346
364
|
const docs = [];
|
|
@@ -357,7 +375,7 @@ function toMedia(attempt, groups, artifactsRoot) {
|
|
|
357
375
|
)
|
|
358
376
|
);
|
|
359
377
|
attachments.push({
|
|
360
|
-
name: artifact
|
|
378
|
+
name: artifactName(artifact),
|
|
361
379
|
mediaType: artifact.mediaType,
|
|
362
380
|
path: path2.join(artifactsRoot, artifact.path),
|
|
363
381
|
fileName: path2.basename(artifact.path),
|
|
@@ -381,7 +399,9 @@ function finalAttempt(result, report) {
|
|
|
381
399
|
startedAt: member?.startedAt ?? last.startedAt,
|
|
382
400
|
durationMs: member?.durationMs ?? last.durationMs,
|
|
383
401
|
artifacts: last.artifacts,
|
|
384
|
-
steps: member?.steps ?? []
|
|
402
|
+
steps: member?.steps ?? [],
|
|
403
|
+
...member?.appLog ? { appLog: member.appLog } : {},
|
|
404
|
+
...member?.skip ? { skip: member.skip } : {}
|
|
385
405
|
},
|
|
386
406
|
count: attempts.length
|
|
387
407
|
};
|
|
@@ -399,7 +419,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
|
|
|
399
419
|
(m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
|
|
400
420
|
).sort((a, b) => a.at - b.at);
|
|
401
421
|
}
|
|
402
|
-
function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
422
|
+
function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
|
|
403
423
|
const scenario = result.titlePath.at(-1) ?? result.testId;
|
|
404
424
|
const suitePath = result.titlePath.slice(0, -1);
|
|
405
425
|
const { attempt, count } = finalAttempt(result, report);
|
|
@@ -414,6 +434,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
414
434
|
groups,
|
|
415
435
|
artifactsRoot
|
|
416
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
|
+
}
|
|
417
454
|
const error = attempt?.error;
|
|
418
455
|
const details = error?.details;
|
|
419
456
|
const stepEvents = groups.map((group, index) => {
|
|
@@ -433,7 +470,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
433
470
|
titlePath: [...result.titlePath],
|
|
434
471
|
story: {
|
|
435
472
|
scenario,
|
|
436
|
-
steps: toSteps(groups),
|
|
473
|
+
steps: toSteps(groups, appLog),
|
|
437
474
|
...result.tags.length > 0 ? { tags: [...result.tags] } : {},
|
|
438
475
|
...suitePath.length > 0 ? { suitePath } : {},
|
|
439
476
|
...docs.length > 0 ? { docs } : {}
|
|
@@ -461,7 +498,14 @@ function toRawRun(args) {
|
|
|
461
498
|
const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
|
|
462
499
|
return {
|
|
463
500
|
testCases: results.map(
|
|
464
|
-
(r) => toTestCase(
|
|
501
|
+
(r) => toTestCase(
|
|
502
|
+
r,
|
|
503
|
+
report,
|
|
504
|
+
artifactsRoot,
|
|
505
|
+
args.markers ?? [],
|
|
506
|
+
projectRoot,
|
|
507
|
+
args.traces?.get(r.id)
|
|
508
|
+
)
|
|
465
509
|
),
|
|
466
510
|
startedAtMs: Date.parse(report.run.startedAt),
|
|
467
511
|
finishedAtMs: Date.parse(report.run.finishedAt),
|
|
@@ -505,11 +549,13 @@ function storyReporter(options = {}) {
|
|
|
505
549
|
await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
|
|
506
550
|
return [{ label: "Stories", text: "skipped (explore run)" }];
|
|
507
551
|
}
|
|
552
|
+
const traces = run.traces;
|
|
508
553
|
const rawRun = toRawRun({
|
|
509
554
|
report: run.report,
|
|
510
555
|
projectRoot: run.projectRoot,
|
|
511
556
|
artifactsRoot: run.artifactsRoot,
|
|
512
557
|
markers: await readMarkers(markersDir),
|
|
558
|
+
...traces ? { traces } : {},
|
|
513
559
|
...run.lastRun ? { lastRun: run.lastRun } : {}
|
|
514
560
|
});
|
|
515
561
|
await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
|
package/dist/index.cjs.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 // 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\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 // 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 ]);\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 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 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(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\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 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 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"]}
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,IAAAA,kBAAuB;AACvB,sBAAwD;AACxD,IAAAC,QAAsB;;;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;;;AC7HA,qBAA0C;AAC1C,WAAsB;AACtB,sBAA8B;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,QACvC,+BAAc,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,gCAAU,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;;;AFjBA,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,IAGT,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,IAAM,iBAAmD;AAAA,EACvD,SAAS;AAAA,EACT,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AACV;AAGA,SAAS,YAAY,OAA0C;AAC7D,SAAO,MACJ,OAAO,CAAC,MAAM,EAAE,UAAU,WAAW,EAAE,UAAU,SAAS,EAC1D,IAAI,CAAC,OAAO;AAAA,IACX,MAAM;AAAA,IACN,MAAM,OAAO,eAAe,EAAE,MAAM,KAAK,EAAE,MAAM,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI;AAAA,IACvE,OAAO;AAAA,EACT,EAAE;AACN;AAEA,SAAS,QACP,QACA,QACa;AACb,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,MACrB,GAAG,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC;AAAA,IACzD,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,aAAa,UAAkC;AACtD,QAAM,OAAY,eAAS,SAAS,QAAQ,EAAE;AAC9C,SAAO,2CAA2C,KAAK,IAAI,IACvD,OACA,SAAS;AACf;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,aAAa,QAAQ;AAAA,MAC3B,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,QAIA;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,MACzB,GAAI,QAAQ,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MAClD,GAAI,QAAQ,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;AAAA,IAC9C;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;AACzC,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,WACa;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;AAGA,MAAI,cAAc,QAAW;AAC3B,gBAAY,KAAK;AAAA,MACf,MAAM;AAAA,MACN,WAAW;AAAA,MACX,MAAW,cAAQ,aAAa,SAAS;AAAA,MACzC,UAAe,eAAS,SAAS;AAAA,IACnC,CAAC;AAAA,EACH;AACA,QAAM,SAAS,SAAS,UAAU,CAAC;AAEnC,OAAK,KAAK,GAAG,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,MAAS,CAAC,CAAC;AACpE,MAAI,OAAO,WAAW,aAAa,SAAS,SAAS,QAAW;AAC9D,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,MAAM,mCAAmC,QAAQ,KAAK,MAAM;AAAA,MAC5D,OAAO;AAAA,IACT,CAAC;AAAA,EACH;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,QAAQ,MAAM;AAAA,MAC7B,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,MAUd;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;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,WAAW,CAAC;AAAA,QACjB;AAAA,QACA,KAAK,QAAQ,IAAI,EAAE,EAAE;AAAA,MACvB;AAAA,IACF;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;AAEA,eAAe,YAAY,KAAgC;AACzD,QAAM,QAAQ,UAAM,yBAAQ,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,UAAM,0BAAc,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;AAGO,SAAS,cAAc,UAAgC,CAAC,GAAa;AAC1E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,OAAO;AAGb,UAAI,MAAM,SAAS;AACjB,oCAAY,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;AAG5D,UAAI,IAAI,OAAO,IAAI,YAAY,QAAW;AACxC,kBAAM,oBAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,eAAO,CAAC,EAAE,OAAO,WAAW,MAAM,wBAAwB,CAAC;AAAA,MAC7D;AAEA,YAAM,SAAS,IAAI;AACnB,YAAM,SAAS,SAAS;AAAA,QACtB,QAAQ,IAAI;AAAA,QACZ,aAAa,IAAI;AAAA,QACjB,eAAe,IAAI;AAAA,QACnB,SAAS,MAAM,YAAY,UAAU;AAAA,QACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,QAC3B,GAAI,IAAI,UAAU,EAAE,SAAS,IAAI,QAAQ,IAAI,CAAC;AAAA,MAChD,CAAC;AACD,gBAAM,oBAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAErD,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,gBAAM,uBAAW,cAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,gBAAM;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":["import_node_fs","path","noun"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -69,6 +69,8 @@ declare function toRawRun(args: {
|
|
|
69
69
|
lastRun?: Report;
|
|
70
70
|
/** Step markers the run's tests recorded with `story.given/when/then`. */
|
|
71
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>;
|
|
72
74
|
}): RawRun;
|
|
73
75
|
/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
|
|
74
76
|
declare function storyReporter(options?: StoryReporterOptions): Reporter;
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ declare function toRawRun(args: {
|
|
|
69
69
|
lastRun?: Report;
|
|
70
70
|
/** Step markers the run's tests recorded with `story.given/when/then`. */
|
|
71
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>;
|
|
72
74
|
}): RawRun;
|
|
73
75
|
/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
|
|
74
76
|
declare function storyReporter(options?: StoryReporterOptions): Reporter;
|
package/dist/index.js
CHANGED
|
@@ -276,7 +276,20 @@ function agentExplanation(step) {
|
|
|
276
276
|
if (!why || step.error?.message.includes(why)) return [];
|
|
277
277
|
return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
|
|
278
278
|
}
|
|
279
|
-
|
|
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) {
|
|
280
293
|
const out = [];
|
|
281
294
|
groups.forEach((group, index) => {
|
|
282
295
|
const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
|
|
@@ -291,7 +304,8 @@ function toSteps(groups) {
|
|
|
291
304
|
phase: "runtime"
|
|
292
305
|
}
|
|
293
306
|
] : [],
|
|
294
|
-
...agentExplanation(c)
|
|
307
|
+
...agentExplanation(c),
|
|
308
|
+
...appLogNotes(appLog.filter((l) => l.step === c.index))
|
|
295
309
|
]);
|
|
296
310
|
out.push({
|
|
297
311
|
id: `step-${index}`,
|
|
@@ -304,6 +318,10 @@ function toSteps(groups) {
|
|
|
304
318
|
});
|
|
305
319
|
return out;
|
|
306
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
|
+
}
|
|
307
325
|
function toMedia(attempt, groups, artifactsRoot) {
|
|
308
326
|
const attachments = [];
|
|
309
327
|
const docs = [];
|
|
@@ -320,7 +338,7 @@ function toMedia(attempt, groups, artifactsRoot) {
|
|
|
320
338
|
)
|
|
321
339
|
);
|
|
322
340
|
attachments.push({
|
|
323
|
-
name: artifact
|
|
341
|
+
name: artifactName(artifact),
|
|
324
342
|
mediaType: artifact.mediaType,
|
|
325
343
|
path: path2.join(artifactsRoot, artifact.path),
|
|
326
344
|
fileName: path2.basename(artifact.path),
|
|
@@ -344,7 +362,9 @@ function finalAttempt(result, report) {
|
|
|
344
362
|
startedAt: member?.startedAt ?? last.startedAt,
|
|
345
363
|
durationMs: member?.durationMs ?? last.durationMs,
|
|
346
364
|
artifacts: last.artifacts,
|
|
347
|
-
steps: member?.steps ?? []
|
|
365
|
+
steps: member?.steps ?? [],
|
|
366
|
+
...member?.appLog ? { appLog: member.appLog } : {},
|
|
367
|
+
...member?.skip ? { skip: member.skip } : {}
|
|
348
368
|
},
|
|
349
369
|
count: attempts.length
|
|
350
370
|
};
|
|
@@ -362,7 +382,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
|
|
|
362
382
|
(m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
|
|
363
383
|
).sort((a, b) => a.at - b.at);
|
|
364
384
|
}
|
|
365
|
-
function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
385
|
+
function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
|
|
366
386
|
const scenario = result.titlePath.at(-1) ?? result.testId;
|
|
367
387
|
const suitePath = result.titlePath.slice(0, -1);
|
|
368
388
|
const { attempt, count } = finalAttempt(result, report);
|
|
@@ -377,6 +397,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
377
397
|
groups,
|
|
378
398
|
artifactsRoot
|
|
379
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
|
+
}
|
|
380
417
|
const error = attempt?.error;
|
|
381
418
|
const details = error?.details;
|
|
382
419
|
const stepEvents = groups.map((group, index) => {
|
|
@@ -396,7 +433,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
396
433
|
titlePath: [...result.titlePath],
|
|
397
434
|
story: {
|
|
398
435
|
scenario,
|
|
399
|
-
steps: toSteps(groups),
|
|
436
|
+
steps: toSteps(groups, appLog),
|
|
400
437
|
...result.tags.length > 0 ? { tags: [...result.tags] } : {},
|
|
401
438
|
...suitePath.length > 0 ? { suitePath } : {},
|
|
402
439
|
...docs.length > 0 ? { docs } : {}
|
|
@@ -424,7 +461,14 @@ function toRawRun(args) {
|
|
|
424
461
|
const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
|
|
425
462
|
return {
|
|
426
463
|
testCases: results.map(
|
|
427
|
-
(r) => toTestCase(
|
|
464
|
+
(r) => toTestCase(
|
|
465
|
+
r,
|
|
466
|
+
report,
|
|
467
|
+
artifactsRoot,
|
|
468
|
+
args.markers ?? [],
|
|
469
|
+
projectRoot,
|
|
470
|
+
args.traces?.get(r.id)
|
|
471
|
+
)
|
|
428
472
|
),
|
|
429
473
|
startedAtMs: Date.parse(report.run.startedAt),
|
|
430
474
|
finishedAtMs: Date.parse(report.run.finishedAt),
|
|
@@ -468,11 +512,13 @@ function storyReporter(options = {}) {
|
|
|
468
512
|
await rm(markersDir, { recursive: true, force: true });
|
|
469
513
|
return [{ label: "Stories", text: "skipped (explore run)" }];
|
|
470
514
|
}
|
|
515
|
+
const traces = run.traces;
|
|
471
516
|
const rawRun = toRawRun({
|
|
472
517
|
report: run.report,
|
|
473
518
|
projectRoot: run.projectRoot,
|
|
474
519
|
artifactsRoot: run.artifactsRoot,
|
|
475
520
|
markers: await readMarkers(markersDir),
|
|
521
|
+
...traces ? { traces } : {},
|
|
476
522
|
...run.lastRun ? { lastRun: run.lastRun } : {}
|
|
477
523
|
});
|
|
478
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 // 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\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 // 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 ]);\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 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 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(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\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 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 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"]}
|
|
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.
|
|
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",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^26.6.1",
|
|
30
|
-
"e2e": "0.
|
|
30
|
+
"e2e": "0.20.0",
|
|
31
31
|
"tsup": "^8.5.1",
|
|
32
32
|
"typescript": "~6.0.3",
|
|
33
33
|
"vitest": "^5.0.1",
|
|
34
34
|
"eslint-config-executable-stories": "0.2.0",
|
|
35
|
-
"executable-stories-formatters": "1.
|
|
36
|
-
"executable-stories-vitest": "8.10.
|
|
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"
|