@aplaytest/core 0.1.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.
Files changed (112) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/config/schema.d.ts +247 -0
  3. package/dist/config/schema.d.ts.map +1 -0
  4. package/dist/config/schema.js +279 -0
  5. package/dist/config/schema.js.map +1 -0
  6. package/dist/evidence/id.d.ts +17 -0
  7. package/dist/evidence/id.d.ts.map +1 -0
  8. package/dist/evidence/id.js +20 -0
  9. package/dist/evidence/id.js.map +1 -0
  10. package/dist/evidence/redact.d.ts +22 -0
  11. package/dist/evidence/redact.d.ts.map +1 -0
  12. package/dist/evidence/redact.js +97 -0
  13. package/dist/evidence/redact.js.map +1 -0
  14. package/dist/evidence/store.d.ts +51 -0
  15. package/dist/evidence/store.d.ts.map +1 -0
  16. package/dist/evidence/store.js +148 -0
  17. package/dist/evidence/store.js.map +1 -0
  18. package/dist/evidence/types.d.ts +183 -0
  19. package/dist/evidence/types.d.ts.map +1 -0
  20. package/dist/evidence/types.js +21 -0
  21. package/dist/evidence/types.js.map +1 -0
  22. package/dist/history/ingest-playwright.d.ts +26 -0
  23. package/dist/history/ingest-playwright.d.ts.map +1 -0
  24. package/dist/history/ingest-playwright.js +73 -0
  25. package/dist/history/ingest-playwright.js.map +1 -0
  26. package/dist/history/ingest.d.ts +29 -0
  27. package/dist/history/ingest.d.ts.map +1 -0
  28. package/dist/history/ingest.js +86 -0
  29. package/dist/history/ingest.js.map +1 -0
  30. package/dist/history/memory-index.d.ts +88 -0
  31. package/dist/history/memory-index.d.ts.map +1 -0
  32. package/dist/history/memory-index.js +236 -0
  33. package/dist/history/memory-index.js.map +1 -0
  34. package/dist/history/playwright-report.d.ts +102 -0
  35. package/dist/history/playwright-report.d.ts.map +1 -0
  36. package/dist/history/playwright-report.js +274 -0
  37. package/dist/history/playwright-report.js.map +1 -0
  38. package/dist/history/store.d.ts +73 -0
  39. package/dist/history/store.d.ts.map +1 -0
  40. package/dist/history/store.js +243 -0
  41. package/dist/history/store.js.map +1 -0
  42. package/dist/history/types.d.ts +69 -0
  43. package/dist/history/types.d.ts.map +1 -0
  44. package/dist/history/types.js +18 -0
  45. package/dist/history/types.js.map +1 -0
  46. package/dist/history/url.d.ts +79 -0
  47. package/dist/history/url.d.ts.map +1 -0
  48. package/dist/history/url.js +121 -0
  49. package/dist/history/url.js.map +1 -0
  50. package/dist/index.d.ts +39 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +31 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/locator/stability.d.ts +63 -0
  55. package/dist/locator/stability.d.ts.map +1 -0
  56. package/dist/locator/stability.js +127 -0
  57. package/dist/locator/stability.js.map +1 -0
  58. package/dist/taxonomy/classify.d.ts +63 -0
  59. package/dist/taxonomy/classify.d.ts.map +1 -0
  60. package/dist/taxonomy/classify.js +344 -0
  61. package/dist/taxonomy/classify.js.map +1 -0
  62. package/dist/taxonomy/kinds.d.ts +49 -0
  63. package/dist/taxonomy/kinds.d.ts.map +1 -0
  64. package/dist/taxonomy/kinds.js +139 -0
  65. package/dist/taxonomy/kinds.js.map +1 -0
  66. package/dist/taxonomy/parse-error.d.ts +85 -0
  67. package/dist/taxonomy/parse-error.d.ts.map +1 -0
  68. package/dist/taxonomy/parse-error.js +179 -0
  69. package/dist/taxonomy/parse-error.js.map +1 -0
  70. package/dist/version.d.ts +10 -0
  71. package/dist/version.d.ts.map +1 -0
  72. package/dist/version.js +10 -0
  73. package/dist/version.js.map +1 -0
  74. package/dist-cjs/.tsbuildinfo +1 -0
  75. package/dist-cjs/config/schema.js +283 -0
  76. package/dist-cjs/config/schema.js.map +1 -0
  77. package/dist-cjs/evidence/id.js +24 -0
  78. package/dist-cjs/evidence/id.js.map +1 -0
  79. package/dist-cjs/evidence/redact.js +103 -0
  80. package/dist-cjs/evidence/redact.js.map +1 -0
  81. package/dist-cjs/evidence/store.js +156 -0
  82. package/dist-cjs/evidence/store.js.map +1 -0
  83. package/dist-cjs/evidence/types.js +25 -0
  84. package/dist-cjs/evidence/types.js.map +1 -0
  85. package/dist-cjs/history/ingest-playwright.js +76 -0
  86. package/dist-cjs/history/ingest-playwright.js.map +1 -0
  87. package/dist-cjs/history/ingest.js +89 -0
  88. package/dist-cjs/history/ingest.js.map +1 -0
  89. package/dist-cjs/history/memory-index.js +242 -0
  90. package/dist-cjs/history/memory-index.js.map +1 -0
  91. package/dist-cjs/history/playwright-report.js +277 -0
  92. package/dist-cjs/history/playwright-report.js.map +1 -0
  93. package/dist-cjs/history/store.js +247 -0
  94. package/dist-cjs/history/store.js.map +1 -0
  95. package/dist-cjs/history/types.js +23 -0
  96. package/dist-cjs/history/types.js.map +1 -0
  97. package/dist-cjs/history/url.js +127 -0
  98. package/dist-cjs/history/url.js.map +1 -0
  99. package/dist-cjs/index.js +88 -0
  100. package/dist-cjs/index.js.map +1 -0
  101. package/dist-cjs/locator/stability.js +134 -0
  102. package/dist-cjs/locator/stability.js.map +1 -0
  103. package/dist-cjs/package.json +3 -0
  104. package/dist-cjs/taxonomy/classify.js +348 -0
  105. package/dist-cjs/taxonomy/classify.js.map +1 -0
  106. package/dist-cjs/taxonomy/kinds.js +145 -0
  107. package/dist-cjs/taxonomy/kinds.js.map +1 -0
  108. package/dist-cjs/taxonomy/parse-error.js +186 -0
  109. package/dist-cjs/taxonomy/parse-error.js.map +1 -0
  110. package/dist-cjs/version.js +13 -0
  111. package/dist-cjs/version.js.map +1 -0
  112. package/package.json +39 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The query engine, with no storage under it.
