@midleman/playwright-reporter 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 (66) hide show
  1. package/README.md +373 -0
  2. package/dist/buffers.d.ts +13 -0
  3. package/dist/buffers.d.ts.map +1 -0
  4. package/dist/buffers.js +58 -0
  5. package/dist/buffers.js.map +1 -0
  6. package/dist/counts.d.ts +32 -0
  7. package/dist/counts.d.ts.map +1 -0
  8. package/dist/counts.js +66 -0
  9. package/dist/counts.js.map +1 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +28 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/payload.d.ts +33 -0
  15. package/dist/payload.d.ts.map +1 -0
  16. package/dist/payload.js +87 -0
  17. package/dist/payload.js.map +1 -0
  18. package/dist/payload.test.d.ts +2 -0
  19. package/dist/payload.test.d.ts.map +1 -0
  20. package/dist/payload.test.js +198 -0
  21. package/dist/payload.test.js.map +1 -0
  22. package/dist/reporter.d.ts +47 -0
  23. package/dist/reporter.d.ts.map +1 -0
  24. package/dist/reporter.js +470 -0
  25. package/dist/reporter.js.map +1 -0
  26. package/dist/reporter.test.d.ts +2 -0
  27. package/dist/reporter.test.d.ts.map +1 -0
  28. package/dist/reporter.test.js +216 -0
  29. package/dist/reporter.test.js.map +1 -0
  30. package/dist/schemas.d.ts +485 -0
  31. package/dist/schemas.d.ts.map +1 -0
  32. package/dist/schemas.js +128 -0
  33. package/dist/schemas.js.map +1 -0
  34. package/dist/schemas.test.d.ts +2 -0
  35. package/dist/schemas.test.d.ts.map +1 -0
  36. package/dist/schemas.test.js +313 -0
  37. package/dist/schemas.test.js.map +1 -0
  38. package/dist/storage/api.d.ts +13 -0
  39. package/dist/storage/api.d.ts.map +1 -0
  40. package/dist/storage/api.js +86 -0
  41. package/dist/storage/api.js.map +1 -0
  42. package/dist/storage/api.test.d.ts +2 -0
  43. package/dist/storage/api.test.d.ts.map +1 -0
  44. package/dist/storage/api.test.js +184 -0
  45. package/dist/storage/api.test.js.map +1 -0
  46. package/dist/storage/parquet.d.ts +10 -0
  47. package/dist/storage/parquet.d.ts.map +1 -0
  48. package/dist/storage/parquet.js +157 -0
  49. package/dist/storage/parquet.js.map +1 -0
  50. package/dist/storage/s3.d.ts +35 -0
  51. package/dist/storage/s3.d.ts.map +1 -0
  52. package/dist/storage/s3.js +129 -0
  53. package/dist/storage/s3.js.map +1 -0
  54. package/dist/types.d.ts +36 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +26 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/utils.d.ts +44 -0
  59. package/dist/utils.d.ts.map +1 -0
  60. package/dist/utils.js +195 -0
  61. package/dist/utils.js.map +1 -0
  62. package/dist/utils.test.d.ts +2 -0
  63. package/dist/utils.test.d.ts.map +1 -0
  64. package/dist/utils.test.js +150 -0
  65. package/dist/utils.test.js.map +1 -0
  66. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,373 @@
