@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,277 @@
1
+ "use strict";
2
+ /**
3
+ * Map a merged Playwright JSON report onto history records.
4
+ *
5
+ * This is a runner adapter for suites that cannot install the atest reporter —
6
+ * most importantly, a suite whose test job is owned by a shared CI template
7
+ * that only uploads `blob-report/` and the merged JSON. Playwright's own
8
+ * `--reporter=json` output is then the ONLY channel through which run data
9
+ * escapes, and it carries more than it first appears to.
10
+ *
11
+ * Pure and synchronous, over structural interfaces: the whole mapping is
12
+ * unit-testable against a plain object, with no store and no filesystem.
13
+ * `ingestPlaywrightJson` does the I/O and hands the result in.
14
+ *
15
+ * ── What this path gives up, and what it does not ───────────────────────────
16
+ * No evidence bundle: no ARIA snapshot, no locator candidates, no network
17
+ * ledger, no console capture. Healing therefore still requires the reporter.
18
+ *
19
+ * What it does NOT give up is identity. Every field below is read from the
20
+ * report rather than invented, because a history row that cannot be joined to
21
+ * the rows around it is worse than no row at all — it looks like data and
22
+ * scores like noise.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.mapPlaywrightReport = mapPlaywrightReport;
26
+ const node_crypto_1 = require("node:crypto");
27
+ const classify_js_1 = require("../taxonomy/classify.js");
28
+ const parse_error_js_1 = require("../taxonomy/parse-error.js");
29
+ const version_js_1 = require("../version.js");
30
+ const types_js_1 = require("./types.js");
31
+ const RUN_ID_PREFIX = 'pwjson_';
32
+ const RUN_ID_LENGTH = 12;
33
+ function toOutcome(status) {
34
+ if (status === 'passed')
35
+ return 'passed';
36
+ if (status === 'timedOut')
37
+ return 'timedOut';
38
+ if (status === 'skipped')
39
+ return 'skipped';
40
+ if (status === 'interrupted')
41
+ return 'interrupted';
42
+ return 'failed';
43
+ }
44
+ /**
45
+ * Playwright's stable test id, which the JSON reporter emits as `spec.id` and
46
+ * which is exactly what `AttemptRecord.testId` is documented to hold. It is
47
+ * unique per (test, project) and survives a title rename.
48
+ *
49
+ * Taking it from the report rather than composing one is the single most
50
+ * important line in this file. A composed `file::title` id is a DIFFERENT ID
51
+ * SPACE from the reporter's, so history gathered on this path would never join
52
+ * to history gathered after the reporter is installed: every test would look
53
+ * new, every score would reset to "insufficient data", and the old rows would
54
+ * linger as phantom tests forever. The adoption sequence that starts on JSON
55
+ * and later adds the reporter is the normal one, so that migration cliff has to
56
+ * not exist.
57
+ *
58
+ * The fallback exists only for a report old enough to lack `spec.id`. It
59
+ * includes the title path, so two same-titled tests in different `describe`
60
+ * blocks stay distinct — but it is still a separate id space, and the caller is
61
+ * told about it.
62
+ */
63
+ function attemptTestId(spec, file, titlePath) {
64
+ if (typeof spec.id === 'string' && spec.id !== '')
65
+ return spec.id;
66
+ return [file, ...titlePath, spec.title ?? ''].join('::');
67
+ }
68
+ /**
69
+ * Classify a failing attempt from its error text.
70
+ *
71
+ * The classifier reads `message + stack` for nearly every rule, so this path
72
+ * recovers most of the taxonomy without an evidence bundle — including `infra`,
73
+ * which matters more than it looks: doc 06 requires infra attempts to be
74
+ * excluded from flake statistics, and a hard-coded `'unknown'` would have let
75
+ * every browser crash count as evidence of flakiness.
76
+ *
77
+ * One honest gap. `contract.app-error` needs captured console output, which
78
+ * only the reporter has. Without it, an application exception whose sole trace
79
+ * was a console message classifies as whatever assertion failed downstream of
80
+ * it — typically `assertion_visibility`. That over-weights a real bug as flake
81
+ * rather than hiding it, which is the safer direction to be wrong in, but it is
82
+ * a reason to prefer the reporter once evidence egress exists.
83
+ */
84
+ function classifyAttempt(result, timeoutMs) {
85
+ const { message, stack } = (0, parse_error_js_1.joinErrors)(result.errors);
86
+ if (message === '' && stack === '')
87
+ return 'unknown';
88
+ const parsed = (0, parse_error_js_1.parsePlaywrightError)(message, stack);
89
+ const duration = result.duration ?? 0;
90
+ return (0, classify_js_1.classify)({
91
+ message,
92
+ stack,
93
+ matcher: parsed.matcher,
94
+ timedOut: parsed.timedOut || result.status === 'timedOut',
95
+ // The reporter's sidecars. Absent here by construction, not by accident.
96
+ consoleErrors: [],
97
+ failedRequests: [],
98
+ budgetUsedRatio: timeoutMs > 0 ? duration / timeoutMs : 0,
99
+ }).kind;
100
+ }
101
+ function collectAttempts(suites, file, titlePath) {
102
+ const out = [];
103
+ for (const suite of suites ?? []) {
104
+ const suiteFile = suite.file ?? file;
105
+ const path = suite.title !== undefined && suite.title !== '' ? [...titlePath, suite.title] : titlePath;
106
+ for (const spec of suite.specs ?? []) {
107
+ const specFile = spec.file ?? suiteFile;
108
+ for (const test of spec.tests ?? []) {
109
+ for (const result of test.results ?? []) {
110
+ const outcome = toOutcome(result.status);
111
+ const failing = outcome === 'failed' || outcome === 'timedOut';
112
+ out.push({
113
+ testId: attemptTestId(spec, specFile, path),
114
+ title: spec.title ?? '',
115
+ titlePath: path,
116
+ file: specFile,
117
+ line: spec.line ?? 0,
118
+ project: test.projectName ?? 'default',
119
+ tags: spec.tags ?? [],
120
+ retry: result.retry ?? 0,
121
+ outcome,
122
+ failureKind: failing ? classifyAttempt(result, test.timeout ?? 0) : null,
123
+ durationMs: result.duration ?? 0,
124
+ workerIndex: result.workerIndex ?? 0,
125
+ // A merged report has already folded its shards together and does
126
+ // not record which one an attempt came from.
127
+ shard: null,
128
+ traceId: null,
129
+ evidenceId: null,
130
+ coScheduled: [],
131
+ routes: [],
132
+ });
133
+ }
134
+ }
135
+ }
136
+ out.push(...collectAttempts(suite.suites, suiteFile, path));
137
+ }
138
+ return out;
139
+ }
140
+ /** Ordered so a duplicate can never mask the worse outcome. */
141
+ const SEVERITY = {
142
+ skipped: 0,
143
+ passed: 1,
144
+ interrupted: 2,
145
+ timedOut: 3,
146
+ failed: 4,
147
+ };
148
+ /**
149
+ * Collapse attempts that share the store's primary key
150
+ * `(run_id, test_id, project, retry)`.
151
+ *
152
+ * With a real `spec.id` this should never fire — it did before, only because
153
+ * the composed id mapped two genuinely distinct setup tests onto one key. It
154
+ * stays as a safety net because the failure mode it guards is severe out of all
155
+ * proportion to its cause: `store.ingest` is a single transaction, so ONE
156
+ * duplicate row used to roll back the entire run. On a real 8-shard acceptance
157
+ * report that cost all 120 attempts, and the only symptom was a single
158
+ * "could not be stored" warning — history silently never accumulated while
159
+ * every CI job reported success.
160
+ *
161
+ * Collisions are returned, not swallowed. A silent dedupe here would be the
162
+ * same class of bug one level down: quietly discarding evidence and looking
163
+ * healthy while doing it.
164
+ */
165
+ function collapseDuplicates(attempts) {
166
+ const byKey = new Map();
167
+ const collapsed = [];
168
+ for (const attempt of attempts) {
169
+ const key = `${attempt.testId}\x00${attempt.project}\x00${attempt.retry}`;
170
+ const existing = byKey.get(key);
171
+ if (existing === undefined) {
172
+ byKey.set(key, attempt);
173
+ continue;
174
+ }
175
+ // Keep the more severe outcome. A duplicate that passed must never mask
176
+ // one that failed — that is precisely the instability scoring exists for.
177
+ const keep = SEVERITY[attempt.outcome] > SEVERITY[existing.outcome] ? attempt : existing;
178
+ byKey.set(key, keep);
179
+ collapsed.push(`${attempt.title} [${attempt.project}] retry ${attempt.retry}`);
180
+ }
181
+ return { kept: [...byKey.values()], collapsed };
182
+ }
183
+ /**
184
+ * A run id derived from the report, never from the clock.
185
+ *
186
+ * `store.ingest` promises idempotency — "CI re-runs, shard merges, and repeated
187
+ * artifact ingestion all replay the same run id" — and enforces it by deleting
188
+ * a run's attempts before reinserting them. A `Date.now()` id opts out of that
189
+ * guarantee silently: re-ingesting one artifact twice (a re-run of the analyze
190
+ * job, a replay of an archived report) writes both copies and double-counts
191
+ * every attempt, which corrupts every score derived from them.
192
+ *
193
+ * The CI build URL is the natural key where the report carries one. Otherwise
194
+ * digest the attempt tuples, which is stable for a given report and distinct
195
+ * across runs because attempt start times are millisecond-precision.
196
+ */
197
+ function deriveRunId(report, attempts) {
198
+ const buildHref = report.config?.metadata?.ci?.buildHref;
199
+ const seed = buildHref !== undefined && buildHref !== ''
200
+ ? buildHref
201
+ : attempts
202
+ .map(a => `${a.testId} ${a.project} ${a.retry} ${a.durationMs}`)
203
+ .sort()
204
+ .join('\n');
205
+ const digest = (0, node_crypto_1.createHash)('sha256').update(seed).digest('hex');
206
+ return `${RUN_ID_PREFIX}${digest.slice(0, RUN_ID_LENGTH)}`;
207
+ }
208
+ /**
209
+ * The wall-clock window the run actually occupied.
210
+ *
211
+ * Flake scoring weights attempts by recency (`halfLifeDays`), so the timestamp
212
+ * has to be when the test RAN, not when somebody ingested the file. Using
213
+ * ingest time would make a replayed six-month-old archive weigh as heavily as
214
+ * this morning's run — and back-filling history from archived artifacts is one
215
+ * of the reasons this adapter exists.
216
+ */
217
+ function runWindow(report, now) {
218
+ const spans = [];
219
+ const walk = (suites) => {
220
+ for (const suite of suites ?? []) {
221
+ for (const spec of suite.specs ?? []) {
222
+ for (const test of spec.tests ?? []) {
223
+ for (const result of test.results ?? []) {
224
+ if (result.startTime === undefined)
225
+ continue;
226
+ const start = Date.parse(result.startTime);
227
+ if (Number.isNaN(start))
228
+ continue;
229
+ spans.push({ start, end: start + (result.duration ?? 0) });
230
+ }
231
+ }
232
+ }
233
+ walk(suite.suites);
234
+ }
235
+ };
236
+ walk(report.suites);
237
+ if (spans.length === 0) {
238
+ const fallback = new Date(now()).toISOString();
239
+ return { startedAt: fallback, finishedAt: fallback };
240
+ }
241
+ return {
242
+ startedAt: new Date(Math.min(...spans.map(s => s.start))).toISOString(),
243
+ finishedAt: new Date(Math.max(...spans.map(s => s.end))).toISOString(),
244
+ };
245
+ }
246
+ function mapPlaywrightReport(report, options) {
247
+ const { identity } = options;
248
+ const now = options.now ?? Date.now;
249
+ const { kept, collapsed } = collapseDuplicates(collectAttempts(report.suites, '', []));
250
+ const { startedAt, finishedAt } = runWindow(report, now);
251
+ const ci = report.config?.metadata?.ci;
252
+ const shard = report.config?.shard;
253
+ const run = {
254
+ schemaVersion: types_js_1.RUN_SCHEMA_VERSION,
255
+ runId: deriveRunId(report, kept),
256
+ startedAt,
257
+ finishedAt,
258
+ // Report first, environment second. Same-commit variance is a documented
259
+ // flake signal and it needs the commit the tests actually ran against, not
260
+ // whatever is checked out in the job doing the ingest.
261
+ commit: ci?.commitHash ?? identity.commit,
262
+ branch: ci?.branch ?? identity.branch,
263
+ appEnv: identity.appEnv,
264
+ ci: ci !== undefined || identity.ci,
265
+ // `actualWorkers` is what the run really used; `workers` is what was asked
266
+ // for, and the two differ under sharding.
267
+ workers: report.config?.metadata?.actualWorkers ?? report.config?.workers ?? 0,
268
+ shard: shard !== null && shard !== undefined && shard.current !== undefined && shard.total !== undefined
269
+ ? { current: shard.current, total: shard.total }
270
+ : null,
271
+ atestVersion: version_js_1.ATEST_VERSION,
272
+ playwrightVersion: report.config?.version ?? null,
273
+ attempts: kept,
274
+ };
275
+ return { run, collapsed };
276
+ }
277
+ //# sourceMappingURL=playwright-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-report.js","sourceRoot":"","sources":["../../src/history/playwright-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ /**
3
+ * Test history — the substrate the flaky engine reasons over.
4
+ *
5
+ * Backed by `node:sqlite`, which ships with Node. That is a deliberate choice
6
+ * over `better-sqlite3`: a native module would need a compiler on every
7
+ * developer machine and every CI image, and the first time it failed to build
8
+ * people would disable the tool rather than debug it. Zero native
9
+ * dependencies is what makes this adoptable.
10
+ *
11
+ * The interface is async so a Postgres driver can slot in unchanged at scale;
12
+ * the SQLite driver simply resolves immediately.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SqliteHistoryStore = void 0;
16
+ const node_sqlite_1 = require("node:sqlite");
17
+ const node_fs_1 = require("node:fs");
18
+ const node_path_1 = require("node:path");
19
+ const SCHEMA = `
20
+ CREATE TABLE IF NOT EXISTS runs (
21
+ run_id TEXT PRIMARY KEY,
22
+ started_at TEXT NOT NULL,
23
+ finished_at TEXT,
24
+ commit_sha TEXT,
25
+ branch TEXT,
26
+ app_env TEXT,
27
+ ci INTEGER NOT NULL,
28
+ workers INTEGER NOT NULL,
29
+ shard_current INTEGER,
30
+ shard_total INTEGER,
31
+ atest_version TEXT NOT NULL,
32
+ playwright_version TEXT
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS attempts (
36
+ run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
37
+ test_id TEXT NOT NULL,
38
+ project TEXT NOT NULL,
39
+ retry INTEGER NOT NULL,
40
+ title TEXT NOT NULL,
41
+ title_path TEXT NOT NULL,
42
+ file TEXT NOT NULL,
43
+ line INTEGER NOT NULL,
44
+ tags TEXT NOT NULL,
45
+ outcome TEXT NOT NULL,
46
+ failure_kind TEXT,
47
+ duration_ms INTEGER NOT NULL,
48
+ worker_index INTEGER NOT NULL,
49
+ shard_current INTEGER,
50
+ shard_total INTEGER,
51
+ trace_id TEXT,
52
+ evidence_id TEXT,
53
+ co_scheduled TEXT NOT NULL,
54
+ routes TEXT NOT NULL DEFAULT '[]',
55
+ PRIMARY KEY (run_id, test_id, project, retry)
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS idx_attempts_test ON attempts(test_id, project);
59
+ CREATE INDEX IF NOT EXISTS idx_attempts_outcome ON attempts(outcome, failure_kind);
60
+ CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at);
61
+ `;
62
+ function parseJsonArray(raw) {
63
+ try {
64
+ const parsed = JSON.parse(raw);
65
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === 'string') : [];
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ function toHistoricalAttempt(row) {
72
+ return {
73
+ runId: row.run_id,
74
+ testId: row.test_id,
75
+ project: row.project,
76
+ retry: row.retry,
77
+ title: row.title,
78
+ titlePath: parseJsonArray(row.title_path),
79
+ file: row.file,
80
+ line: row.line,
81
+ tags: parseJsonArray(row.tags),
82
+ outcome: row.outcome,
83
+ failureKind: row.failure_kind,
84
+ durationMs: row.duration_ms,
85
+ workerIndex: row.worker_index,
86
+ shard: row.shard_current === null || row.shard_total === null
87
+ ? null
88
+ : { current: row.shard_current, total: row.shard_total },
89
+ traceId: row.trace_id,
90
+ evidenceId: row.evidence_id,
91
+ coScheduled: parseJsonArray(row.co_scheduled),
92
+ routes: parseJsonArray(row.routes ?? '[]'),
93
+ startedAt: row.started_at,
94
+ commit: row.commit_sha,
95
+ branch: row.branch,
96
+ ci: row.ci === 1,
97
+ workers: row.workers,
98
+ };
99
+ }
100
+ /**
101
+ * `undefined` is not bindable by node:sqlite — only `null` is.
102
+ *
103
+ * The distinction never shows up in a hand-written fixture where every field is
104
+ * present, and it is invisible in the type system because `string | null` and a
105
+ * MISSING key both satisfy a `Partial<RunRecord>` shape check. What it produces
106
+ * at runtime is `Provided value cannot be bound to SQLite parameter 6`, which
107
+ * names neither the field nor the file — and because the insert is inside a
108
+ * transaction, the whole batch rolls back. One run record written by a
109
+ * different atest version therefore silently costs you every other record in
110
+ * the directory.
111
+ */
112
+ function nullable(value) {
113
+ return value ?? null;
114
+ }
115
+ class SqliteHistoryStore {
116
+ db;
117
+ constructor(url) {
118
+ if (url !== ':memory:')
119
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(url), { recursive: true });
120
+ this.db = new node_sqlite_1.DatabaseSync(url);
121
+ this.db.exec('PRAGMA foreign_keys = ON');
122
+ this.db.exec(SCHEMA);
123
+ }
124
+ /**
125
+ * Idempotent by construction. CI re-runs, shard merges, and repeated
126
+ * artifact ingestion all replay the same run id — double-counting an
127
+ * attempt would silently corrupt every score derived from it.
128
+ */
129
+ async ingest(run) {
130
+ const insertRun = this.db.prepare(`
131
+ -- UPSERT, never INSERT OR REPLACE.
132
+ --
133
+ -- SQLite implements OR REPLACE as DELETE-then-INSERT, so replacing a run
134
+ -- row fires the attempts table's ON DELETE CASCADE. Under sharding every
135
+ -- shard shares one run id, so ingesting shard 2 deleted shard 1's
136
+ -- attempts before writing its own — the shard-scoped delete below cannot
137
+ -- help, because the rows are already gone by the time it runs. Measured:
138
+ -- three shard files carrying four attempts ingested as zero.
139
+ --
140
+ -- DO UPDATE mutates the row in place. No delete, no cascade.
141
+ INSERT INTO runs
142
+ (run_id, started_at, finished_at, commit_sha, branch, app_env, ci, workers,
143
+ shard_current, shard_total, atest_version, playwright_version)
144
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
145
+ ON CONFLICT(run_id) DO UPDATE SET
146
+ finished_at = excluded.finished_at,
147
+ commit_sha = excluded.commit_sha,
148
+ branch = excluded.branch,
149
+ app_env = excluded.app_env,
150
+ ci = excluded.ci,
151
+ workers = excluded.workers,
152
+ atest_version = excluded.atest_version,
153
+ playwright_version = excluded.playwright_version
154
+ -- started_at keeps the EARLIEST shard's value, and shard_* are left
155
+ -- alone: a run spanning shards has no single shard number, and letting
156
+ -- the last writer stamp one would misreport the run.
157
+ `);
158
+ // Scoped to THIS shard, not the whole run.
159
+ //
160
+ // `DELETE FROM attempts WHERE run_id = ?` is correct for an unsharded run
161
+ // and catastrophic for a sharded one: every shard shares the run id, so
162
+ // ingesting six shard files left only the sixth shard's attempts — one
163
+ // sixth of the data, with no error anywhere. `IS` rather than `=` because
164
+ // an unsharded record carries NULL, and NULL = NULL is never true.
165
+ const deleteAttempts = this.db.prepare('DELETE FROM attempts WHERE run_id = ? AND shard_current IS ? AND shard_total IS ?');
166
+ const insertAttempt = this.db.prepare(`
167
+ INSERT INTO attempts
168
+ (run_id, test_id, project, retry, title, title_path, file, line, tags, outcome,
169
+ failure_kind, duration_ms, worker_index, shard_current, shard_total,
170
+ trace_id, evidence_id, co_scheduled, routes)
171
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
172
+ `);
173
+ this.db.exec('BEGIN');
174
+ try {
175
+ insertRun.run(run.runId, run.startedAt, nullable(run.finishedAt), nullable(run.commit), nullable(run.branch), nullable(run.appEnv), run.ci ? 1 : 0, run.workers ?? 0, run.shard?.current ?? null, run.shard?.total ?? null, run.atestVersion ?? '', nullable(run.playwrightVersion));
176
+ deleteAttempts.run(run.runId, run.shard?.current ?? null, run.shard?.total ?? null);
177
+ for (const attempt of run.attempts) {
178
+ insertAttempt.run(run.runId, attempt.testId, attempt.project, attempt.retry, attempt.title, JSON.stringify(attempt.titlePath ?? []), attempt.file, attempt.line, JSON.stringify(attempt.tags ?? []), attempt.outcome, nullable(attempt.failureKind), attempt.durationMs ?? 0, attempt.workerIndex ?? 0,
179
+ // Fall back to the RUN's shard. The scoped delete above matches on
180
+ // these columns, so an attempt that omits its shard while the run
181
+ // declares one would never be cleaned up — a re-run of that shard
182
+ // would leave the previous attempt rows behind and inflate every
183
+ // statistic derived from them. An attempt executed during shard 1
184
+ // of 3 belongs to shard 1 of 3; recording anything else is wrong.
185
+ attempt.shard?.current ?? run.shard?.current ?? null, attempt.shard?.total ?? run.shard?.total ?? null, nullable(attempt.traceId), nullable(attempt.evidenceId), JSON.stringify(attempt.coScheduled ?? []), JSON.stringify(attempt.routes ?? []));
186
+ }
187
+ this.db.exec('COMMIT');
188
+ }
189
+ catch (error) {
190
+ this.db.exec('ROLLBACK');
191
+ throw error;
192
+ }
193
+ }
194
+ async attempts(query = {}) {
195
+ const where = [];
196
+ const params = [];
197
+ if (query.testId !== undefined) {
198
+ where.push('a.test_id = ?');
199
+ params.push(query.testId);
200
+ }
201
+ if (query.project !== undefined) {
202
+ where.push('a.project = ?');
203
+ params.push(query.project);
204
+ }
205
+ if (query.since !== undefined) {
206
+ where.push('r.started_at >= ?');
207
+ params.push(query.since);
208
+ }
209
+ const clause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
210
+ const limit = query.limit === undefined ? '' : `LIMIT ${Number(query.limit)}`;
211
+ const rows = this.db
212
+ .prepare(`SELECT a.*, r.started_at, r.commit_sha, r.branch, r.ci, r.workers
213
+ FROM attempts a JOIN runs r ON r.run_id = a.run_id
214
+ ${clause}
215
+ ORDER BY r.started_at DESC, a.retry ASC
216
+ ${limit}`)
217
+ .all(...params);
218
+ return rows.map(toHistoricalAttempt);
219
+ }
220
+ async testKeys() {
221
+ const rows = this.db
222
+ .prepare(`SELECT test_id, project, title, file FROM attempts
223
+ GROUP BY test_id, project
224
+ ORDER BY file, title`)
225
+ .all();
226
+ return rows.map(r => ({
227
+ testId: r.test_id,
228
+ project: r.project,
229
+ title: r.title,
230
+ file: r.file,
231
+ }));
232
+ }
233
+ async runCount() {
234
+ const row = this.db.prepare('SELECT COUNT(*) AS n FROM runs').get();
235
+ return row.n;
236
+ }
237
+ async prune(olderThanIso) {
238
+ const before = await this.runCount();
239
+ this.db.prepare('DELETE FROM runs WHERE started_at < ?').run(olderThanIso);
240
+ return before - (await this.runCount());
241
+ }
242
+ async close() {
243
+ this.db.close();
244
+ }
245
+ }
246
+ exports.SqliteHistoryStore = SqliteHistoryStore;
247
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/history/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * Run and attempt records — the unit of test history.
4
+ *
5
+ * The reporter writes these as JSON; the history store ingests them
6
+ * into SQLite. Keeping the reporter's output a plain file rather than a
7
+ * database write matters: the reporter runs INSIDE the test process, where a
8
+ * native SQLite binding, a migration, or a lock contention bug would become a
9
+ * test failure. The run path stays dependency-light on purpose.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.RUN_SCHEMA_VERSION = void 0;
13
+ exports.isFailure = isFailure;
14
+ exports.isConclusive = isConclusive;
15
+ exports.RUN_SCHEMA_VERSION = 1;
16
+ function isFailure(outcome) {
17
+ return outcome === 'failed' || outcome === 'timedOut';
18
+ }
19
+ /** Skipped and interrupted attempts are not evidence about a test's behaviour. */
20
+ function isConclusive(outcome) {
21
+ return outcome === 'passed' || outcome === 'failed' || outcome === 'timedOut';
22
+ }
23
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/history/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAyDH,8BAEC;AAGD,oCAEC;AATY,QAAA,kBAAkB,GAAG,CAAU,CAAC;AAE7C,SAAgB,SAAS,CAAC,OAAgB;IACxC,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,UAAU,CAAC;AACxD,CAAC;AAED,kFAAkF;AAClF,SAAgB,YAAY,CAAC,OAAgB;IAC3C,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,UAAU,CAAC;AAChF,CAAC"}