@testpulse.run/playwright-jsonl-reporter 0.2.3
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 +72 -0
- package/dist/index.cjs +568 -0
- package/dist/index.d.cts +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +545 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
// src/reporter.ts
|
|
2
|
+
import {
|
|
3
|
+
TestPulseReporterCore,
|
|
4
|
+
TestPulseReporterEventName
|
|
5
|
+
} from "@testpulse.run/playwright-core";
|
|
6
|
+
import {
|
|
7
|
+
PlaywrightEvents,
|
|
8
|
+
TestPulseDerivedEventName
|
|
9
|
+
} from "@testpulse.run/playwright-events";
|
|
10
|
+
|
|
11
|
+
// src/serialize.ts
|
|
12
|
+
function serializeCoreEvent(event, context) {
|
|
13
|
+
return serializeEvent(event, context, "core");
|
|
14
|
+
}
|
|
15
|
+
function serializeDerivedEvent(event, context) {
|
|
16
|
+
return serializeEvent(event, context, "derived");
|
|
17
|
+
}
|
|
18
|
+
function serializeEvent(event, context, stream) {
|
|
19
|
+
const payload = createPayload(event);
|
|
20
|
+
const worker = readWorker(event);
|
|
21
|
+
const test = readTest(event);
|
|
22
|
+
const step = readStep(event);
|
|
23
|
+
const terminal = readTerminalFacts(event);
|
|
24
|
+
const run = readRunFacts(event);
|
|
25
|
+
const eventName = event.eventName;
|
|
26
|
+
return omitUndefined({
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
eventId: context.eventId,
|
|
29
|
+
sequence: context.sequence,
|
|
30
|
+
type: eventName,
|
|
31
|
+
stream,
|
|
32
|
+
eventName,
|
|
33
|
+
timestamp: event.timestamp,
|
|
34
|
+
source: event.source,
|
|
35
|
+
runId: context.runId,
|
|
36
|
+
writerId: context.writerId,
|
|
37
|
+
shardId: context.shard?.shardId,
|
|
38
|
+
shardIndex: context.shard?.shardIndex,
|
|
39
|
+
shardCount: context.shard?.shardCount,
|
|
40
|
+
workerId: worker ? `worker-${worker.workerIndex}` : void 0,
|
|
41
|
+
workerIndex: worker?.workerIndex,
|
|
42
|
+
parallelIndex: worker?.parallelIndex,
|
|
43
|
+
testId: test?.testId,
|
|
44
|
+
testTitle: test?.title,
|
|
45
|
+
file: test?.location?.file,
|
|
46
|
+
line: test?.location?.line,
|
|
47
|
+
column: test?.location?.column,
|
|
48
|
+
projectName: test?.projectName,
|
|
49
|
+
retry: readRetry(event),
|
|
50
|
+
repeatEachIndex: test?.repeatEachIndex,
|
|
51
|
+
expectedStatus: readExpectedStatus(event, test),
|
|
52
|
+
stepId: step?.stepId,
|
|
53
|
+
parentStepId: step?.parentStepId,
|
|
54
|
+
stepTitle: step?.title,
|
|
55
|
+
stepCategory: step?.category,
|
|
56
|
+
stepFile: step?.location?.file,
|
|
57
|
+
stepLine: step?.location?.line,
|
|
58
|
+
stepColumn: step?.location?.column,
|
|
59
|
+
status: terminal.status ?? run.status,
|
|
60
|
+
durationMs: terminal.durationMs ?? run.durationMs,
|
|
61
|
+
startTime: run.startTime,
|
|
62
|
+
totalTests: run.totalTests,
|
|
63
|
+
configuredWorkers: run.configuredWorkers,
|
|
64
|
+
errors: normalizeErrors(terminal.errors),
|
|
65
|
+
attachments: terminal.attachments,
|
|
66
|
+
outcome: "outcome" in event ? event.outcome : void 0,
|
|
67
|
+
payload: Object.keys(payload).length > 0 ? payload : void 0
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function createPayload(event) {
|
|
71
|
+
const payload = {};
|
|
72
|
+
for (const [key, value] of Object.entries(event)) {
|
|
73
|
+
if (value === void 0 || TOP_LEVEL_OR_STRUCTURAL_KEYS.has(key)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
payload[key] = value;
|
|
77
|
+
}
|
|
78
|
+
return payload;
|
|
79
|
+
}
|
|
80
|
+
function readWorker(event) {
|
|
81
|
+
return "worker" in event ? event.worker : void 0;
|
|
82
|
+
}
|
|
83
|
+
function readTest(event) {
|
|
84
|
+
return "test" in event ? event.test : void 0;
|
|
85
|
+
}
|
|
86
|
+
function readStep(event) {
|
|
87
|
+
return "step" in event ? event.step : void 0;
|
|
88
|
+
}
|
|
89
|
+
function readRetry(event) {
|
|
90
|
+
if ("retry" in event) {
|
|
91
|
+
return event.retry;
|
|
92
|
+
}
|
|
93
|
+
if ("attempt" in event) {
|
|
94
|
+
return event.attempt.retry;
|
|
95
|
+
}
|
|
96
|
+
if ("finalAttempt" in event) {
|
|
97
|
+
return event.finalAttempt.retry;
|
|
98
|
+
}
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
101
|
+
function readExpectedStatus(event, test) {
|
|
102
|
+
return "expectedStatus" in event ? event.expectedStatus : test?.expectedStatus;
|
|
103
|
+
}
|
|
104
|
+
function readTerminalFacts(event) {
|
|
105
|
+
if ("attempt" in event) {
|
|
106
|
+
return readAttemptFacts(event.attempt);
|
|
107
|
+
}
|
|
108
|
+
if ("finalAttempt" in event) {
|
|
109
|
+
return readAttemptFacts(event.finalAttempt);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
status: "status" in event ? event.status : void 0,
|
|
113
|
+
durationMs: "durationMs" in event ? event.durationMs : void 0,
|
|
114
|
+
errors: readErrors(event),
|
|
115
|
+
attachments: "attachments" in event ? event.attachments : void 0
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function readAttemptFacts(attempt) {
|
|
119
|
+
return {
|
|
120
|
+
status: attempt.status,
|
|
121
|
+
durationMs: attempt.durationMs,
|
|
122
|
+
errors: attempt.errors,
|
|
123
|
+
attachments: attempt.attachments
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function normalizeErrors(errors) {
|
|
127
|
+
return errors?.map((error) => ({
|
|
128
|
+
message: error.message,
|
|
129
|
+
stack: error.stack,
|
|
130
|
+
value: error.value,
|
|
131
|
+
file: error.location?.file,
|
|
132
|
+
line: error.location?.line,
|
|
133
|
+
column: error.location?.column
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function readRunFacts(event) {
|
|
137
|
+
const result = "result" in event ? event.result : void 0;
|
|
138
|
+
return {
|
|
139
|
+
startTime: result?.startTime,
|
|
140
|
+
status: result?.status,
|
|
141
|
+
durationMs: result?.durationMs,
|
|
142
|
+
totalTests: "totalTests" in event ? event.totalTests : "suite" in event ? event.suite.totalTests : void 0,
|
|
143
|
+
configuredWorkers: "configuredWorkers" in event ? event.configuredWorkers : "config" in event ? event.config.configuredWorkers : void 0
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function readErrors(event) {
|
|
147
|
+
if ("errors" in event) {
|
|
148
|
+
return event.errors;
|
|
149
|
+
}
|
|
150
|
+
if ("error" in event) {
|
|
151
|
+
return event.error ? [event.error] : void 0;
|
|
152
|
+
}
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
var TOP_LEVEL_OR_STRUCTURAL_KEYS = /* @__PURE__ */ new Set([
|
|
156
|
+
"eventName",
|
|
157
|
+
"timestamp",
|
|
158
|
+
"source",
|
|
159
|
+
"test",
|
|
160
|
+
"step",
|
|
161
|
+
"result",
|
|
162
|
+
"config",
|
|
163
|
+
"suite",
|
|
164
|
+
"shard",
|
|
165
|
+
"worker",
|
|
166
|
+
"testId",
|
|
167
|
+
"title",
|
|
168
|
+
"titlePath",
|
|
169
|
+
"totalTests",
|
|
170
|
+
"configuredWorkers",
|
|
171
|
+
"location",
|
|
172
|
+
"projectName",
|
|
173
|
+
"retryCount",
|
|
174
|
+
"repeatEachIndex",
|
|
175
|
+
"retry",
|
|
176
|
+
"attempt",
|
|
177
|
+
"attemptIndex",
|
|
178
|
+
"status",
|
|
179
|
+
"durationMs",
|
|
180
|
+
"expectedStatus",
|
|
181
|
+
"errors",
|
|
182
|
+
"error",
|
|
183
|
+
"attachments",
|
|
184
|
+
"outcome",
|
|
185
|
+
"finalAttempt",
|
|
186
|
+
"attempts",
|
|
187
|
+
"coreEvent"
|
|
188
|
+
]);
|
|
189
|
+
function omitUndefined(event) {
|
|
190
|
+
return Object.fromEntries(Object.entries(event).filter(([, value]) => value !== void 0));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/sink.ts
|
|
194
|
+
import { randomUUID } from "crypto";
|
|
195
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
196
|
+
import { resolve } from "path";
|
|
197
|
+
var FileSystemEventSink = class {
|
|
198
|
+
filePath;
|
|
199
|
+
metadata;
|
|
200
|
+
stream;
|
|
201
|
+
closed = false;
|
|
202
|
+
streamError;
|
|
203
|
+
constructor(options = {}) {
|
|
204
|
+
const outputDir = resolve(options.outputDir ?? "testpulse-events");
|
|
205
|
+
mkdirSync(outputDir, { recursive: true });
|
|
206
|
+
this.metadata = {
|
|
207
|
+
writerId: options.writerId ?? createWriterId(),
|
|
208
|
+
sinkType: "filesystem"
|
|
209
|
+
};
|
|
210
|
+
this.filePath = resolve(outputDir, options.fileName ?? createDefaultFileName());
|
|
211
|
+
this.stream = createWriteStream(this.filePath, { flags: "a" });
|
|
212
|
+
this.stream.on("error", (error) => {
|
|
213
|
+
this.streamError = error;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
write(event) {
|
|
217
|
+
if (this.closed) {
|
|
218
|
+
throw new Error("Cannot write to a closed FileSystemEventSink.");
|
|
219
|
+
}
|
|
220
|
+
if (this.streamError) {
|
|
221
|
+
throw this.streamError;
|
|
222
|
+
}
|
|
223
|
+
this.stream.write(`${JSON.stringify(event)}
|
|
224
|
+
`, (error) => {
|
|
225
|
+
if (error) {
|
|
226
|
+
this.streamError = error;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async flush() {
|
|
231
|
+
if (this.closed) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (this.streamError) {
|
|
235
|
+
throw this.streamError;
|
|
236
|
+
}
|
|
237
|
+
await new Promise((resolvePromise, reject) => {
|
|
238
|
+
this.stream.write("", (error) => {
|
|
239
|
+
if (error) {
|
|
240
|
+
this.streamError = error;
|
|
241
|
+
reject(error);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (this.streamError) {
|
|
245
|
+
reject(this.streamError);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
resolvePromise();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async close() {
|
|
253
|
+
if (this.closed) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.closed = true;
|
|
257
|
+
await new Promise((resolvePromise, reject) => {
|
|
258
|
+
this.stream.end((error) => {
|
|
259
|
+
if (error) {
|
|
260
|
+
this.streamError = error;
|
|
261
|
+
reject(error);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (this.streamError) {
|
|
265
|
+
reject(this.streamError);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
resolvePromise();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function createDefaultFileName() {
|
|
274
|
+
return `events-pid-${process.pid}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.jsonl`;
|
|
275
|
+
}
|
|
276
|
+
function createWriterId() {
|
|
277
|
+
return `filesystem-${process.pid}-${randomUUID()}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/reporter.ts
|
|
281
|
+
var JsonlReporter = class {
|
|
282
|
+
core;
|
|
283
|
+
events;
|
|
284
|
+
sink;
|
|
285
|
+
configuredRunId;
|
|
286
|
+
nextEventId = 1;
|
|
287
|
+
runId = createRunId();
|
|
288
|
+
shard;
|
|
289
|
+
constructor(options = {}) {
|
|
290
|
+
this.sink = options.sink ?? new FileSystemEventSink({ outputDir: options.outputDir });
|
|
291
|
+
this.configuredRunId = options.runId;
|
|
292
|
+
const includeCoreEvents = options.includeCoreEvents ?? true;
|
|
293
|
+
const includeDerivedEvents = options.includeDerivedEvents ?? true;
|
|
294
|
+
const corePlugins = [];
|
|
295
|
+
const derivedPlugins = [];
|
|
296
|
+
if (includeDerivedEvents) {
|
|
297
|
+
derivedPlugins.push(this.createDerivedPlugin());
|
|
298
|
+
}
|
|
299
|
+
this.events = new PlaywrightEvents({ plugins: derivedPlugins });
|
|
300
|
+
if (includeCoreEvents) {
|
|
301
|
+
corePlugins.push(this.createCorePlugin());
|
|
302
|
+
}
|
|
303
|
+
corePlugins.push(this.events.asCorePlugin());
|
|
304
|
+
if (!includeCoreEvents) {
|
|
305
|
+
corePlugins.push(this.createSinkFinalizerPlugin());
|
|
306
|
+
}
|
|
307
|
+
this.core = new TestPulseReporterCore({
|
|
308
|
+
plugins: corePlugins,
|
|
309
|
+
failOnHandlerError: options.failOnHandlerError
|
|
310
|
+
});
|
|
311
|
+
this.events.setErrorSink(this.core);
|
|
312
|
+
}
|
|
313
|
+
onBegin(config, suite) {
|
|
314
|
+
this.nextEventId = 1;
|
|
315
|
+
this.runId = this.configuredRunId ?? createRunId();
|
|
316
|
+
this.shard = void 0;
|
|
317
|
+
this.core.onBegin(config, suite);
|
|
318
|
+
}
|
|
319
|
+
onEnd(result) {
|
|
320
|
+
return this.core.onEnd(result);
|
|
321
|
+
}
|
|
322
|
+
onError(...args) {
|
|
323
|
+
this.core.onError(...args);
|
|
324
|
+
}
|
|
325
|
+
onTestBegin(...args) {
|
|
326
|
+
this.core.onTestBegin(...args);
|
|
327
|
+
}
|
|
328
|
+
onTestEnd(...args) {
|
|
329
|
+
this.core.onTestEnd(...args);
|
|
330
|
+
}
|
|
331
|
+
onStepBegin(...args) {
|
|
332
|
+
this.core.onStepBegin(...args);
|
|
333
|
+
}
|
|
334
|
+
onStepEnd(...args) {
|
|
335
|
+
this.core.onStepEnd(...args);
|
|
336
|
+
}
|
|
337
|
+
onStdOut(...args) {
|
|
338
|
+
this.core.onStdOut(...args);
|
|
339
|
+
}
|
|
340
|
+
onStdErr(...args) {
|
|
341
|
+
this.core.onStdErr(...args);
|
|
342
|
+
}
|
|
343
|
+
createCorePlugin() {
|
|
344
|
+
const plugin = { name: "@testpulse.run/playwright-jsonl-reporter/core" };
|
|
345
|
+
for (const eventName of Object.values(TestPulseReporterEventName)) {
|
|
346
|
+
plugin[eventName] = ((event) => {
|
|
347
|
+
this.writeCoreEvent(event);
|
|
348
|
+
if (event.eventName === "RunFinished") {
|
|
349
|
+
return this.sink.close();
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return plugin;
|
|
354
|
+
}
|
|
355
|
+
createDerivedPlugin() {
|
|
356
|
+
const plugin = { name: "@testpulse.run/playwright-jsonl-reporter/derived" };
|
|
357
|
+
for (const eventName of Object.values(TestPulseDerivedEventName)) {
|
|
358
|
+
plugin[eventName] = ((event) => {
|
|
359
|
+
this.writeDerivedEvent(event);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return plugin;
|
|
363
|
+
}
|
|
364
|
+
createSinkFinalizerPlugin() {
|
|
365
|
+
return {
|
|
366
|
+
name: "@testpulse.run/playwright-jsonl-reporter/finalizer",
|
|
367
|
+
RunFinished: () => this.sink.close()
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
writeCoreEvent(event) {
|
|
371
|
+
if (event.eventName === "ShardStarted") {
|
|
372
|
+
this.shard = event.shard;
|
|
373
|
+
}
|
|
374
|
+
this.sink.write(serializeCoreEvent(event, this.createSerializationContext()));
|
|
375
|
+
}
|
|
376
|
+
writeDerivedEvent(event) {
|
|
377
|
+
this.sink.write(serializeDerivedEvent(event, this.createSerializationContext()));
|
|
378
|
+
}
|
|
379
|
+
createSerializationContext() {
|
|
380
|
+
const sequence = this.nextEventId;
|
|
381
|
+
this.nextEventId += 1;
|
|
382
|
+
return {
|
|
383
|
+
eventId: `${this.runId}:${this.sink.metadata.writerId}:${sequence}`,
|
|
384
|
+
sequence,
|
|
385
|
+
runId: this.runId,
|
|
386
|
+
writerId: this.sink.metadata.writerId,
|
|
387
|
+
shard: this.shard ? {
|
|
388
|
+
shardId: `${this.runId}:${this.sink.metadata.writerId}:shard-${this.shard.current}-of-${this.shard.total}`,
|
|
389
|
+
shardIndex: this.shard.current,
|
|
390
|
+
shardCount: this.shard.total
|
|
391
|
+
} : void 0
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
function createRunId() {
|
|
396
|
+
return `run-${process.pid}-${Date.now()}`;
|
|
397
|
+
}
|
|
398
|
+
var reporter_default = JsonlReporter;
|
|
399
|
+
|
|
400
|
+
// src/http-sink.ts
|
|
401
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
402
|
+
var HttpEventSink = class {
|
|
403
|
+
metadata;
|
|
404
|
+
baseUrl;
|
|
405
|
+
apiKey;
|
|
406
|
+
storageStreamId;
|
|
407
|
+
flushIntervalMs;
|
|
408
|
+
batchSize;
|
|
409
|
+
fetchImpl;
|
|
410
|
+
pending = [];
|
|
411
|
+
closed = false;
|
|
412
|
+
inFlight;
|
|
413
|
+
flushTimer;
|
|
414
|
+
lastBackgroundError;
|
|
415
|
+
constructor(options) {
|
|
416
|
+
if (!options.baseUrl) {
|
|
417
|
+
throw new Error("HttpEventSink requires a baseUrl.");
|
|
418
|
+
}
|
|
419
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
420
|
+
this.apiKey = options.apiKey;
|
|
421
|
+
this.storageStreamId = options.storageStreamId ?? "events";
|
|
422
|
+
this.flushIntervalMs = readPositiveNumber(options.flushIntervalMs, 250, "flushIntervalMs");
|
|
423
|
+
this.batchSize = readPositiveNumber(options.batchSize, 100, "batchSize");
|
|
424
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
425
|
+
this.metadata = {
|
|
426
|
+
writerId: options.writerId ?? createWriterId2(),
|
|
427
|
+
sinkType: "http"
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
write(event) {
|
|
431
|
+
if (this.closed) {
|
|
432
|
+
throw new Error("Cannot write to a closed HttpEventSink.");
|
|
433
|
+
}
|
|
434
|
+
this.pending.push(event);
|
|
435
|
+
this.ensureFlushTimer();
|
|
436
|
+
if (this.pending.length >= this.batchSize) {
|
|
437
|
+
this.triggerBackgroundFlush();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async flush() {
|
|
441
|
+
if (this.inFlight) {
|
|
442
|
+
try {
|
|
443
|
+
await this.inFlight;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
this.lastBackgroundError = error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (this.pending.length === 0) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const events = this.pending.slice();
|
|
452
|
+
this.inFlight = this.sendEvents(events);
|
|
453
|
+
try {
|
|
454
|
+
await this.inFlight;
|
|
455
|
+
this.pending.splice(0, events.length);
|
|
456
|
+
this.lastBackgroundError = void 0;
|
|
457
|
+
} finally {
|
|
458
|
+
this.inFlight = void 0;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async close() {
|
|
462
|
+
if (this.closed) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.clearFlushTimer();
|
|
466
|
+
await this.flush();
|
|
467
|
+
this.closed = true;
|
|
468
|
+
}
|
|
469
|
+
ensureFlushTimer() {
|
|
470
|
+
if (this.flushTimer) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
this.flushTimer = setInterval(() => this.triggerBackgroundFlush(), this.flushIntervalMs);
|
|
474
|
+
this.flushTimer.unref?.();
|
|
475
|
+
}
|
|
476
|
+
clearFlushTimer() {
|
|
477
|
+
if (!this.flushTimer) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
clearInterval(this.flushTimer);
|
|
481
|
+
this.flushTimer = void 0;
|
|
482
|
+
}
|
|
483
|
+
triggerBackgroundFlush() {
|
|
484
|
+
if (this.inFlight) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
void this.flush().catch((error) => {
|
|
488
|
+
this.lastBackgroundError = error;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
async sendEvents(events) {
|
|
492
|
+
for (const group of groupByRun(events)) {
|
|
493
|
+
const response = await this.fetchImpl(this.createEndpoint(group.runId), {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: this.createHeaders(),
|
|
496
|
+
body: JSON.stringify({ events: group.events })
|
|
497
|
+
});
|
|
498
|
+
if (!response.ok) {
|
|
499
|
+
throw new Error(`TestPulse HTTP ingest failed with ${response.status} ${response.statusText}: ${await response.text()}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
createEndpoint(runId) {
|
|
504
|
+
return `${this.baseUrl}/api/v1/runs/${encodeURIComponent(runId)}/streams/${encodeURIComponent(this.storageStreamId)}/events`;
|
|
505
|
+
}
|
|
506
|
+
createHeaders() {
|
|
507
|
+
const headers = {
|
|
508
|
+
"content-type": "application/json"
|
|
509
|
+
};
|
|
510
|
+
if (this.apiKey) {
|
|
511
|
+
headers["x-api-key"] = this.apiKey;
|
|
512
|
+
}
|
|
513
|
+
return headers;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
function groupByRun(events) {
|
|
517
|
+
const groups = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const event of events) {
|
|
519
|
+
const group = groups.get(event.runId);
|
|
520
|
+
if (group) {
|
|
521
|
+
group.events.push(event);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
groups.set(event.runId, { runId: event.runId, events: [event] });
|
|
525
|
+
}
|
|
526
|
+
return [...groups.values()];
|
|
527
|
+
}
|
|
528
|
+
function createWriterId2() {
|
|
529
|
+
return `http-${process.pid}-${randomUUID2()}`;
|
|
530
|
+
}
|
|
531
|
+
function readPositiveNumber(value, defaultValue, optionName) {
|
|
532
|
+
if (value === void 0) {
|
|
533
|
+
return defaultValue;
|
|
534
|
+
}
|
|
535
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
536
|
+
throw new Error(`HttpEventSink ${optionName} must be a positive number.`);
|
|
537
|
+
}
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
export {
|
|
541
|
+
FileSystemEventSink,
|
|
542
|
+
HttpEventSink,
|
|
543
|
+
JsonlReporter,
|
|
544
|
+
reporter_default as default
|
|
545
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testpulse.run/playwright-jsonl-reporter",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Append-only JSONL event reporter for Playwright TestPulse events.",
|
|
5
|
+
"homepage": "https://testpulse.run",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"testpulse",
|
|
8
|
+
"playwright",
|
|
9
|
+
"playwright-reporter",
|
|
10
|
+
"jsonl",
|
|
11
|
+
"test-events",
|
|
12
|
+
"test-reporter",
|
|
13
|
+
"test-automation",
|
|
14
|
+
"testing",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@testpulse.run/playwright-core": "0.2.3",
|
|
40
|
+
"@testpulse.run/playwright-events": "0.2.3"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@playwright/test": ">=1.40.0"
|
|
44
|
+
}
|
|
45
|
+
}
|