3
+ *
4
+ * Every driver ends up answering the same four questions — attempts in a
5
+ * window, distinct test keys, run count, prune — and the SQL that answers them
6
+ * carries semantics that took real incidents to get right: shard-scoped
7
+ * replacement, earliest-shard `startedAt`, ordering before the limit. Writing
8
+ * those a second time in a blob driver would mean maintaining two definitions
9
+ * of "the same history" and discovering the drift months later, as a flake
10
+ * score that differs between local and CI.
11
+ *
12
+ * So the semantics live here, once, over plain objects. `SqliteHistoryStore`
13
+ * keeps its own SQL (it is the local/dev driver and the schema is its own
14
+ * documentation); the Azure driver is this index plus blob I/O, which is the
15
+ * only shape that makes sense when the durable form is an append-only log of
16
+ * run records rather than a queryable database.
17
+ *
18
+ * Pure and synchronous: no clock, no filesystem, no network. That is what
19
+ * makes the query semantics testable without a container or an emulator.
20
+ */
21
+ import type { RunRecord } from './types.js';
22
+ import type { HistoricalAttempt, HistoryQuery, HistoryStore, TestKey } from './store.js';
23
+ /**
24
+ * Identifies the unit that is replaced wholesale on re-ingest.
25
+ *
26
+ * NOT the run id. Under sharding every shard reports the same run id, so
27
+ * keying on it alone means ingesting shard 2 discards shard 1 — the bug that
28
+ * once turned three shard files carrying four attempts into zero. A shard is
29
+ * the smallest thing a re-run can replace, so it is the key.
30
+ */
31
+ export declare function shardKeyOf(shard: {
32
+ current: number;
33
+ total: number;
34
+ } | null | undefined): string;
35
+ /**
36
+ * In-memory history with SQLite's query semantics and none of its storage.
37
+ *
38
+ * Add segments in any order; the answers do not depend on it, except where
39
+ * SQLite's own answers would not either (last writer wins on mutable run
40
+ * metadata).
41
+ */
42
+ export declare class HistoryIndex {
43
+ /** Keyed `runId\x00shardKey` — the replacement unit. */
44
+ private readonly segments;
45
+ add(run: RunRecord): void;
46
+ /** Drop everything, so a driver can reload from its backing store. */
47
+ clear(): void;
48
+ has(runId: string, shardKey: string): boolean;
49
+ /**
50
+ * One metadata row per run id, merged across its shards.
51
+ *
52
+ * `startedAt` takes the EARLIEST value: a run spanning six shards started
53
+ * when its first shard did, and letting the last shard ingested stamp the
54
+ * time would move the run inside the recency window depending on artifact
55
+ * download order. Everything else is last-writer-wins, matching the UPSERT.
56
+ */
57
+ private runMetadata;
58
+ attempts(query?: HistoryQuery): HistoricalAttempt[];
59
+ testKeys(): TestKey[];
60
+ runCount(): number;
61
+ /**
62
+ * Remove runs that started before the cutoff. Returns the number of RUNS
63
+ * dropped, not segments — a six-shard run pruned is one run, which is what
64
+ * `runCount()` counted it as.
65
+ */
66
+ prune(olderThanIso: string): {
67
+ removedRuns: number;
68
+ removedKeys: string[];
69
+ };
70
+ }
71
+ /**
72
+ * `HistoryStore` over a `HistoryIndex` and nothing else.
73
+ *
74
+ * This is what `--db :memory:` should always have meant. Reaching for
75
+ * `new SqliteHistoryStore(':memory:')` to get a throwaway store — as the MCP
76
+ * server and `aplaytest impact` both did — pulls in `node:sqlite`, a schema and a
77
+ * transaction to hold data that is discarded when the process exits.
78
+ */
79
+ export declare class MemoryHistoryStore implements HistoryStore {
80
+ private readonly index;
81
+ ingest(run: RunRecord): Promise<void>;
82
+ attempts(query?: HistoryQuery): Promise<HistoricalAttempt[]>;
83
+ testKeys(): Promise<TestKey[]>;
84
+ runCount(): Promise<number>;
85
+ prune(olderThanIso: string): Promise<number>;
86
+ close(): Promise<void>;
87
+ }
88
+ //# sourceMappingURL=memory-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-index.d.ts","sourceRoot":"","sources":["../../src/history/memory-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAA0B,SAAS,EAAE,MAAM,YAAY,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEzF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE/F;AAuED;;;;;;GAMG;AACH,qBAAa,YAAY;IACvB,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IAEvD,GAAG,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI;IAKzB,sEAAsE;IACtE,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI7C;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAwBnB,QAAQ,CAAC,KAAK,GAAE,YAAiB,GAAG,iBAAiB,EAAE;IAiCvD,QAAQ,IAAI,OAAO,EAAE;IAqBrB,QAAQ,IAAI,MAAM;IAMlB;;;;OAIG;IACH,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;CAgB5E;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAEtC,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,QAAQ,CAAC,KAAK,GAAE,YAAiB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAIhE,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI9B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAI3B,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -0,0 +1,236 @@
1
+ /**
2
+ * The query engine, with no storage under it.
3
+ *
4
+ * Every driver ends up answering the same four questions — attempts in a
5
+ * window, distinct test keys, run count, prune — and the SQL that answers them
6
+ * carries semantics that took real incidents to get right: shard-scoped
7
+ * replacement, earliest-shard `startedAt`, ordering before the limit. Writing
8
+ * those a second time in a blob driver would mean maintaining two definitions
9
+ * of "the same history" and discovering the drift months later, as a flake
10
+ * score that differs between local and CI.
11
+ *
12
+ * So the semantics live here, once, over plain objects. `SqliteHistoryStore`
13
+ * keeps its own SQL (it is the local/dev driver and the schema is its own
14
+ * documentation); the Azure driver is this index plus blob I/O, which is the
15
+ * only shape that makes sense when the durable form is an append-only log of
16
+ * run records rather than a queryable database.
17
+ *
18
+ * Pure and synchronous: no clock, no filesystem, no network. That is what
19
+ * makes the query semantics testable without a container or an emulator.
20
+ */
21
+ /**
22
+ * Identifies the unit that is replaced wholesale on re-ingest.
23
+ *
24
+ * NOT the run id. Under sharding every shard reports the same run id, so
25
+ * keying on it alone means ingesting shard 2 discards shard 1 — the bug that
26
+ * once turned three shard files carrying four attempts into zero. A shard is
27
+ * the smallest thing a re-run can replace, so it is the key.
28
+ */
29
+ export function shardKeyOf(shard) {
30
+ return shard === null || shard === undefined ? 'all' : `${shard.current}-of-${shard.total}`;
31
+ }
32
+ /**
33
+ * Composite-key separator. NUL rather than a space or a colon, because a run id
34
+ * is whatever `ATEST_RUN_ID` contained and both of those are legal in one — and
35
+ * `"a b" + sep + "c"` colliding with `"a" + sep + "b c"` would silently merge
36
+ * two runs into one.
37
+ */
38
+ const KEY_SEP = '\u0000';
39
+ function asStringArray(value) {
40
+ return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : [];
41
+ }
42
+ /**
43
+ * Records reaching here have been shape-checked, not type-checked: `validate()`
44
+ * in ingest.ts asserts a schema version, a run id and an attempts array, and
45
+ * nothing else. A record written by a different atest version can be missing
46
+ * any optional field entirely, and `undefined` where the type says `T | null`
47
+ * propagates into scores as a silent NaN rather than an error.
48
+ */
49
+ function normalizeAttempt(attempt, segment, runMeta) {
50
+ return {
51
+ runId: segment.runId,
52
+ testId: attempt.testId,
53
+ project: attempt.project,
54
+ retry: attempt.retry ?? 0,
55
+ title: attempt.title,
56
+ titlePath: asStringArray(attempt.titlePath),
57
+ file: attempt.file,
58
+ line: attempt.line ?? 0,
59
+ tags: asStringArray(attempt.tags),
60
+ outcome: attempt.outcome,
61
+ failureKind: (attempt.failureKind ?? null),
62
+ durationMs: attempt.durationMs ?? 0,
63
+ workerIndex: attempt.workerIndex ?? 0,
64
+ // An attempt executed during shard 1 of 3 belongs to shard 1 of 3.
65
+ // Falling back to the run's shard is what keeps the replacement key and
66
+ // the attempt's own record of itself in agreement.
67
+ shard: attempt.shard ?? segment.run.shard ?? null,
68
+ traceId: attempt.traceId ?? null,
69
+ evidenceId: (attempt.evidenceId ?? null),
70
+ coScheduled: asStringArray(attempt.coScheduled),
71
+ routes: asStringArray(attempt.routes),
72
+ startedAt: runMeta.startedAt,
73
+ commit: runMeta.commit,
74
+ branch: runMeta.branch,
75
+ ci: runMeta.ci,
76
+ workers: runMeta.workers,
77
+ };
78
+ }
79
+ /**
80
+ * In-memory history with SQLite's query semantics and none of its storage.
81
+ *
82
+ * Add segments in any order; the answers do not depend on it, except where
83
+ * SQLite's own answers would not either (last writer wins on mutable run
84
+ * metadata).
85
+ */
86
+ export class HistoryIndex {
87
+ /** Keyed `runId\x00shardKey` — the replacement unit. */
88
+ segments = new Map();
89
+ add(run) {
90
+ const shardKey = shardKeyOf(run.shard);
91
+ this.segments.set(`${run.runId}${KEY_SEP}${shardKey}`, { runId: run.runId, shardKey, run });
92
+ }
93
+ /** Drop everything, so a driver can reload from its backing store. */
94
+ clear() {
95
+ this.segments.clear();
96
+ }
97
+ has(runId, shardKey) {
98
+ return this.segments.has(`${runId}${KEY_SEP}${shardKey}`);
99
+ }
100
+ /**
101
+ * One metadata row per run id, merged across its shards.
102
+ *
103
+ * `startedAt` takes the EARLIEST value: a run spanning six shards started
104
+ * when its first shard did, and letting the last shard ingested stamp the
105
+ * time would move the run inside the recency window depending on artifact
106
+ * download order. Everything else is last-writer-wins, matching the UPSERT.
107
+ */
108
+ runMetadata() {
109
+ const meta = new Map();
110
+ for (const segment of this.segments.values()) {
111
+ const run = segment.run;
112
+ const existing = meta.get(run.runId);
113
+ if (existing === undefined) {
114
+ meta.set(run.runId, {
115
+ startedAt: run.startedAt,
116
+ commit: run.commit ?? null,
117
+ branch: run.branch ?? null,
118
+ ci: run.ci === true,
119
+ workers: run.workers ?? 0,
120
+ });
121
+ continue;
122
+ }
123
+ if (run.startedAt < existing.startedAt)
124
+ existing.startedAt = run.startedAt;
125
+ existing.commit = run.commit ?? null;
126
+ existing.branch = run.branch ?? null;
127
+ existing.ci = run.ci === true;
128
+ existing.workers = run.workers ?? 0;
129
+ }
130
+ return meta;
131
+ }
132
+ attempts(query = {}) {
133
+ const meta = this.runMetadata();
134
+ const rows = [];
135
+ for (const segment of this.segments.values()) {
136
+ const runMeta = meta.get(segment.runId);
137
+ if (runMeta === undefined)
138
+ continue;
139
+ if (query.since !== undefined && runMeta.startedAt < query.since)
140
+ continue;
141
+ for (const attempt of segment.run.attempts ?? []) {
142
+ if (query.testId !== undefined && attempt.testId !== query.testId)
143
+ continue;
144
+ if (query.project !== undefined && attempt.project !== query.project)
145
+ continue;
146
+ rows.push(normalizeAttempt(attempt, segment, runMeta));
147
+ }
148
+ }
149
+ // `ORDER BY r.started_at DESC, a.retry ASC`, then LIMIT — the limit is a
150
+ // window over the newest attempts, so applying it before the sort would
151
+ // silently score an arbitrary subset. The extra tiebreakers are not in the
152
+ // SQL: SQLite leaves ties unordered, and an unstable order here would make
153
+ // a `--limit`ed score depend on Map iteration order.
154
+ rows.sort((a, b) => b.startedAt.localeCompare(a.startedAt) ||
155
+ a.retry - b.retry ||
156
+ a.runId.localeCompare(b.runId) ||
157
+ a.testId.localeCompare(b.testId) ||
158
+ a.project.localeCompare(b.project));
159
+ return query.limit === undefined ? rows : rows.slice(0, Math.max(0, query.limit));
160
+ }
161
+ testKeys() {
162
+ // Newest first, so the title and file a key reports are the ones the test
163
+ // has NOW. SQLite's bare-column GROUP BY picks an arbitrary row, which
164
+ // after a rename reports whichever the query planner happened to visit.
165
+ const keys = new Map();
166
+ for (const attempt of this.attempts()) {
167
+ const key = `${attempt.testId}${KEY_SEP}${attempt.project}`;
168
+ if (keys.has(key))
169
+ continue;
170
+ keys.set(key, {
171
+ testId: attempt.testId,
172
+ project: attempt.project,
173
+ title: attempt.title,
174
+ file: attempt.file,
175
+ });
176
+ }
177
+ return [...keys.values()].sort((a, b) => a.file.localeCompare(b.file) || a.title.localeCompare(b.title));
178
+ }
179
+ runCount() {
180
+ const ids = new Set();
181
+ for (const segment of this.segments.values())
182
+ ids.add(segment.runId);
183
+ return ids.size;
184
+ }
185
+ /**
186
+ * Remove runs that started before the cutoff. Returns the number of RUNS
187
+ * dropped, not segments — a six-shard run pruned is one run, which is what
188
+ * `runCount()` counted it as.
189
+ */
190
+ prune(olderThanIso) {
191
+ const meta = this.runMetadata();
192
+ const doomed = new Set();
193
+ for (const [runId, runMeta] of meta) {
194
+ if (runMeta.startedAt < olderThanIso)
195
+ doomed.add(runId);
196
+ }
197
+ const removedKeys = [];
198
+ for (const [key, segment] of this.segments) {
199
+ if (!doomed.has(segment.runId))
200
+ continue;
201
+ removedKeys.push(key);
202
+ this.segments.delete(key);
203
+ }
204
+ return { removedRuns: doomed.size, removedKeys };
205
+ }
206
+ }
207
+ /**
208
+ * `HistoryStore` over a `HistoryIndex` and nothing else.
209
+ *
210
+ * This is what `--db :memory:` should always have meant. Reaching for
211
+ * `new SqliteHistoryStore(':memory:')` to get a throwaway store — as the MCP
212
+ * server and `aplaytest impact` both did — pulls in `node:sqlite`, a schema and a
213
+ * transaction to hold data that is discarded when the process exits.
214
+ */
215
+ export class MemoryHistoryStore {
216
+ index = new HistoryIndex();
217
+ async ingest(run) {
218
+ this.index.add(run);
219
+ }
220
+ async attempts(query = {}) {
221
+ return this.index.attempts(query);
222
+ }
223
+ async testKeys() {
224
+ return this.index.testKeys();
225
+ }
226
+ async runCount() {
227
+ return this.index.runCount();
228
+ }
229
+ async prune(olderThanIso) {
230
+ return this.index.prune(olderThanIso).removedRuns;
231
+ }
232
+ async close() {
233
+ this.index.clear();
234
+ }
235
+ }
236
+ //# sourceMappingURL=memory-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-index.js","sourceRoot":"","sources":["../../src/history/memory-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Map a merged Playwright JSON report onto history records.
3
+ *
4
+ * This is a runner adapter for suites that cannot install the atest reporter —
5
+ * most importantly, a suite whose test job is owned by a shared CI template
6
+ * that only uploads `blob-report/` and the merged JSON. Playwright's own
7
+ * `--reporter=json` output is then the ONLY channel through which run data
8
+ * escapes, and it carries more than it first appears to.
9
+ *
10
+ * Pure and synchronous, over structural interfaces: the whole mapping is
11
+ * unit-testable against a plain object, with no store and no filesystem.
12
+ * `ingestPlaywrightJson` does the I/O and hands the result in.
13
+ *
14
+ * ── What this path gives up, and what it does not ───────────────────────────
15
+ * No evidence bundle: no ARIA snapshot, no locator candidates, no network
16
+ * ledger, no console capture. Healing therefore still requires the reporter.
17
+ *
18
+ * What it does NOT give up is identity. Every field below is read from the
19
+ * report rather than invented, because a history row that cannot be joined to
20
+ * the rows around it is worse than no row at all — it looks like data and
21
+ * scores like noise.
22
+ */
23
+ import { type RunRecord } from './types.js';
24
+ export interface JsonError {
25
+ readonly message?: string;
26
+ readonly stack?: string;
27
+ }
28
+ export interface JsonResult {
29
+ readonly status?: string;
30
+ readonly duration?: number;
31
+ readonly retry?: number;
32
+ readonly workerIndex?: number;
33
+ /** ISO timestamp of the attempt. The reason history can be back-filled. */
34
+ readonly startTime?: string;
35
+ readonly errors?: readonly JsonError[];
36
+ }
37
+ export interface JsonTest {
38
+ readonly projectName?: string;
39
+ readonly timeout?: number;
40
+ readonly results?: readonly JsonResult[];
41
+ }
42
+ export interface JsonSpec {
43
+ /** Playwright's stable test id. Present since 1.38; see `attemptTestId`. */
44
+ readonly id?: string;
45
+ readonly title?: string;
46
+ readonly file?: string;
47
+ readonly line?: number;
48
+ readonly tags?: readonly string[];
49
+ readonly tests?: readonly JsonTest[];
50
+ }
51
+ export interface JsonSuite {
52
+ readonly title?: string;
53
+ readonly file?: string;
54
+ readonly specs?: readonly JsonSpec[];
55
+ readonly suites?: readonly JsonSuite[];
56
+ }
57
+ export interface JsonReport {
58
+ readonly config?: {
59
+ readonly version?: string;
60
+ readonly workers?: number;
61
+ readonly shard?: {
62
+ readonly current?: number;
63
+ readonly total?: number;
64
+ } | null;
65
+ readonly metadata?: {
66
+ readonly actualWorkers?: number;
67
+ readonly ci?: {
68
+ readonly commitHash?: string;
69
+ readonly branch?: string;
70
+ readonly buildHref?: string;
71
+ };
72
+ };
73
+ };
74
+ readonly suites?: readonly JsonSuite[];
75
+ }
76
+ /**
77
+ * Run-level facts the report cannot supply, from the environment doing the
78
+ * ingest. The report always wins where it has an answer — it describes the run
79
+ * being ingested, whereas the environment describes whoever is ingesting it,
80
+ * and those differ whenever history is back-filled from an archived artifact.
81
+ */
82
+ export interface RunIdentity {
83
+ readonly commit: string | null;
84
+ readonly branch: string | null;
85
+ readonly appEnv: string | null;
86
+ readonly ci: boolean;
87
+ }
88
+ export interface MappedReport {
89
+ readonly run: RunRecord;
90
+ /**
91
+ * Attempts that collided on the store's primary key and had to be collapsed.
92
+ * Reported rather than swallowed — see `collapseDuplicates`.
93
+ */
94
+ readonly collapsed: readonly string[];
95
+ }
96
+ export interface MapReportOptions {
97
+ readonly identity: RunIdentity;
98
+ /** Injectable clock, for the no-timestamps fallback. Tests pin it. */
99
+ readonly now?: () => number;
100
+ }
101
+ export declare function mapPlaywrightReport(report: JsonReport, options: MapReportOptions): MappedReport;
102
+ //# sourceMappingURL=playwright-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-report.d.ts","sourceRoot":"","sources":["../../src/history/playwright-report.ts"],"names":[],"mappings":"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"}