1
+ # @midleman/playwright-reporter
2
+
3
+ A Playwright reporter that writes test results directly to Parquet files on S3 or local filesystem.
4
+
5
+ ## Overview
6
+
7
+ This reporter collects Playwright test results and writes them as Parquet files to S3 or local filesystem for analysis with DuckDB.
8
+
9
+ ## Installation
10
+
11
+ ### Production (Published Package)
12
+
13
+ ```bash
14
+ npm install @midleman/playwright-reporter
15
+ ```
16
+
17
+ ### Local Development (Linked Package)
18
+
19
+ For iterating on the reporter locally, use npm link:
20
+
21
+ ```bash
22
+ # In e2e-test-insights repo - build and link
23
+ cd packages/playwright-reporter
24
+ npm install
25
+ npm run build
26
+ npm link
27
+
28
+ # In your test project (e.g., positron) - use the link
29
+ npm link @midleman/playwright-reporter
30
+ ```
31
+
32
+ > **Note:** After making changes to the reporter, run `npm run build` to update the linked package.
33
+
34
+ ## Configuration
35
+
36
+ ### Via Playwright Config
37
+
38
+ ```typescript
39
+ // playwright.config.ts
40
+ import { defineConfig } from '@playwright/test';
41
+
42
+ export default defineConfig({
43
+ reporter: [
44
+ ['html'],
45
+ [
46
+ '@midleman/playwright-reporter',
47
+ {
48
+ mode: 'dev', // 'dev', 'prod', or 'disabled'
49
+ verbose: true, // Enable debug logging
50
+ },
51
+ ],
52
+ ],
53
+ });
54
+ ```
55
+
56
+ > **Note:** If using from another repository, see [Using from Another Repository](#using-from-another-repository) for setup instructions.
57
+
58
+ ### Reporter Mode
59
+
60
+ The `mode` option (required) controls where results are sent:
61
+
62
+ | Mode | API Endpoint | Use Case | CI Required? |
63
+ | ---------- | ----------------------------------------------- | --------------------------- | ------------ |
64
+ | `dev` | `http://localhost:8000` | Local development | No |
65
+ | `prod` | `https://connect.posit.it/e2e-test-insights-api`| CI/Production | **Yes** |
66
+ | `disabled` | None | Skip reporting | No |
67
+
68
+ **Important:**
69
+ - `mode: 'prod'` only works in CI environments (GitHub Actions or `CI=true`). If used locally, it will log a warning and disable reporting.
70
+ - `mode: 'dev'` works anywhere and sends to your local API.
71
+ - The reporter sends data to the Plumber API, which then writes to storage (local or S3). Make sure your API is running with matching storage mode:
72
+ - `mode: 'dev'` → run API with `npm run api` (local) or `npm run api:s3` (S3)
73
+ - `mode: 'prod'` → uses deployed Posit Connect API (CI only)
74
+
75
+ ### Environment Variables
76
+
77
+ | Variable | Description | Default |
78
+ | --------------------- | ---------------------------------------- | ------------------------ |
79
+ | `E2E_REPORTER_MODE` | Reporter mode: `dev`, `prod`, `disabled` | (none - required) |
80
+ | `REPORTER_REPO_NAME` | Repository name override | From `GITHUB_REPOSITORY` |
81
+ | `E2E_REPORTER_VERBOSE`| Enable debug logging (`1` or `0`) | `0` |
82
+
83
+ ## Output Structure
84
+
85
+ ```
86
+ s3://{bucket}/{prefix}/
87
+ └── {repoName}/
88
+ └── {date}/ # YYYY-MM-DD partition
89
+ └── {runId}/
90
+ ├── run.parquet # Run metadata (started + completed)
91
+ ├── results.parquet # Consolidated test results (written on completion)
92
+ ├── results-001.parquet # Incremental flush 1 (for live updates)
93
+ ├── results-002.parquet # Incremental flush 2
94
+ └── results-XXX.parquet # etc.
95
+ ```
96
+
97
+ **File types:**
98
+ - `results.parquet` - Single file with all deduplicated test results (final state). Best for querying.
99
+ - `results-XXX.parquet` - Incremental files written during the run for live viewing. May contain duplicates from retries.
100
+
101
+ ## Parquet Schemas
102
+
103
+ ### results.parquet
104
+
105
+ | Column | Type | Description |
106
+ | ---------------- | ------ | ----------------------------------------- |
107
+ | `type` | UTF8 | Always `result.created` |
108
+ | `repoName` | UTF8 | Repository name |
109
+ | `runId` | UTF8 | Unique run identifier |
110
+ | `specPath` | UTF8 | Relative path to spec file |
111
+ | `project` | UTF8? | Playwright project name |
112
+ | `testName` | UTF8 | Test title |
113
+ | `testId` | UTF8? | Test identifier |
114
+ | `status` | UTF8 | `passed`, `failed`, `skipped`, or `flaky` |
115
+ | `durationMs` | INT64? | Test duration in milliseconds |
116
+ | `startedAt` | UTF8? | ISO timestamp when test started |
117
+ | `attempt` | INT32? | Retry attempt number (1-indexed) |
118
+ | `retries` | INT32? | Total retries configured |
119
+ | `failureMessage` | UTF8? | Error message if failed |
120
+ | `eventId` | UTF8 | Idempotent event ID for deduplication |
121
+ | `timestamp` | UTF8 | ISO timestamp when event was created |
122
+ | `browser` | UTF8? | Browser name (chromium, firefox, etc.) |
123
+ | `os` | UTF8? | Operating system (mac, win, linux) |
124
+ | `branch` | UTF8? | Git branch name |
125
+ | `sha` | UTF8? | Git commit SHA |
126
+
127
+ ### run.parquet
128
+
129
+ Contains both `run.started` and `run.completed` events:
130
+
131
+ | Column | Type | Description |
132
+ | --------------- | ------ | -------------------------------- |
133
+ | `type` | UTF8 | `run.started` or `run.completed` |
134
+ | `repoName` | UTF8 | Repository name |
135
+ | `runId` | UTF8 | Unique run identifier |
136
+ | `startedAt` | UTF8? | ISO timestamp (run.started) |
137
+ | `branch` | UTF8? | Git branch name |
138
+ | `sha` | UTF8? | Git commit SHA |
139
+ | `workflowName` | UTF8? | GitHub workflow name |
140
+ | `jobName` | UTF8? | GitHub job name |
141
+ | `runUrl` | UTF8? | GitHub Actions run URL |
142
+ | `completedAt` | UTF8? | ISO timestamp (run.completed) |
143
+ | `status` | UTF8? | Final status (run.completed) |
144
+ | `durationMs` | INT64? | Total duration (run.completed) |
145
+ | `countsPassed` | INT32? | Passed test count |
146
+ | `countsFailed` | INT32? | Failed test count |
147
+ | `countsSkipped` | INT32? | Skipped test count |
148
+ | `countsFlaky` | INT32? | Flaky test count |
149
+
150
+ ## Querying with DuckDB
151
+
152
+ ```sql
153
+ -- Query all results from a date range
154
+ SELECT * FROM read_parquet('s3://bucket/test-results/positron/2024-01-*/*/results.parquet');
155
+
156
+ -- Get failure summary by spec
157
+ SELECT
158
+ specPath,
159
+ COUNT(*) as total,
160
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failures
161
+ FROM read_parquet('s3://bucket/test-results/positron/**/results.parquet')
162
+ GROUP BY specPath
163
+ ORDER BY failures DESC;
164
+
165
+ -- Get run summaries
166
+ SELECT * FROM read_parquet('s3://bucket/test-results/positron/**/run.parquet')
167
+ WHERE type = 'run.completed';
168
+ ```
169
+
170
+ ## Usage in Other Repositories
171
+
172
+ ### Production Setup (CI)
173
+
174
+ For CI/production, install the published package:
175
+
176
+ ```bash
177
+ npm install @midleman/playwright-reporter
178
+ ```
179
+
180
+ Configure Playwright with `mode: 'prod'`:
181
+
182
+ ```typescript
183
+ // playwright.config.ts
184
+ export default defineConfig({
185
+ reporter: [
186
+ ['html'],
187
+ [
188
+ '@midleman/playwright-reporter',
189
+ {
190
+ repoName: 'positron',
191
+ mode: 'prod', // Sends to connect.posit.it API (CI only)
192
+ },
193
+ ],
194
+ ],
195
+ });
196
+ ```
197
+
198
+ > **Note:** `mode: 'prod'` requires a CI environment (`GITHUB_ACTIONS=true` or `CI=true`). This prevents local test runs from accidentally sending data to production.
199
+
200
+ ### Local Testing Setup
201
+
202
+ For testing the reporter locally before publishing:
203
+
204
+ **1. Link the package** (see [Installation](#local-development-linked-package))
205
+
206
+ **2. Start the local API** (in e2e-test-insights repo):
207
+
208
+ ```bash
209
+ npm run api # Local storage
210
+ # OR
211
+ npm run api:s3 # S3 storage (requires aws sso login)
212
+ ```
213
+
214
+ **3. Configure Playwright with `mode: 'dev'`:**
215
+
216
+ ```typescript
217
+ // playwright.config.ts
218
+ export default defineConfig({
219
+ reporter: [
220
+ ['html'],
221
+ [
222
+ '@midleman/playwright-reporter',
223
+ {
224
+ repoName: 'positron',
225
+ mode: 'dev', // Sends to localhost:8000
226
+ verbose: true, // See debug output
227
+ },
228
+ ],
229
+ ],
230
+ });
231
+ ```
232
+
233
+ **4. Run tests:**
234
+
235
+ ```bash
236
+ npx playwright test
237
+ ```
238
+
239
+ Results are sent to your local API.
240
+
241
+ ## Local Development
242
+
243
+ ```bash
244
+ # 1. Start the API (in e2e-test-insights repo)
245
+ npm run api
246
+
247
+ # 2. Run tests with reporter in dev mode
248
+ npx playwright test
249
+
250
+ # 3. View the dashboard
251
+ npm run app
252
+ ```
253
+
254
+ For verbose logging, set `verbose: true` in your playwright config.
255
+
256
+ ## Multi-Worker Support
257
+
258
+ This reporter is safe for parallel execution:
259
+
260
+ - Each worker writes to the same `{runId}` directory
261
+ - Idempotent `eventId` enables deduplication at query time
262
+ - DuckDB can query across all files with glob patterns
263
+
264
+ ## GitHub Actions Integration
265
+
266
+ In CI environments (GitHub Actions or `CI=true`), use `mode: 'prod'` to send results to the deployed API:
267
+
268
+ ```typescript
269
+ [
270
+ '@midleman/playwright-reporter',
271
+ {
272
+ mode: 'prod', // Sends to connect.posit.it/e2e-test-insights-api (CI only)
273
+ },
274
+ ],
275
+ ```
276
+
277
+ > **Note:** `mode: 'prod'` is designed for CI only. If you accidentally run with `mode: 'prod'` locally, the reporter will log a warning and disable itself to prevent local test runs from polluting production data.
278
+
279
+ The reporter automatically extracts metadata from GitHub Actions environment:
280
+
281
+ - Branch name from `GITHUB_REF` or `GITHUB_HEAD_REF`
282
+ - Commit SHA from `GITHUB_SHA`
283
+ - Workflow name from `GITHUB_WORKFLOW`
284
+ - Job name from `GITHUB_JOB`
285
+ - Run URL constructed from GitHub environment variables
286
+
287
+ ## Development
288
+
289
+ ```bash
290
+ # Install dependencies
291
+ npm install
292
+
293
+ # Build
294
+ npm run build
295
+
296
+ # Watch mode
297
+ npm run watch
298
+
299
+ # Clean
300
+ npm run clean
301
+ ```
302
+
303
+ ## Testing
304
+
305
+ Unit tests use [Vitest](https://vitest.dev/) and cover:
306
+
307
+ - **reporter.test.ts** - Reporter lifecycle (init, begin, end, test events)
308
+ - **payload.test.ts** - Event payload builders (result.created, run.started, run.completed)
309
+ - **schemas.test.ts** - Zod schema validation
310
+ - **api.test.ts** - API writer with retry logic
311
+ - **utils.test.ts** - Utility functions (ID generation, path handling, browser inference)
312
+
313
+ ```bash
314
+ # Run tests once
315
+ npm test
316
+
317
+ # Watch mode (re-runs on file changes)
318
+ npm run test:watch
319
+
320
+ # With coverage report
321
+ npm run test:coverage
322
+ ```
323
+
324
+ ### Test Structure
325
+
326
+ Tests are co-located with source files (`*.test.ts`). The test configuration is in `vitest.config.ts`:
327
+
328
+ - Environment: Node
329
+ - Globals: enabled (`describe`, `it`, `expect` available without imports)
330
+ - Coverage: v8 provider, excludes test files and index.ts
331
+
332
+ ## Publishing
333
+
334
+ To publish a new version to npm:
335
+
336
+ ### Prerequisites
337
+
338
+ - npm account with access to the `@posit-dev` organization
339
+ - Logged in via `npm login`
340
+
341
+ ### Publish Steps
342
+
343
+ ```bash
344
+ cd packages/playwright-reporter
345
+
346
+ # 1. Ensure tests pass
347
+ npm test
348
+
349
+ # 2. Build the package
350
+ npm run build
351
+
352
+ # 3. Preview what will be published
353
+ npm pack --dry-run
354
+
355
+ # 4. Bump version (choose one)
356
+ npm version patch # 0.1.0 → 0.1.1 (bug fixes)
357
+ npm version minor # 0.1.0 → 0.2.0 (new features)
358
+ npm version major # 0.1.0 → 1.0.0 (breaking changes)
359
+
360
+ # 5. Publish to npm (scoped packages require --access public)
361
+ npm publish --access public
362
+
363
+ # 6. Push the version commit and tag
364
+ git push && git push --tags
365
+ ```
366
+
367
+ ### Updating in Consumer Repos
368
+
369
+ After publishing, update the package in repos that use it:
370
+
371
+ ```bash
372
+ npm update @midleman/playwright-reporter
373
+ ```
@@ -0,0 +1,13 @@
1
+ import { ResultCreatedEvent } from './types';
2
+ export type SpecBuffer = Map<string, ResultCreatedEvent>;
3
+ export type Buffers = Map<string, SpecBuffer>;
4
+ export declare function ensureSpec(buffers: Buffers, spec: string): SpecBuffer;
5
+ export declare function setTest(buffers: Buffers, spec: string, testKey: string, payload: ResultCreatedEvent): void;
6
+ export declare function getSpec(buffers: Buffers, spec: string): SpecBuffer | undefined;
7
+ export declare function deleteTest(buffers: Buffers, spec: string, testKey: string): void;
8
+ export declare function deleteSpec(buffers: Buffers, spec: string): void;
9
+ export declare function specValues(buffers: Buffers, spec: string): ResultCreatedEvent[];
10
+ export declare function specSize(buffers: Buffers, spec: string): number;
11
+ export declare function listSpecs(buffers: Buffers): string[];
12
+ export declare function allValues(buffers: Buffers): ResultCreatedEvent[];
13
+ //# sourceMappingURL=buffers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffers.d.ts","sourceRoot":"","sources":["../src/buffers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE9C,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAOrE;AAED,wBAAgB,OAAO,CACrB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,QAI5B;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE9E;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAKzE;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,QAExD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAG/E;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAEpD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAQhE"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureSpec = ensureSpec;
4
+ exports.setTest = setTest;
5
+ exports.getSpec = getSpec;
6
+ exports.deleteTest = deleteTest;
7
+ exports.deleteSpec = deleteSpec;
8
+ exports.specValues = specValues;
9
+ exports.specSize = specSize;
10
+ exports.listSpecs = listSpecs;
11
+ exports.allValues = allValues;
12
+ function ensureSpec(buffers, spec) {
13
+ let m = buffers.get(spec);
14
+ if (!m) {
15
+ m = new Map();
16
+ buffers.set(spec, m);
17
+ }
18
+ return m;
19
+ }
20
+ function setTest(buffers, spec, testKey, payload) {
21
+ const m = ensureSpec(buffers, spec);
22
+ m.set(testKey, payload);
23
+ }
24
+ function getSpec(buffers, spec) {
25
+ return buffers.get(spec);
26
+ }
27
+ function deleteTest(buffers, spec, testKey) {
28
+ const m = buffers.get(spec);
29
+ if (!m)
30
+ return;
31
+ m.delete(testKey);
32
+ if (m.size === 0)
33
+ buffers.delete(spec);
34
+ }
35
+ function deleteSpec(buffers, spec) {
36
+ buffers.delete(spec);
37
+ }
38
+ function specValues(buffers, spec) {
39
+ const m = buffers.get(spec);
40
+ return m ? Array.from(m.values()) : [];
41
+ }
42
+ function specSize(buffers, spec) {
43
+ const m = buffers.get(spec);
44
+ return m ? m.size : 0;
45
+ }
46
+ function listSpecs(buffers) {
47
+ return Array.from(buffers.keys());
48
+ }
49
+ function allValues(buffers) {
50
+ const results = [];
51
+ for (const specMap of buffers.values()) {
52
+ for (const result of specMap.values()) {
53
+ results.push(result);
54
+ }
55
+ }
56
+ return results;
57
+ }
58
+ //# sourceMappingURL=buffers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffers.js","sourceRoot":"","sources":["../src/buffers.ts"],"names":[],"mappings":";;AAKA,gCAOC;AAED,0BAQC;AAED,0BAEC;AAED,gCAKC;AAED,gCAEC;AAED,gCAGC;AAED,4BAGC;AAED,8BAEC;AAED,8BAQC;AAxDD,SAAgB,UAAU,CAAC,OAAgB,EAAE,IAAY;IACvD,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,IAAI,GAAG,EAA8B,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAgB,OAAO,CACrB,OAAgB,EAChB,IAAY,EACZ,OAAe,EACf,OAA2B;IAE3B,MAAM,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED,SAAgB,OAAO,CAAC,OAAgB,EAAE,IAAY;IACpD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,SAAgB,UAAU,CAAC,OAAgB,EAAE,IAAY,EAAE,OAAe;IACxE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,CAAC;QAAE,OAAO;IACf,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,UAAU,CAAC,OAAgB,EAAE,IAAY;IACvD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC;AAED,SAAgB,UAAU,CAAC,OAAgB,EAAE,IAAY;IACvD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACzC,CAAC;AAED,SAAgB,QAAQ,CAAC,OAAgB,EAAE,IAAY;IACrD,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,SAAS,CAAC,OAAgB;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,SAAS,CAAC,OAAgB;IACxC,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { ResultCreatedEvent, ResultStatus } from './types';
2
+ export interface CountsState {
3
+ countedStatusPerTest: Map<string, ResultStatus>;
4
+ totalPassed: number;
5
+ totalFailed: number;
6
+ totalSkipped: number;
7
+ totalFlaky: number;
8
+ buffers: Map<string, Map<string, ResultCreatedEvent>>;
9
+ }
10
+ /**
11
+ * Update run-level totals for a single test key idempotently.
12
+ */
13
+ export declare function updateRunTotalsForTest(state: CountsState, testKey: string, now: ResultStatus): void;
14
+ /**
15
+ * Compute live counts starting from finalized totals and including buffered results.
16
+ */
17
+ export declare function getLiveCounts(state: CountsState): {
18
+ passed: number;
19
+ failed: number;
20
+ skipped: number;
21
+ flaky: number;
22
+ };
23
+ /**
24
+ * Get final counts for run completion.
25
+ */
26
+ export declare function getFinalCounts(state: CountsState): {
27
+ passed: number;
28
+ failed: number;
29
+ skipped: number;
30
+ flaky: number;
31
+ };
32
+ //# sourceMappingURL=counts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.d.ts","sourceRoot":"","sources":["../src/counts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;CACvD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,YAAY,QAgBlB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW;;;;;EAiB/C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW;;;;;EAOhD"}
package/dist/counts.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateRunTotalsForTest = updateRunTotalsForTest;
4
+ exports.getLiveCounts = getLiveCounts;
5
+ exports.getFinalCounts = getFinalCounts;
6
+ /**
7
+ * Update run-level totals for a single test key idempotently.
8
+ */
9
+ function updateRunTotalsForTest(state, testKey, now) {
10
+ const prev = state.countedStatusPerTest.get(testKey);
11
+ if (prev !== now) {
12
+ if (prev === 'flaky')
13
+ state.totalFlaky = Math.max(0, state.totalFlaky - 1);
14
+ else if (prev === 'passed')
15
+ state.totalPassed = Math.max(0, state.totalPassed - 1);
16
+ else if (prev === 'failed')
17
+ state.totalFailed = Math.max(0, state.totalFailed - 1);
18
+ else if (prev === 'skipped')
19
+ state.totalSkipped = Math.max(0, state.totalSkipped - 1);
20
+ if (now === 'flaky')
21
+ state.totalFlaky += 1;
22
+ else if (now === 'passed')
23
+ state.totalPassed += 1;
24
+ else if (now === 'failed')
25
+ state.totalFailed += 1;
26
+ else if (now === 'skipped')
27
+ state.totalSkipped += 1;
28
+ state.countedStatusPerTest.set(testKey, now);
29
+ }
30
+ }
31
+ /**
32
+ * Compute live counts starting from finalized totals and including buffered results.
33
+ */
34
+ function getLiveCounts(state) {
35
+ let passed = state.totalPassed;
36
+ const failed = state.totalFailed;
37
+ let skipped = state.totalSkipped;
38
+ let flaky = state.totalFlaky;
39
+ for (const specMap of state.buffers.values()) {
40
+ for (const p of specMap.values()) {
41
+ const s = p.status;
42
+ if (!s)
43
+ continue;
44
+ if (s === 'passed')
45
+ passed += 1;
46
+ else if (s === 'flaky')
47
+ flaky += 1;
48
+ else if (s === 'skipped')
49
+ skipped += 1;
50
+ // do not count interim 'failed' attempts here
51
+ }
52
+ }
53
+ return { passed, failed, skipped, flaky };
54
+ }
55
+ /**
56
+ * Get final counts for run completion.
57
+ */
58
+ function getFinalCounts(state) {
59
+ return {
60
+ passed: state.totalPassed,
61
+ failed: state.totalFailed,
62
+ skipped: state.totalSkipped,
63
+ flaky: state.totalFlaky,
64
+ };
65
+ }
66
+ //# sourceMappingURL=counts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counts.js","sourceRoot":"","sources":["../src/counts.ts"],"names":[],"mappings":";;AAcA,wDAmBC;AAKD,sCAiBC;AAKD,wCAOC;AAxDD;;GAEG;AACH,SAAgB,sBAAsB,CACpC,KAAkB,EAClB,OAAe,EACf,GAAiB;IAEjB,MAAM,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,IAAI,IAAI,KAAK,OAAO;YAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;aACtE,IAAI,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;aAC9E,IAAI,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;aAC9E,IAAI,IAAI,KAAK,SAAS;YAAE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;QAEtF,IAAI,GAAG,KAAK,OAAO;YAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;aACtC,IAAI,GAAG,KAAK,QAAQ;YAAE,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;aAC7C,IAAI,GAAG,KAAK,QAAQ;YAAE,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;aAC7C,IAAI,GAAG,KAAK,SAAS;YAAE,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC;QAEpD,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,KAAkB;IAC9C,IAAI,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC;IACjC,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;IAE7B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC7C,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,CAAC,MAA4B,CAAC;YACzC,IAAI,CAAC,CAAC;gBAAE,SAAS;YACjB,IAAI,CAAC,KAAK,QAAQ;gBAAE,MAAM,IAAI,CAAC,CAAC;iBAC3B,IAAI,CAAC,KAAK,OAAO;gBAAE,KAAK,IAAI,CAAC,CAAC;iBAC9B,IAAI,CAAC,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC,CAAC;YACvC,8CAA8C;QAChD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,KAAkB;IAC/C,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,WAAW;QACzB,MAAM,EAAE,KAAK,CAAC,WAAW;QACzB,OAAO,EAAE,KAAK,CAAC,YAAY;QAC3B,KAAK,EAAE,KAAK,CAAC,UAAU;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { default } from './reporter';
2
+ export { default as ParquetReporter } from './reporter';
3
+ export type { ReporterOptions, ReporterMode } from './types';
4
+ export type { ResultStatus, ResultCreatedEvent, RunStartedEvent, RunCompletedEvent, Counts } from './schemas';
5
+ export { SCHEMA_VERSION, ResultStatusSchema, ResultCreatedEventSchema, RunStartedEventSchema, RunCompletedEventSchema, CountsSchema, OutboundEventSchema, } from './schemas';
6
+ export { validateResultEvent, validateRunStartedEvent, validateRunCompletedEvent, safeValidateResultEvent, safeValidateRunStartedEvent, safeValidateRunCompletedEvent, } from './schemas';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,YAAY,CAAC;AAGxD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAG9G,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,EACZ,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.safeValidateRunCompletedEvent = exports.safeValidateRunStartedEvent = exports.safeValidateResultEvent = exports.validateRunCompletedEvent = exports.validateRunStartedEvent = exports.validateResultEvent = exports.OutboundEventSchema = exports.CountsSchema = exports.RunCompletedEventSchema = exports.RunStartedEventSchema = exports.ResultCreatedEventSchema = exports.ResultStatusSchema = exports.SCHEMA_VERSION = exports.ParquetReporter = exports.default = void 0;
7
+ var reporter_1 = require("./reporter");
8
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(reporter_1).default; } });
9
+ var reporter_2 = require("./reporter");
10
+ Object.defineProperty(exports, "ParquetReporter", { enumerable: true, get: function () { return __importDefault(reporter_2).default; } });
11
+ // Zod schemas (for consumers who want to validate their own payloads)
12
+ var schemas_1 = require("./schemas");
13
+ Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return schemas_1.SCHEMA_VERSION; } });
14
+ Object.defineProperty(exports, "ResultStatusSchema", { enumerable: true, get: function () { return schemas_1.ResultStatusSchema; } });
15
+ Object.defineProperty(exports, "ResultCreatedEventSchema", { enumerable: true, get: function () { return schemas_1.ResultCreatedEventSchema; } });
16
+ Object.defineProperty(exports, "RunStartedEventSchema", { enumerable: true, get: function () { return schemas_1.RunStartedEventSchema; } });
17
+ Object.defineProperty(exports, "RunCompletedEventSchema", { enumerable: true, get: function () { return schemas_1.RunCompletedEventSchema; } });
18
+ Object.defineProperty(exports, "CountsSchema", { enumerable: true, get: function () { return schemas_1.CountsSchema; } });
19
+ Object.defineProperty(exports, "OutboundEventSchema", { enumerable: true, get: function () { return schemas_1.OutboundEventSchema; } });
20
+ // Validation helpers
21
+ var schemas_2 = require("./schemas");
22
+ Object.defineProperty(exports, "validateResultEvent", { enumerable: true, get: function () { return schemas_2.validateResultEvent; } });
23
+ Object.defineProperty(exports, "validateRunStartedEvent", { enumerable: true, get: function () { return schemas_2.validateRunStartedEvent; } });
24
+ Object.defineProperty(exports, "validateRunCompletedEvent", { enumerable: true, get: function () { return schemas_2.validateRunCompletedEvent; } });
25
+ Object.defineProperty(exports, "safeValidateResultEvent", { enumerable: true, get: function () { return schemas_2.safeValidateResultEvent; } });
26
+ Object.defineProperty(exports, "safeValidateRunStartedEvent", { enumerable: true, get: function () { return schemas_2.safeValidateRunStartedEvent; } });
27
+ Object.defineProperty(exports, "safeValidateRunCompletedEvent", { enumerable: true, get: function () { return schemas_2.safeValidateRunCompletedEvent; } });
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,uCAAqC;AAA5B,oHAAA,OAAO,OAAA;AAChB,uCAAwD;AAA/C,4HAAA,OAAO,OAAmB;AAMnC,sEAAsE;AACtE,qCAQmB;AAPjB,yGAAA,cAAc,OAAA;AACd,6GAAA,kBAAkB,OAAA;AAClB,mHAAA,wBAAwB,OAAA;AACxB,gHAAA,qBAAqB,OAAA;AACrB,kHAAA,uBAAuB,OAAA;AACvB,uGAAA,YAAY,OAAA;AACZ,8GAAA,mBAAmB,OAAA;AAGrB,qBAAqB;AACrB,qCAOmB;AANjB,8GAAA,mBAAmB,OAAA;AACnB,kHAAA,uBAAuB,OAAA;AACvB,oHAAA,yBAAyB,OAAA;AACzB,kHAAA,uBAAuB,OAAA;AACvB,sHAAA,2BAA2B,OAAA;AAC3B,wHAAA,6BAA6B,OAAA"}
@@ -0,0 +1,33 @@
1
+ import type { TestCase, TestResult } from '@playwright/test/reporter';
2
+ import { ResultCreatedEvent, ResultStatus, RunStartedEvent, RunCompletedEvent } from './types';
3
+ export interface RunContext {
4
+ repoName: string;
5
+ runId: string;
6
+ branchName?: string;
7
+ sha?: string;
8
+ commitMessage?: string;
9
+ commitAuthor?: string;
10
+ }
11
+ export declare function buildResultPayload(runCtx: RunContext, spec: string, project: string | undefined, browser: string | undefined, osName: string, test: TestCase, result: TestResult, status: ResultStatus, attempt: number, titlePath?: string[]): ResultCreatedEvent;
12
+ export declare function buildRunStarted(runCtx: RunContext, opts: {
13
+ startedAt: string;
14
+ branch?: string;
15
+ sha?: string;
16
+ workflowName?: string;
17
+ jobName?: string;
18
+ runUrl?: string;
19
+ commitMessage?: string;
20
+ commitAuthor?: string;
21
+ }): RunStartedEvent;
22
+ export declare function buildRunCompleted(runCtx: RunContext, opts: {
23
+ completedAt: string;
24
+ status: string;
25
+ durationMs: number;
26
+ counts: {
27
+ passed: number;
28
+ failed: number;
29
+ skipped: number;
30
+ flaky: number;
31
+ };
32
+ }): RunCompletedEvent;
33
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,iBAAiB,EAIlB,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAAE,GACnB,kBAAkB,CA2CpB;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,eAAe,CAuBjB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E,GACA,iBAAiB,CAmBnB"}