executable-stories-e2e 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -26
- package/dist/index.cjs +56 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +56 -11
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# executable-stories-e2e
|
|
2
2
|
|
|
3
|
-
Living documentation from [e2e](https://
|
|
3
|
+
Living documentation from [e2e](https://www.npmjs.com/package/e2e) runs. A reporter turns e2e's `report-1` document into the RawRun JSON every executable-stories adapter writes, so `executable-stories format` renders it like any other suite.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm i -D executable-stories-e2e executable-stories-formatters
|
|
@@ -13,44 +13,50 @@ import type { E2EConfig } from 'e2e';
|
|
|
13
13
|
import { storyReporter } from 'executable-stories-e2e';
|
|
14
14
|
|
|
15
15
|
export default {
|
|
16
|
-
targets: [{ engine: web(), app: { url: 'localhost:3000' } }],
|
|
16
|
+
targets: [{ engine: web(), app: { url: 'http://localhost:3000' } }],
|
|
17
17
|
reporters: ['list', storyReporter()],
|
|
18
18
|
} satisfies E2EConfig;
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npx e2e
|
|
22
|
+
npx e2e run
|
|
23
23
|
npx executable-stories format --format html,markdown # reads .executable-stories/raw-run.json
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Option: `storyReporter({ rawRunPath })` writes somewhere else, relative to the project root.
|
|
27
27
|
|
|
28
|
+
Works with e2e 0.15 and later. e2e 0.18 and later need Node.js `^22.22.3 || >=24.8.0`; fields a newer e2e adds are read when present.
|
|
29
|
+
|
|
28
30
|
## How a run maps
|
|
29
31
|
|
|
30
|
-
| e2e | executable-stories
|
|
31
|
-
| ------------------------------------------------------ |
|
|
32
|
-
| result (test × target) | scenario;
|
|
33
|
-
| `app`, `session`, `resource` step | Given
|
|
34
|
-
| `agent.act` and other agent steps, `locator`, `screen` | When
|
|
35
|
-
| `assertion` step, `agent.assert` | Then, `assertions: 1` when it reached a verdict
|
|
36
|
-
| repeated Given/When/Then | And
|
|
37
|
-
| step text | marker text
|
|
38
|
-
| `passed` / `flaky` / `failed` / `skipped` | pass / pass
|
|
39
|
-
| `timed-out` / `interrupted` | timeout / interrupted
|
|
40
|
-
| step `blocked`, `timed-out` / `cancelled` | fail / skip
|
|
41
|
-
| attempt screenshot, video, trace, download, log | attachment
|
|
42
|
-
| hosted video (`url`, no file) | video doc
|
|
43
|
-
| error `details.expected` / `observed` | expected / actual on the failure
|
|
44
|
-
| test tags | story tags
|
|
45
|
-
| agent step `explanation` | `Agent
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
| e2e | executable-stories |
|
|
33
|
+
| ------------------------------------------------------ | ----------------------------------------------------------------------- |
|
|
34
|
+
| result (test × target) | scenario; describes give the suite path |
|
|
35
|
+
| `app`, `session`, `resource` step | Given |
|
|
36
|
+
| `agent.act` and other agent steps, `locator`, `screen` | When |
|
|
37
|
+
| `assertion` step, `agent.assert` | Then, `assertions: 1` when it reached a verdict |
|
|
38
|
+
| repeated Given/When/Then | And |
|
|
39
|
+
| step text | marker text, the agent instruction, or a phrased locator or expect step |
|
|
40
|
+
| `passed` / `flaky` / `failed` / `skipped` | pass / pass with the retry counted / fail / skip |
|
|
41
|
+
| `timed-out` / `interrupted` | timeout / interrupted |
|
|
42
|
+
| step `blocked`, `timed-out` / `cancelled` | fail / skip, with e2e's error code in the message |
|
|
43
|
+
| attempt screenshot, video, trace, download, log | attachment on the step that produced it |
|
|
44
|
+
| hosted video (`url`, no file) | video doc |
|
|
45
|
+
| error `details.expected` / `observed` | expected / actual on the failure |
|
|
46
|
+
| test tags | story tags |
|
|
47
|
+
| agent step `explanation` | `Agent:` note under the step |
|
|
48
|
+
| `toHaveScreenshot` expected / actual / diff images | attachments on the step, paired in the report as a comparison |
|
|
49
|
+
| `trace.md` page (e2e 0.19+) | `trace` attachment (markdown) on the test |
|
|
50
|
+
| app log warning / error (e2e 0.19+) | `App <source> <level>:` note under the step that was running |
|
|
51
|
+
| skip reason on a failed attempt (e2e 0.19+) | `Skipped itself after a failure:` note on the scenario |
|
|
52
|
+
|
|
53
|
+
The report shows the final attempt. Unselected results stay out. A `--last-failed` or tag-filtered run is marked `runScope: "filtered"`, so it keeps the scenarios it did not run.
|
|
54
|
+
|
|
55
|
+
An `e2e explore` run, or a run that reached no test, leaves the last `raw-run.json` in place. The `bug-bash` skill turns explore findings into scenarios.
|
|
50
56
|
|
|
51
57
|
## Name steps in your own words
|
|
52
58
|
|
|
53
|
-
Without markers, step text comes from what e2e records: the agent's instruction, or a locator or `expect` step phrased as `click the "Increment" button`.
|
|
59
|
+
Without markers, step text comes from what e2e records: the agent's instruction, or a locator or `expect` step phrased as `click the "Increment" button`. Markers name a group of steps:
|
|
54
60
|
|
|
55
61
|
```ts
|
|
56
62
|
import { expect, test } from 'e2e';
|
|
@@ -67,6 +73,6 @@ test('pressing Increment twice reads 2', async ({ app, screen }) => {
|
|
|
67
73
|
});
|
|
68
74
|
```
|
|
69
75
|
|
|
70
|
-
Each marker
|
|
76
|
+
Each marker renders as a step with the e2e steps that ran after it listed beneath. It takes the worst status of those steps and counts their recorded claims as assertions.
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
A marker appends its call site and time to `.executable-stories/e2e-markers/<pid>.jsonl`. The reporter matches markers to results by test file, line range and attempt time, then deletes the folder. Call markers in the test body, before the steps they describe.
|
package/dist/index.cjs
CHANGED
|
@@ -225,14 +225,13 @@ function stepStatus(status) {
|
|
|
225
225
|
switch (status) {
|
|
226
226
|
case "passed":
|
|
227
227
|
return "pass";
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// The step's error message keeps the distinction.
|
|
228
|
+
// Blocked and timed-out steps stopped the scenario, so they read as fail;
|
|
229
|
+
// the error message keeps e2e's code.
|
|
231
230
|
case "failed":
|
|
232
231
|
case "blocked":
|
|
233
232
|
case "timed-out":
|
|
234
233
|
return "fail";
|
|
235
|
-
//
|
|
234
|
+
// A cancelled step did not finish, so it reads as not run.
|
|
236
235
|
case "cancelled":
|
|
237
236
|
return "skip";
|
|
238
237
|
default:
|
|
@@ -314,7 +313,20 @@ function agentExplanation(step) {
|
|
|
314
313
|
if (!why || step.error?.message.includes(why)) return [];
|
|
315
314
|
return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
|
|
316
315
|
}
|
|
317
|
-
|
|
316
|
+
var APP_LOG_SOURCE = {
|
|
317
|
+
console: "console",
|
|
318
|
+
error: "uncaught",
|
|
319
|
+
network: "network",
|
|
320
|
+
system: "system"
|
|
321
|
+
};
|
|
322
|
+
function appLogNotes(lines) {
|
|
323
|
+
return lines.filter((l) => l.level === "error" || l.level === "warning").map((l) => ({
|
|
324
|
+
kind: "note",
|
|
325
|
+
text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,
|
|
326
|
+
phase: "runtime"
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
function toSteps(groups, appLog) {
|
|
318
330
|
const out = [];
|
|
319
331
|
groups.forEach((group, index) => {
|
|
320
332
|
const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
|
|
@@ -329,7 +341,8 @@ function toSteps(groups) {
|
|
|
329
341
|
phase: "runtime"
|
|
330
342
|
}
|
|
331
343
|
] : [],
|
|
332
|
-
...agentExplanation(c)
|
|
344
|
+
...agentExplanation(c),
|
|
345
|
+
...appLogNotes(appLog.filter((l) => l.step === c.index))
|
|
333
346
|
]);
|
|
334
347
|
out.push({
|
|
335
348
|
id: `step-${index}`,
|
|
@@ -342,6 +355,10 @@ function toSteps(groups) {
|
|
|
342
355
|
});
|
|
343
356
|
return out;
|
|
344
357
|
}
|
|
358
|
+
function artifactName(artifact) {
|
|
359
|
+
const file = path2.basename(artifact.path ?? "");
|
|
360
|
+
return /-(?:expected|actual|diff|previous)\.png$/.test(file) ? file : artifact.kind;
|
|
361
|
+
}
|
|
345
362
|
function toMedia(attempt, groups, artifactsRoot) {
|
|
346
363
|
const attachments = [];
|
|
347
364
|
const docs = [];
|
|
@@ -358,7 +375,7 @@ function toMedia(attempt, groups, artifactsRoot) {
|
|
|
358
375
|
)
|
|
359
376
|
);
|
|
360
377
|
attachments.push({
|
|
361
|
-
name: artifact
|
|
378
|
+
name: artifactName(artifact),
|
|
362
379
|
mediaType: artifact.mediaType,
|
|
363
380
|
path: path2.join(artifactsRoot, artifact.path),
|
|
364
381
|
fileName: path2.basename(artifact.path),
|
|
@@ -382,7 +399,9 @@ function finalAttempt(result, report) {
|
|
|
382
399
|
startedAt: member?.startedAt ?? last.startedAt,
|
|
383
400
|
durationMs: member?.durationMs ?? last.durationMs,
|
|
384
401
|
artifacts: last.artifacts,
|
|
385
|
-
steps: member?.steps ?? []
|
|
402
|
+
steps: member?.steps ?? [],
|
|
403
|
+
...member?.appLog ? { appLog: member.appLog } : {},
|
|
404
|
+
...member?.skip ? { skip: member.skip } : {}
|
|
386
405
|
},
|
|
387
406
|
count: attempts.length
|
|
388
407
|
};
|
|
@@ -400,7 +419,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
|
|
|
400
419
|
(m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
|
|
401
420
|
).sort((a, b) => a.at - b.at);
|
|
402
421
|
}
|
|
403
|
-
function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
422
|
+
function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
|
|
404
423
|
const scenario = result.titlePath.at(-1) ?? result.testId;
|
|
405
424
|
const suitePath = result.titlePath.slice(0, -1);
|
|
406
425
|
const { attempt, count } = finalAttempt(result, report);
|
|
@@ -415,6 +434,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
415
434
|
groups,
|
|
416
435
|
artifactsRoot
|
|
417
436
|
);
|
|
437
|
+
if (tracePage !== void 0) {
|
|
438
|
+
attachments.push({
|
|
439
|
+
name: "trace",
|
|
440
|
+
mediaType: "text/markdown",
|
|
441
|
+
path: path2.resolve(projectRoot, tracePage),
|
|
442
|
+
fileName: path2.basename(tracePage)
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
const appLog = attempt?.appLog ?? [];
|
|
446
|
+
docs.push(...appLogNotes(appLog.filter((l) => l.step === void 0)));
|
|
447
|
+
if (result.status !== "skipped" && attempt?.skip !== void 0) {
|
|
448
|
+
docs.push({
|
|
449
|
+
kind: "note",
|
|
450
|
+
text: `Skipped itself after a failure: ${attempt.skip.reason}`,
|
|
451
|
+
phase: "runtime"
|
|
452
|
+
});
|
|
453
|
+
}
|
|
418
454
|
const error = attempt?.error;
|
|
419
455
|
const details = error?.details;
|
|
420
456
|
const stepEvents = groups.map((group, index) => {
|
|
@@ -434,7 +470,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
434
470
|
titlePath: [...result.titlePath],
|
|
435
471
|
story: {
|
|
436
472
|
scenario,
|
|
437
|
-
steps: toSteps(groups),
|
|
473
|
+
steps: toSteps(groups, appLog),
|
|
438
474
|
...result.tags.length > 0 ? { tags: [...result.tags] } : {},
|
|
439
475
|
...suitePath.length > 0 ? { suitePath } : {},
|
|
440
476
|
...docs.length > 0 ? { docs } : {}
|
|
@@ -462,7 +498,14 @@ function toRawRun(args) {
|
|
|
462
498
|
const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
|
|
463
499
|
return {
|
|
464
500
|
testCases: results.map(
|
|
465
|
-
(r) => toTestCase(
|
|
501
|
+
(r) => toTestCase(
|
|
502
|
+
r,
|
|
503
|
+
report,
|
|
504
|
+
artifactsRoot,
|
|
505
|
+
args.markers ?? [],
|
|
506
|
+
projectRoot,
|
|
507
|
+
args.traces?.get(r.id)
|
|
508
|
+
)
|
|
466
509
|
),
|
|
467
510
|
startedAtMs: Date.parse(report.run.startedAt),
|
|
468
511
|
finishedAtMs: Date.parse(report.run.finishedAt),
|
|
@@ -506,11 +549,13 @@ function storyReporter(options = {}) {
|
|
|
506
549
|
await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
|
|
507
550
|
return [{ label: "Stories", text: "skipped (explore run)" }];
|
|
508
551
|
}
|
|
552
|
+
const traces = run.traces;
|
|
509
553
|
const rawRun = toRawRun({
|
|
510
554
|
report: run.report,
|
|
511
555
|
projectRoot: run.projectRoot,
|
|
512
556
|
artifactsRoot: run.artifactsRoot,
|
|
513
557
|
markers: await readMarkers(markersDir),
|
|
558
|
+
...traces ? { traces } : {},
|
|
514
559
|
...run.lastRun ? { lastRun: run.lastRun } : {}
|
|
515
560
|
});
|
|
516
561
|
await (0, import_promises.rm)(markersDir, { recursive: true, force: true });
|
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 // Lossy: our step model has no \"blocked\" (the agent could not reach a\n // verdict) or \"timed-out\"; both stopped the scenario, so both read as fail.\n // The step's error message keeps the distinction.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // Lossy: a cancelled step never finished; it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nfunction toSteps(groups: readonly Group[]): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // Only recorded claims are visible: e2e's synchronous value matchers\n // (`expect(2).toBe(2)`) leave no step. So 0 seen is not 0 made, and the\n // count is omitted rather than graded as unasserted.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifact.kind,\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: Pick<\n ReportAttempt,\n 'error' | 'startedAt' | 'durationMs' | 'artifacts' | 'steps'\n >;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n // ponytail: two repeats of one test running at the same moment in different\n // workers cannot be told apart; markers would need the worker pid in the report.\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results are report-only, as e2e's own JUnit reporter treats them.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Markers from an earlier run must not leak into this one. Small and\n // synchronous, as onEvent must not block for long.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An `e2e explore` run has findings, not tests: unverified leads that\n // belong in a bug bash, not in living docs. Writing would also replace\n // the last test run's raw-run.json with an empty one.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that never reached a test (a device that failed to boot, an\n // invalid config) has nothing to say; an empty file would only replace\n // the last real run.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). That reads as code\n * in a living-docs page, so the common shapes are rephrased here. Anything\n * this does not recognise keeps the raw `api label`, so nothing is lost.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * e2e has no step API of its own and no channel from a test back to a\n * reporter, so a marker records where it was called and when. The reporter\n * then groups the e2e steps that ran after a marker under its text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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","names":["import_node_fs","path","noun"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n/**\n * One line of what the app did during an attempt (e2e 0.19+): console output,\n * an uncaught error, a failed request or a 4xx/5xx response, with the index of\n * the step that was running.\n */\ntype AppLogLine = NonNullable<ReportAttempt['appLog']>[number];\n\ntype FinalAttempt = Pick<\n ReportAttempt,\n | 'error'\n | 'startedAt'\n | 'durationMs'\n | 'artifacts'\n | 'steps'\n | 'appLog'\n | 'skip'\n>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Blocked and timed-out steps stopped the scenario, so they read as fail;\n // the error message keeps e2e's code.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // A cancelled step did not finish, so it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nconst APP_LOG_SOURCE: Readonly<Record<string, string>> = {\n console: 'console',\n error: 'uncaught',\n network: 'network',\n system: 'system',\n};\n\n/** Warnings and errors the app logged while a step ran; info lines are noise in docs. */\nfunction appLogNotes(lines: readonly AppLogLine[]): DocEntry[] {\n return lines\n .filter((l) => l.level === 'error' || l.level === 'warning')\n .map((l) => ({\n kind: 'note' as const,\n text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,\n phase: 'runtime' as const,\n }));\n}\n\nfunction toSteps(\n groups: readonly Group[],\n appLog: readonly AppLogLine[],\n): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // e2e records locator and agent claims as steps. A plain value matcher\n // such as `expect(2).toBe(2)` records none, so a group without a recorded\n // claim leaves the count out.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ...appLogNotes(appLog.filter((l) => l.step === c.index)),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * A failed `toHaveScreenshot` attaches `<stem>-expected.png`, `-actual.png`\n * and `-diff.png` to its step, all of kind `screenshot`. They keep their file\n * name, as Playwright names the same trio, so the report pairs them.\n */\nfunction artifactName(artifact: ReportArtifact): string {\n const file = path.basename(artifact.path ?? '');\n return /-(?:expected|actual|diff|previous)\\.png$/.test(file)\n ? file\n : artifact.kind;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifactName(artifact),\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: FinalAttempt;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n ...(member?.appLog ? { appLog: member.appLog } : {}),\n ...(member?.skip ? { skip: member.skip } : {}),\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n tracePage: string | undefined,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n // e2e 0.19 writes a markdown trace page per test that kept one (failed\n // tests by default): what each step did, read and logged.\n if (tracePage !== undefined) {\n attachments.push({\n name: 'trace',\n mediaType: 'text/markdown',\n path: path.resolve(projectRoot, tracePage),\n fileName: path.basename(tracePage),\n });\n }\n const appLog = attempt?.appLog ?? [];\n // Lines logged before the first step have no step to sit under.\n docs.push(...appLogNotes(appLog.filter((l) => l.step === undefined)));\n if (result.status !== 'skipped' && attempt?.skip !== undefined) {\n docs.push({\n kind: 'note',\n text: `Skipped itself after a failure: ${attempt.skip.reason}`,\n phase: 'runtime',\n });\n }\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups, appLog),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n /** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */\n traces?: ReadonlyMap<string, string>;\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results stay out of the docs.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(\n r,\n report,\n artifactsRoot,\n args.markers ?? [],\n projectRoot,\n args.traces?.get(r.id),\n ),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Start each run with an empty markers folder. The delete is small and\n // synchronous, which suits onEvent.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An explore run records findings, which the bug-bash skill turns into\n // scenarios. The last test run's raw-run.json stays as it is.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n // Added in e2e 0.19; undefined on an older runner.\n const traces = run.traces as FinishedRun['traces'] | undefined;\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(traces ? { traces } : {}),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that reached no test keeps the last test run's raw-run.json.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). This module\n * rephrases the common shapes for a living-docs page. Other shapes keep the\n * raw `api label`.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * A marker records where and when the test called it. The reporter groups\n * the e2e steps that ran after a marker under the marker's text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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","names":["import_node_fs","path","noun"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,9 +4,8 @@ import { RawRun } from 'executable-stories-core/types/raw';
|
|
|
4
4
|
/**
|
|
5
5
|
* Step markers for e2e tests.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* then groups the e2e steps that ran after a marker under its text:
|
|
7
|
+
* A marker records where and when the test called it. The reporter groups
|
|
8
|
+
* the e2e steps that ran after a marker under the marker's text:
|
|
10
9
|
*
|
|
11
10
|
* ```ts
|
|
12
11
|
* test('incrementing shows the new count', async ({ app, screen }) => {
|
|
@@ -70,7 +69,10 @@ declare function toRawRun(args: {
|
|
|
70
69
|
lastRun?: Report;
|
|
71
70
|
/** Step markers the run's tests recorded with `story.given/when/then`. */
|
|
72
71
|
markers?: readonly Marker[];
|
|
72
|
+
/** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */
|
|
73
|
+
traces?: ReadonlyMap<string, string>;
|
|
73
74
|
}): RawRun;
|
|
75
|
+
/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
|
|
74
76
|
declare function storyReporter(options?: StoryReporterOptions): Reporter;
|
|
75
77
|
|
|
76
78
|
export { type Marker, type MarkerKeyword, type StoryReporterOptions, phraseStep, story, storyReporter, toRawRun };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,9 +4,8 @@ import { RawRun } from 'executable-stories-core/types/raw';
|
|
|
4
4
|
/**
|
|
5
5
|
* Step markers for e2e tests.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* then groups the e2e steps that ran after a marker under its text:
|
|
7
|
+
* A marker records where and when the test called it. The reporter groups
|
|
8
|
+
* the e2e steps that ran after a marker under the marker's text:
|
|
10
9
|
*
|
|
11
10
|
* ```ts
|
|
12
11
|
* test('incrementing shows the new count', async ({ app, screen }) => {
|
|
@@ -70,7 +69,10 @@ declare function toRawRun(args: {
|
|
|
70
69
|
lastRun?: Report;
|
|
71
70
|
/** Step markers the run's tests recorded with `story.given/when/then`. */
|
|
72
71
|
markers?: readonly Marker[];
|
|
72
|
+
/** e2e 0.19+: each kept `trace.md` page by result id, as a path from the project root. */
|
|
73
|
+
traces?: ReadonlyMap<string, string>;
|
|
73
74
|
}): RawRun;
|
|
75
|
+
/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */
|
|
74
76
|
declare function storyReporter(options?: StoryReporterOptions): Reporter;
|
|
75
77
|
|
|
76
78
|
export { type Marker, type MarkerKeyword, type StoryReporterOptions, phraseStep, story, storyReporter, toRawRun };
|
package/dist/index.js
CHANGED
|
@@ -188,14 +188,13 @@ function stepStatus(status) {
|
|
|
188
188
|
switch (status) {
|
|
189
189
|
case "passed":
|
|
190
190
|
return "pass";
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
// The step's error message keeps the distinction.
|
|
191
|
+
// Blocked and timed-out steps stopped the scenario, so they read as fail;
|
|
192
|
+
// the error message keeps e2e's code.
|
|
194
193
|
case "failed":
|
|
195
194
|
case "blocked":
|
|
196
195
|
case "timed-out":
|
|
197
196
|
return "fail";
|
|
198
|
-
//
|
|
197
|
+
// A cancelled step did not finish, so it reads as not run.
|
|
199
198
|
case "cancelled":
|
|
200
199
|
return "skip";
|
|
201
200
|
default:
|
|
@@ -277,7 +276,20 @@ function agentExplanation(step) {
|
|
|
277
276
|
if (!why || step.error?.message.includes(why)) return [];
|
|
278
277
|
return [{ kind: "note", text: `Agent: ${why}`, phase: "runtime" }];
|
|
279
278
|
}
|
|
280
|
-
|
|
279
|
+
var APP_LOG_SOURCE = {
|
|
280
|
+
console: "console",
|
|
281
|
+
error: "uncaught",
|
|
282
|
+
network: "network",
|
|
283
|
+
system: "system"
|
|
284
|
+
};
|
|
285
|
+
function appLogNotes(lines) {
|
|
286
|
+
return lines.filter((l) => l.level === "error" || l.level === "warning").map((l) => ({
|
|
287
|
+
kind: "note",
|
|
288
|
+
text: `App ${APP_LOG_SOURCE[l.source] ?? l.source} ${l.level}: ${l.text}`,
|
|
289
|
+
phase: "runtime"
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
function toSteps(groups, appLog) {
|
|
281
293
|
const out = [];
|
|
282
294
|
groups.forEach((group, index) => {
|
|
283
295
|
const repeat = group.keyword !== "And" && group.keyword !== "But" && out.some((s) => s.keyword === group.keyword);
|
|
@@ -292,7 +304,8 @@ function toSteps(groups) {
|
|
|
292
304
|
phase: "runtime"
|
|
293
305
|
}
|
|
294
306
|
] : [],
|
|
295
|
-
...agentExplanation(c)
|
|
307
|
+
...agentExplanation(c),
|
|
308
|
+
...appLogNotes(appLog.filter((l) => l.step === c.index))
|
|
296
309
|
]);
|
|
297
310
|
out.push({
|
|
298
311
|
id: `step-${index}`,
|
|
@@ -305,6 +318,10 @@ function toSteps(groups) {
|
|
|
305
318
|
});
|
|
306
319
|
return out;
|
|
307
320
|
}
|
|
321
|
+
function artifactName(artifact) {
|
|
322
|
+
const file = path2.basename(artifact.path ?? "");
|
|
323
|
+
return /-(?:expected|actual|diff|previous)\.png$/.test(file) ? file : artifact.kind;
|
|
324
|
+
}
|
|
308
325
|
function toMedia(attempt, groups, artifactsRoot) {
|
|
309
326
|
const attachments = [];
|
|
310
327
|
const docs = [];
|
|
@@ -321,7 +338,7 @@ function toMedia(attempt, groups, artifactsRoot) {
|
|
|
321
338
|
)
|
|
322
339
|
);
|
|
323
340
|
attachments.push({
|
|
324
|
-
name: artifact
|
|
341
|
+
name: artifactName(artifact),
|
|
325
342
|
mediaType: artifact.mediaType,
|
|
326
343
|
path: path2.join(artifactsRoot, artifact.path),
|
|
327
344
|
fileName: path2.basename(artifact.path),
|
|
@@ -345,7 +362,9 @@ function finalAttempt(result, report) {
|
|
|
345
362
|
startedAt: member?.startedAt ?? last.startedAt,
|
|
346
363
|
durationMs: member?.durationMs ?? last.durationMs,
|
|
347
364
|
artifacts: last.artifacts,
|
|
348
|
-
steps: member?.steps ?? []
|
|
365
|
+
steps: member?.steps ?? [],
|
|
366
|
+
...member?.appLog ? { appLog: member.appLog } : {},
|
|
367
|
+
...member?.skip ? { skip: member.skip } : {}
|
|
349
368
|
},
|
|
350
369
|
count: attempts.length
|
|
351
370
|
};
|
|
@@ -363,7 +382,7 @@ function markersFor(result, attempt, report, markers, projectRoot) {
|
|
|
363
382
|
(m) => m.file === file && m.line >= from && m.line < to && m.at >= start && m.at <= end
|
|
364
383
|
).sort((a, b) => a.at - b.at);
|
|
365
384
|
}
|
|
366
|
-
function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
385
|
+
function toTestCase(result, report, artifactsRoot, markers, projectRoot, tracePage) {
|
|
367
386
|
const scenario = result.titlePath.at(-1) ?? result.testId;
|
|
368
387
|
const suitePath = result.titlePath.slice(0, -1);
|
|
369
388
|
const { attempt, count } = finalAttempt(result, report);
|
|
@@ -378,6 +397,23 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
378
397
|
groups,
|
|
379
398
|
artifactsRoot
|
|
380
399
|
);
|
|
400
|
+
if (tracePage !== void 0) {
|
|
401
|
+
attachments.push({
|
|
402
|
+
name: "trace",
|
|
403
|
+
mediaType: "text/markdown",
|
|
404
|
+
path: path2.resolve(projectRoot, tracePage),
|
|
405
|
+
fileName: path2.basename(tracePage)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const appLog = attempt?.appLog ?? [];
|
|
409
|
+
docs.push(...appLogNotes(appLog.filter((l) => l.step === void 0)));
|
|
410
|
+
if (result.status !== "skipped" && attempt?.skip !== void 0) {
|
|
411
|
+
docs.push({
|
|
412
|
+
kind: "note",
|
|
413
|
+
text: `Skipped itself after a failure: ${attempt.skip.reason}`,
|
|
414
|
+
phase: "runtime"
|
|
415
|
+
});
|
|
416
|
+
}
|
|
381
417
|
const error = attempt?.error;
|
|
382
418
|
const details = error?.details;
|
|
383
419
|
const stepEvents = groups.map((group, index) => {
|
|
@@ -397,7 +433,7 @@ function toTestCase(result, report, artifactsRoot, markers, projectRoot) {
|
|
|
397
433
|
titlePath: [...result.titlePath],
|
|
398
434
|
story: {
|
|
399
435
|
scenario,
|
|
400
|
-
steps: toSteps(groups),
|
|
436
|
+
steps: toSteps(groups, appLog),
|
|
401
437
|
...result.tags.length > 0 ? { tags: [...result.tags] } : {},
|
|
402
438
|
...suitePath.length > 0 ? { suitePath } : {},
|
|
403
439
|
...docs.length > 0 ? { docs } : {}
|
|
@@ -425,7 +461,14 @@ function toRawRun(args) {
|
|
|
425
461
|
const filtered = args.lastRun !== void 0 || results.length < report.run.results.length;
|
|
426
462
|
return {
|
|
427
463
|
testCases: results.map(
|
|
428
|
-
(r) => toTestCase(
|
|
464
|
+
(r) => toTestCase(
|
|
465
|
+
r,
|
|
466
|
+
report,
|
|
467
|
+
artifactsRoot,
|
|
468
|
+
args.markers ?? [],
|
|
469
|
+
projectRoot,
|
|
470
|
+
args.traces?.get(r.id)
|
|
471
|
+
)
|
|
429
472
|
),
|
|
430
473
|
startedAtMs: Date.parse(report.run.startedAt),
|
|
431
474
|
finishedAtMs: Date.parse(report.run.finishedAt),
|
|
@@ -469,11 +512,13 @@ function storyReporter(options = {}) {
|
|
|
469
512
|
await rm(markersDir, { recursive: true, force: true });
|
|
470
513
|
return [{ label: "Stories", text: "skipped (explore run)" }];
|
|
471
514
|
}
|
|
515
|
+
const traces = run.traces;
|
|
472
516
|
const rawRun = toRawRun({
|
|
473
517
|
report: run.report,
|
|
474
518
|
projectRoot: run.projectRoot,
|
|
475
519
|
artifactsRoot: run.artifactsRoot,
|
|
476
520
|
markers: await readMarkers(markersDir),
|
|
521
|
+
...traces ? { traces } : {},
|
|
477
522
|
...run.lastRun ? { lastRun: run.lastRun } : {}
|
|
478
523
|
});
|
|
479
524
|
await rm(markersDir, { recursive: true, force: true });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/phrase.ts","../src/story.ts"],"sourcesContent":["/**\n * executable-stories reporter for the `e2e` agentic test runner.\n *\n * The reporter reads the finished `report-1` document and writes the RawRun\n * JSON every other adapter writes. `executable-stories format` takes it from\n * there. Optional `story.given/when/then` markers in a test name the steps in\n * the test's own words; without them, step text is phrased from e2e's record.\n */\n\nimport { rmSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { FinishedRun, Report, Reporter, ReporterSummary } from 'e2e';\nimport type {\n RawAttachment,\n RawRun,\n RawStatus,\n RawStepEvent,\n RawTestCase,\n} from 'executable-stories-core/types/raw';\nimport type {\n DocEntry,\n StepKeyword,\n StoryStep,\n} from 'executable-stories-core/types/story';\nimport { phraseStep } from './phrase';\nimport { MARKERS_DIR, type Marker } from './story';\n\nexport { story, type Marker, type MarkerKeyword } from './story';\nexport { phraseStep } from './phrase';\n\ntype ReportResult = Report['run']['results'][number];\ntype ReportAttempt = ReportResult['attempts'][number];\ntype ReportStep = ReportAttempt['steps'][number];\ntype ReportArtifact = ReportAttempt['artifacts'][number];\ntype ReportError = NonNullable<ReportAttempt['error']>;\n\nexport interface StoryReporterOptions {\n /** Where to write the RawRun JSON, relative to the project root. Default: `.executable-stories/raw-run.json`, where `executable-stories format` looks first. */\n rawRunPath?: string;\n}\n\n/**\n * Exhaustive over e2e's unions at compile time, forgiving at run time: a newer\n * e2e may report a kind or status this version has never seen, and its\n * contract says to ignore what you do not know rather than fail the reporter.\n */\nfunction unknownValue<T>(_value: never, fallback: T): T {\n return fallback;\n}\n\nfunction resultStatus(status: ReportResult['status']): RawStatus {\n switch (status) {\n case 'passed':\n case 'flaky': // the final attempt passed; `retry` records the failed ones\n return 'pass';\n case 'failed':\n return 'fail';\n case 'skipped':\n return 'skip';\n case 'timed-out':\n return 'timeout';\n case 'interrupted':\n return 'interrupted';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\nfunction stepStatus(status: ReportStep['status']): RawStatus {\n switch (status) {\n case 'passed':\n return 'pass';\n // Lossy: our step model has no \"blocked\" (the agent could not reach a\n // verdict) or \"timed-out\"; both stopped the scenario, so both read as fail.\n // The step's error message keeps the distinction.\n case 'failed':\n case 'blocked':\n case 'timed-out':\n return 'fail';\n // Lossy: a cancelled step never finished; it reads as not run.\n case 'cancelled':\n return 'skip';\n default:\n return unknownValue(status, 'unknown');\n }\n}\n\n/** Arrange, act or assert, by what the step did. Repeats become `And` in {@link toSteps}. */\nfunction primaryKeyword(step: ReportStep): 'Given' | 'When' | 'Then' {\n switch (step.kind) {\n case 'app':\n case 'session':\n case 'resource':\n return 'Given';\n case 'locator':\n case 'screen':\n return 'When';\n case 'assertion':\n return 'Then';\n case 'agent':\n return step.api === 'agent.assert' ? 'Then' : 'When';\n default:\n return unknownValue(step.kind, 'When');\n }\n}\n\n/** Agent steps are prose already; the rest are phrased from the API they called on what. */\nfunction stepText(step: ReportStep): string {\n if (step.kind === 'agent' && step.label) return step.label;\n return phraseStep(step.api, step.label);\n}\n\nfunction errorMessage(error: ReportError): string {\n return `${error.code}: ${error.message}`;\n}\n\n/**\n * One rendered step: a marker with the e2e steps that ran under it, or a\n * single e2e step when the test has no markers before it.\n */\ninterface Group {\n keyword: Marker['keyword'];\n text: string;\n marker: boolean;\n children: ReportStep[];\n}\n\nfunction toGroups(\n steps: readonly ReportStep[],\n markers: readonly Marker[],\n projectRoot: string,\n): Group[] {\n const groups: Group[] = [];\n let pending = 0;\n let current: Group | undefined;\n // A marker goes before a step that started after it. Timestamps are whole\n // milliseconds, so on a tie the source position decides: a marker written\n // before the step in the same file, on an earlier line or earlier on the\n // same line, came first. Elsewhere, the marker wins.\n const before = (m: Marker, step: ReportStep | undefined): boolean => {\n if (step === undefined) return true;\n const at = Date.parse(step.startedAt);\n if (m.at !== at) return m.at < at;\n if (m.file !== path.resolve(projectRoot, step.source.file)) return true;\n return (\n m.line < step.source.line ||\n (m.line === step.source.line && (m.column ?? 0) < step.source.column)\n );\n };\n const takeMarkersBefore = (step: ReportStep | undefined): void => {\n while (pending < markers.length && before(markers[pending]!, step)) {\n const m = markers[pending++]!;\n current = {\n keyword: m.keyword,\n text: m.text,\n marker: true,\n children: [],\n };\n groups.push(current);\n }\n };\n for (const step of steps) {\n takeMarkersBefore(step);\n if (current) current.children.push(step);\n else\n groups.push({\n keyword: primaryKeyword(step),\n text: stepText(step),\n marker: false,\n children: [step],\n });\n }\n takeMarkersBefore(undefined);\n return groups;\n}\n\n/** A step that never reached a verdict (blocked, timed out, cancelled) asserted nothing we can see. */\nfunction isObservedClaim(step: ReportStep): boolean {\n return (\n primaryKeyword(step) === 'Then' &&\n (step.status === 'passed' || step.status === 'failed')\n );\n}\n\nfunction groupStatus(group: Group): RawStatus {\n const statuses = group.children.map((c) => stepStatus(c.status));\n for (const worst of ['fail', 'unknown', 'skip'] as const)\n if (statuses.includes(worst)) return worst;\n return 'pass';\n}\n\n/** The agent's explanation, unless the error the report shows already says it. */\nfunction agentExplanation(step: ReportStep): DocEntry[] {\n const why = step.explanation?.trim();\n if (!why || step.error?.message.includes(why)) return [];\n return [{ kind: 'note', text: `Agent: ${why}`, phase: 'runtime' }];\n}\n\nfunction toSteps(groups: readonly Group[]): StoryStep[] {\n const out: StoryStep[] = [];\n groups.forEach((group, index) => {\n const repeat =\n group.keyword !== 'And' &&\n group.keyword !== 'But' &&\n out.some((s) => s.keyword === group.keyword);\n const keyword: StepKeyword = repeat ? 'And' : group.keyword;\n const claims = group.children.filter(isObservedClaim).length;\n // Only recorded claims are visible: e2e's synchronous value matchers\n // (`expect(2).toBe(2)`) leave no step. So 0 seen is not 0 made, and the\n // count is omitted rather than graded as unasserted.\n const assertions = claims > 0 ? { assertions: claims } : {};\n // A marker lists the e2e steps under it; any step adds the agent's own\n // account of what it did or why it judged a claim true.\n const docs: DocEntry[] = group.children.flatMap((c): DocEntry[] => [\n ...(group.marker\n ? [\n {\n kind: 'note' as const,\n text: stepText(c),\n phase: 'runtime' as const,\n },\n ]\n : []),\n ...agentExplanation(c),\n ]);\n out.push({\n id: `step-${index}`,\n keyword,\n text: group.text,\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...assertions,\n ...(docs.length > 0 ? { docs } : {}),\n });\n });\n return out;\n}\n\n/**\n * Artifacts become attachments, the shape the Playwright adapter gives\n * screenshots, videos and traces. A step-produced artifact is pinned to its\n * step. A hosted video has a URL and no file, so it becomes a video doc.\n */\nfunction toMedia(\n attempt: {\n artifacts: readonly ReportArtifact[];\n steps: readonly ReportStep[];\n },\n groups: readonly Group[],\n artifactsRoot: string,\n): { attachments: RawAttachment[]; docs: DocEntry[] } {\n const attachments: RawAttachment[] = [];\n const docs: DocEntry[] = [];\n for (const artifact of attempt.artifacts) {\n if (artifact.path === undefined) {\n if (\n artifact.kind === 'video' &&\n artifact.url !== undefined &&\n /^https?:\\/\\//.test(artifact.url)\n ) {\n docs.push({ kind: 'video', path: artifact.url, phase: 'runtime' });\n }\n continue;\n }\n const stepIndex = groups.findIndex((group) =>\n group.children.some(\n (step) =>\n step.artifacts.includes(artifact.id) ||\n (artifact.producer?.kind === 'step' &&\n artifact.producer.stepId === step.id),\n ),\n );\n attachments.push({\n name: artifact.kind,\n mediaType: artifact.mediaType,\n path: path.join(artifactsRoot, artifact.path),\n fileName: path.basename(artifact.path),\n ...(artifact.size !== undefined ? { byteLength: artifact.size } : {}),\n ...(stepIndex >= 0 ? { stepIndex, stepId: `step-${stepIndex}` } : {}),\n });\n }\n return { attachments, docs };\n}\n\n/** The final attempt, wherever the report keeps it: a serial member's lives on its group. */\nfunction finalAttempt(\n result: ReportResult,\n report: Report,\n): {\n attempt?: Pick<\n ReportAttempt,\n 'error' | 'startedAt' | 'durationMs' | 'artifacts' | 'steps'\n >;\n count: number;\n} {\n if (result.serialGroupId === undefined) {\n return { attempt: result.attempts.at(-1), count: result.attempts.length };\n }\n const attempts =\n report.run.serialGroups.find((g) => g.id === result.serialGroupId)\n ?.attempts ?? [];\n const last = attempts.at(-1);\n const member = last?.members.find((m) => m.testId === result.testId);\n if (last === undefined) return { count: 0 };\n return {\n attempt: {\n error: member?.error ?? last.error,\n startedAt: member?.startedAt ?? last.startedAt,\n durationMs: member?.durationMs ?? last.durationMs,\n artifacts: last.artifacts,\n steps: member?.steps ?? [],\n },\n count: attempts.length,\n };\n}\n\n/**\n * The markers one attempt recorded: called from the test's own file, below its\n * declaration and above the next test's, while the attempt ran.\n */\nfunction markersFor(\n result: ReportResult,\n attempt: Pick<ReportAttempt, 'startedAt' | 'durationMs'> | undefined,\n report: Report,\n markers: readonly Marker[],\n projectRoot: string,\n): Marker[] {\n if (attempt === undefined || markers.length === 0) return [];\n const file = path.resolve(projectRoot, result.file);\n const from = result.source.line;\n const to = Math.min(\n ...report.run.results\n .filter((r) => r.file === result.file && r.source.line > from)\n .map((r) => r.source.line),\n );\n const start = Date.parse(attempt.startedAt);\n const end = start + attempt.durationMs + 1000; // slack for clock reads on either side\n // ponytail: two repeats of one test running at the same moment in different\n // workers cannot be told apart; markers would need the worker pid in the report.\n return markers\n .filter(\n (m) =>\n m.file === file &&\n m.line >= from &&\n m.line < to &&\n m.at >= start &&\n m.at <= end,\n )\n .sort((a, b) => a.at - b.at);\n}\n\nfunction toTestCase(\n result: ReportResult,\n report: Report,\n artifactsRoot: string,\n markers: readonly Marker[],\n projectRoot: string,\n): RawTestCase {\n const scenario = result.titlePath.at(-1) ?? result.testId;\n const suitePath = result.titlePath.slice(0, -1);\n const { attempt, count } = finalAttempt(result, report);\n const steps = attempt?.steps ?? [];\n const groups = toGroups(\n steps,\n markersFor(result, attempt, report, markers, projectRoot),\n projectRoot,\n );\n const { attachments, docs } = toMedia(\n { artifacts: attempt?.artifacts ?? [], steps },\n groups,\n artifactsRoot,\n );\n const error = attempt?.error;\n const details = error?.details;\n const stepEvents: RawStepEvent[] = groups.map((group, index) => {\n const failed = group.children.find((c) => c.error !== undefined);\n return {\n index,\n stepId: `step-${index}`,\n title: group.text,\n status: groupStatus(group),\n durationMs: group.children.reduce((sum, c) => sum + c.durationMs, 0),\n ...(failed?.error ? { errorMessage: errorMessage(failed.error) } : {}),\n };\n });\n\n return {\n externalId: result.id,\n title: scenario,\n titlePath: [...result.titlePath],\n story: {\n scenario,\n steps: toSteps(groups),\n ...(result.tags.length > 0 ? { tags: [...result.tags] } : {}),\n ...(suitePath.length > 0 ? { suitePath } : {}),\n ...(docs.length > 0 ? { docs } : {}),\n },\n sourceFile: result.file,\n sourceLine: Math.max(1, result.source.line),\n status: resultStatus(result.status),\n durationMs: attempt?.durationMs ?? 0,\n ...(error\n ? {\n error: {\n message: errorMessage(error),\n ...(details?.expected !== undefined\n ? { expected: details.expected }\n : {}),\n ...(details?.observed !== undefined\n ? { actual: details.observed }\n : {}),\n },\n }\n : {}),\n ...(stepEvents.length > 0 ? { stepEvents } : {}),\n ...(attachments.length > 0 ? { attachments } : {}),\n retry: Math.max(0, count - 1),\n projectName: result.targetId,\n };\n}\n\n/**\n * Map an e2e `report-1` document to a RawRun. Pure: artifact paths resolve\n * against `artifactsRoot` and nothing is read from disk.\n */\nexport function toRawRun(args: {\n report: Report;\n projectRoot: string;\n artifactsRoot: string;\n /** Set when the run was `--last-failed`: it then covered only part of the suite. */\n lastRun?: Report;\n /** Step markers the run's tests recorded with `story.given/when/then`. */\n markers?: readonly Marker[];\n}): RawRun {\n const { report, projectRoot, artifactsRoot } = args;\n // Unselected results are report-only, as e2e's own JUnit reporter treats them.\n const results = report.run.results.filter((r) => r.selected !== false);\n const filtered =\n args.lastRun !== undefined || results.length < report.run.results.length;\n return {\n testCases: results.map((r) =>\n toTestCase(r, report, artifactsRoot, args.markers ?? [], projectRoot),\n ),\n startedAtMs: Date.parse(report.run.startedAt),\n finishedAtMs: Date.parse(report.run.finishedAt),\n projectRoot,\n // Only \"filtered\" is claimed: e2e can narrow a run in ways the report\n // does not record, so \"full\" (which retires scenarios) is never guessed.\n ...(filtered ? { runScope: 'filtered' as const } : {}),\n coveredSourceFiles: [...new Set(results.map((r) => r.file))].sort(),\n ...(report.run.vcs?.commit ? { gitSha: report.run.vcs.commit } : {}),\n };\n}\n\n/** An e2e reporter that writes the run as RawRun JSON for the executable-stories CLI. */\nasync function readMarkers(dir: string): Promise<Marker[]> {\n const files = await readdir(dir).catch(() => [] as string[]);\n const markers: Marker[] = [];\n for (const file of files.filter((f) => f.endsWith('.jsonl'))) {\n for (const line of (await readFile(path.join(dir, file), 'utf8')).split(\n '\\n',\n )) {\n if (!line) continue;\n try {\n markers.push(JSON.parse(line) as Marker);\n } catch {\n // a worker killed mid-write leaves a torn last line; skip it\n }\n }\n }\n return markers;\n}\n\nexport function storyReporter(options: StoryReporterOptions = {}): Reporter {\n return {\n name: 'executable-stories',\n onEvent(event) {\n // Markers from an earlier run must not leak into this one. Small and\n // synchronous, as onEvent must not block for long.\n if (event.type === 'run-started')\n rmSync(path.resolve(event.projectRoot, MARKERS_DIR), {\n recursive: true,\n force: true,\n });\n },\n async onRunFinished(run: FinishedRun): Promise<ReporterSummary> {\n const markersDir = path.resolve(run.projectRoot, MARKERS_DIR);\n // An `e2e explore` run has findings, not tests: unverified leads that\n // belong in a bug bash, not in living docs. Writing would also replace\n // the last test run's raw-run.json with an empty one.\n if (run.report.run.explore !== undefined) {\n await rm(markersDir, { recursive: true, force: true });\n return [{ label: 'Stories', text: 'skipped (explore run)' }];\n }\n const rawRun = toRawRun({\n report: run.report,\n projectRoot: run.projectRoot,\n artifactsRoot: run.artifactsRoot,\n markers: await readMarkers(markersDir),\n ...(run.lastRun ? { lastRun: run.lastRun } : {}),\n });\n await rm(markersDir, { recursive: true, force: true });\n // A run that never reached a test (a device that failed to boot, an\n // invalid config) has nothing to say; an empty file would only replace\n // the last real run.\n if (rawRun.testCases.length === 0) {\n return [{ label: 'Stories', text: 'skipped (no tests ran)' }];\n }\n const target = path.resolve(\n run.projectRoot,\n options.rawRunPath ?? '.executable-stories/raw-run.json',\n );\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(\n target,\n JSON.stringify({ schemaVersion: 1, ...rawRun }, null, 2),\n 'utf8',\n );\n return [\n { label: 'Stories', text: path.relative(run.projectRoot, target) },\n ];\n },\n };\n}\n","/**\n * Plain-language step text for e2e's non-agent steps.\n *\n * e2e records a step as `api` + `label`, where the label is the locator's own\n * description (`getByRole(\"button\", name: \"Increment\")`). That reads as code\n * in a living-docs page, so the common shapes are rephrased here. Anything\n * this does not recognise keeps the raw `api label`, so nothing is lost.\n */\n\nconst ROLE_NOUNS: Record<string, string> = {\n textbox: 'field',\n searchbox: 'search field',\n combobox: 'dropdown',\n spinbutton: 'number field',\n img: 'image',\n};\n\n/** `getByKind(\"value\"[, name: \"name\"])` with plain string arguments only; scopes, filters, regexes and nth() stay raw. */\nconst SIMPLE_QUERY =\n /^getBy(\\w+)\\((\"(?:[^\"\\\\]|\\\\.)*\")(?:, name: (\"(?:[^\"\\\\]|\\\\.)*\"))?\\)$/;\n\nfunction unquote(json: string): string {\n try {\n return JSON.parse(json) as string;\n } catch {\n return json;\n }\n}\n\n/** A locator description as a noun phrase, or `undefined` when it is not a simple query. */\nexport function describeTarget(label: string): string | undefined {\n const match = SIMPLE_QUERY.exec(label);\n if (!match) return undefined;\n const [, kind, rawValue, rawName] = match;\n const value = unquote(rawValue!);\n const name = rawName === undefined ? undefined : unquote(rawName);\n switch (kind) {\n case 'Role': {\n const noun = ROLE_NOUNS[value] ?? value;\n return name === undefined ? `the ${noun}` : `the \"${name}\" ${noun}`;\n }\n case 'Text':\n return `the text \"${value}\"`;\n case 'Label':\n case 'Placeholder':\n return `the \"${value}\" field`;\n case 'AltText':\n return `the \"${value}\" image`;\n case 'TestId':\n case 'Title':\n return `the \"${value}\" element`;\n default:\n return undefined;\n }\n}\n\nconst ACTIONS: Record<string, string> = {\n click: 'click',\n tap: 'tap',\n dblclick: 'double-click',\n hover: 'hover over',\n check: 'check',\n uncheck: 'uncheck',\n fill: 'fill in',\n clear: 'clear',\n press: 'press a key on',\n pressSequentially: 'type into',\n selectOption: 'choose an option in',\n longPress: 'long-press',\n swipe: 'swipe',\n focus: 'focus',\n};\n\n/** `expect.toX` → what it claims about the target. */\nconst CLAIMS: Record<string, [positive: string, negative: string]> = {\n toBeVisible: ['is visible', 'is not visible'],\n toBeHidden: ['is hidden', 'is not hidden'],\n toBeEnabled: ['is enabled', 'is not enabled'],\n toBeDisabled: ['is disabled', 'is not disabled'],\n toBeChecked: ['is checked', 'is not checked'],\n toBeFocused: ['is focused', 'is not focused'],\n toBeSelected: ['is selected', 'is not selected'],\n toBeExpanded: ['is expanded', 'is not expanded'],\n toBeAttached: ['is on the page', 'is not on the page'],\n toHaveText: ['shows the expected text', 'does not show the given text'],\n toContainText: [\n 'contains the expected text',\n 'does not contain the given text',\n ],\n toHaveValue: ['has the expected value', 'does not have the given value'],\n toHaveCount: [\n 'appears the expected number of times',\n 'does not appear the given number of times',\n ],\n toHaveAccessibleName: [\n 'has the expected accessible name',\n 'does not have the given accessible name',\n ],\n toHaveAttribute: [\n 'has the expected attribute',\n 'does not have the given attribute',\n ],\n};\n\nconst APP: Record<string, string> = {\n 'app.back': 'go back',\n 'app.restart': 'restart the app',\n 'app.clearState': 'clear the app state',\n 'session.launch': 'launch the app session',\n 'session.close': 'close the app session',\n};\n\n/** Step text for one non-agent step. */\nexport function phraseStep(api: string, label: string): string {\n const raw = label ? `${api} ${label}` : api;\n\n if (api === 'app.open') return label ? `open ${label}` : 'open the app';\n if (api === 'app.screenshot')\n return label ? `take a screenshot: ${label}` : 'take a screenshot';\n if (api === 'session.save')\n return label ? `save the session as ${label}` : 'save the session';\n if (APP[api]) return APP[api];\n\n const action = /^locator\\.(\\w+)$/.exec(api)?.[1];\n if (action !== undefined) {\n if (action === 'waitFor') {\n // `<target> → <state>`\n const [target, state] = label.split(' → ');\n const noun = target === undefined ? undefined : describeTarget(target);\n return noun && state ? `wait until ${noun} is ${state}` : raw;\n }\n const verb = ACTIONS[action];\n const noun = describeTarget(label);\n return verb && noun ? `${verb} ${noun}` : raw;\n }\n\n const claim = /^expect\\.(not\\.)?(\\w+)$/.exec(api);\n if (claim) {\n const words = CLAIMS[claim[2]!];\n const noun = describeTarget(label);\n return words && noun ? `${noun} ${words[claim[1] ? 1 : 0]}` : raw;\n }\n\n return raw;\n}\n","/**\n * Step markers for e2e tests.\n *\n * e2e has no step API of its own and no channel from a test back to a\n * reporter, so a marker records where it was called and when. The reporter\n * then groups the e2e steps that ran after a marker under its text:\n *\n * ```ts\n * test('incrementing shows the new count', async ({ app, screen }) => {\n * story.given('the counter page is open');\n * await app.open('/');\n * story.when('the user presses Increment twice');\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * await screen.getByRole('button', { name: 'Increment' }).click();\n * story.then('the count reads 2');\n * await expect(screen.getByRole('status')).toHaveText('2');\n * });\n * ```\n */\n\nimport { appendFileSync, mkdirSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type MarkerKeyword = 'Given' | 'When' | 'Then' | 'And' | 'But';\n\n/** One line of a markers file. */\nexport interface Marker {\n keyword: MarkerKeyword;\n text: string;\n /** Absolute path of the test file that called the marker. */\n file: string;\n line: number;\n /** Orders a marker before a step on its own line; absent reads as the line's start. */\n column?: number;\n /** Epoch milliseconds. */\n at: number;\n}\n\n/** Workers and the reporter both resolve this against the project root e2e runs from. */\nexport const MARKERS_DIR = '.executable-stories/e2e-markers';\n\nconst FRAME = /\\(?((?:file:\\/\\/)?[^\\s()]+?):(\\d+):(\\d+)\\)?$/;\n\n/** The first stack frame outside this module: the test line that called the marker. */\nfunction callSite():\n { file: string; line: number; column: number } | undefined {\n const frames = (new Error().stack ?? '').split('\\n').slice(1);\n let own: string | undefined;\n for (const frame of frames) {\n const match = FRAME.exec(frame.trim());\n if (!match) continue;\n const file = match[1]!.startsWith('file://')\n ? fileURLToPath(match[1]!)\n : match[1]!;\n own ??= file;\n if (file !== own)\n return { file, line: Number(match[2]), column: Number(match[3]) };\n }\n return undefined;\n}\n\nfunction mark(keyword: MarkerKeyword, text: string): void {\n const site = callSite();\n if (site === undefined) return;\n const marker: Marker = { keyword, text, ...site, at: Date.now() };\n const dir = path.resolve(MARKERS_DIR);\n mkdirSync(dir, { recursive: true });\n // One file per worker process; appends from one process stay in order.\n appendFileSync(\n path.join(dir, `${process.pid}.jsonl`),\n `${JSON.stringify(marker)}\\n`,\n );\n}\n\n/** Step markers. Call before the e2e steps the marker describes; they render under its text. */\nexport const story = {\n given: (text: string): void => mark('Given', text),\n when: (text: string): void => mark('When', text),\n then: (text: string): void => mark('Then', text),\n and: (text: string): void => mark('And', text),\n but: (text: string): void => mark('But', text),\n};\n"],"mappings":"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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",
|
|
@@ -23,17 +23,17 @@
|
|
|
23
23
|
"e2e": ">=0.15.0"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"executable-stories-core": "0.28.
|
|
26
|
+
"executable-stories-core": "0.28.1"
|
|
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
|
-
"executable-stories
|
|
35
|
-
"executable-stories-
|
|
36
|
-
"
|
|
34
|
+
"eslint-config-executable-stories": "0.2.0",
|
|
35
|
+
"executable-stories-formatters": "1.24.1",
|
|
36
|
+
"executable-stories-vitest": "8.10.3"
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=22.12.0"
|