@piwitests/reporter 0.12.0 → 0.13.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 (77) hide show
  1. package/dist/global-setup-module.d.ts +2 -1
  2. package/dist/global-setup-module.js +432 -3
  3. package/dist/index.d.ts +204 -8
  4. package/dist/index.js +4724 -23
  5. package/dist/internal/capture/attachments.d.ts +8 -3
  6. package/dist/internal/capture/attachments.js +46 -21
  7. package/dist/internal/capture/capture-fixtures.d.ts +16 -14
  8. package/dist/internal/capture/capture-fixtures.js +2211 -971
  9. package/dist/internal/capture/inspect-on-failure.d.ts +50 -0
  10. package/dist/internal/capture/inspect-on-failure.js +66 -0
  11. package/dist/internal/capture/locator-healing.d.ts +33 -190
  12. package/dist/internal/capture/locator-healing.js +599 -815
  13. package/dist/internal/capture/pick-on-failure.d.ts +198 -0
  14. package/dist/internal/capture/pick-on-failure.js +1203 -0
  15. package/package.json +9 -4
  16. package/dist/internal/collect/error-text.d.ts +0 -18
  17. package/dist/internal/collect/error-text.js +0 -79
  18. package/dist/internal/collect/metadata-collector.d.ts +0 -32
  19. package/dist/internal/collect/metadata-collector.js +0 -246
  20. package/dist/internal/collect/skip-classify.d.ts +0 -27
  21. package/dist/internal/collect/skip-classify.js +0 -40
  22. package/dist/internal/collect/step-analyzer.d.ts +0 -103
  23. package/dist/internal/collect/step-analyzer.js +0 -221
  24. package/dist/internal/config/env.d.ts +0 -46
  25. package/dist/internal/config/env.js +0 -162
  26. package/dist/internal/files/compression.d.ts +0 -5
  27. package/dist/internal/files/compression.js +0 -69
  28. package/dist/internal/files/file-handler.d.ts +0 -38
  29. package/dist/internal/files/file-handler.js +0 -207
  30. package/dist/internal/streaming/crash-recovery.d.ts +0 -23
  31. package/dist/internal/streaming/crash-recovery.js +0 -106
  32. package/dist/internal/streaming/stream-buffer.d.ts +0 -17
  33. package/dist/internal/streaming/stream-buffer.js +0 -102
  34. package/dist/internal/streaming/stream-manager.d.ts +0 -88
  35. package/dist/internal/streaming/stream-manager.js +0 -395
  36. package/dist/internal/submit/run-submitter.d.ts +0 -67
  37. package/dist/internal/submit/run-submitter.js +0 -190
  38. package/dist/internal/submit/serializer.d.ts +0 -45
  39. package/dist/internal/submit/serializer.js +0 -108
  40. package/dist/internal/submit/uploader.d.ts +0 -89
  41. package/dist/internal/submit/uploader.js +0 -226
  42. package/dist/internal/support/ci.d.ts +0 -2
  43. package/dist/internal/support/ci.js +0 -32
  44. package/dist/internal/support/cli-filters.d.ts +0 -1
  45. package/dist/internal/support/cli-filters.js +0 -51
  46. package/dist/internal/support/errors.d.ts +0 -8
  47. package/dist/internal/support/errors.js +0 -15
  48. package/dist/internal/support/instance-id.d.ts +0 -4
  49. package/dist/internal/support/instance-id.js +0 -48
  50. package/dist/internal/support/limiter.d.ts +0 -2
  51. package/dist/internal/support/limiter.js +0 -27
  52. package/dist/internal/support/logger.d.ts +0 -26
  53. package/dist/internal/support/logger.js +0 -43
  54. package/dist/internal/support/reporter-version.d.ts +0 -2
  55. package/dist/internal/support/reporter-version.js +0 -54
  56. package/dist/internal/support/setup-file.d.ts +0 -13
  57. package/dist/internal/support/setup-file.js +0 -61
  58. package/dist/internal/support/source-snippet.d.ts +0 -12
  59. package/dist/internal/support/source-snippet.js +0 -97
  60. package/dist/internal/support/worker-index.d.ts +0 -7
  61. package/dist/internal/support/worker-index.js +0 -14
  62. package/dist/internal/transport/http-client.d.ts +0 -52
  63. package/dist/internal/transport/http-client.js +0 -201
  64. package/dist/public/config-wrapper.d.ts +0 -21
  65. package/dist/public/config-wrapper.js +0 -64
  66. package/dist/public/global-setup.d.ts +0 -13
  67. package/dist/public/global-setup.js +0 -146
  68. package/dist/public/options.d.ts +0 -86
  69. package/dist/public/options.js +0 -2
  70. package/dist/public/reporter.d.ts +0 -68
  71. package/dist/public/reporter.js +0 -376
  72. package/dist/types/collected.d.ts +0 -97
  73. package/dist/types/collected.js +0 -10
  74. package/dist/types/wire.d.ts +0 -176
  75. package/dist/types/wire.js +0 -14
  76. package/dist/types.d.ts +0 -11
  77. package/dist/types.js +0 -27
@@ -1,2 +1,3 @@
1
1
  declare const _default: (config: any) => Promise<any>;
2
- export default _default;
2
+
3
+ export { _default as default };
@@ -1,4 +1,433 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const global_setup_js_1 = require("./public/global-setup.js");
4
- exports.default = (0, global_setup_js_1.createGlobalSetup)();
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/global-setup-module.ts
31
+ var global_setup_module_exports = {};
32
+ __export(global_setup_module_exports, {
33
+ default: () => global_setup_module_default
34
+ });
35
+ module.exports = __toCommonJS(global_setup_module_exports);
36
+
37
+ // src/public/global-setup.ts
38
+ var path2 = __toESM(require("path"));
39
+
40
+ // src/internal/support/errors.ts
41
+ function errorMessage(error) {
42
+ if (error instanceof Error) return error.message;
43
+ return String(error);
44
+ }
45
+
46
+ // src/public/global-setup.ts
47
+ var fs2 = __toESM(require("fs"));
48
+
49
+ // src/internal/config/env.ts
50
+ var DEFAULTS = {
51
+ projectName: "default-project",
52
+ uploadTraces: true,
53
+ uploadReport: true,
54
+ liveFileUploads: true,
55
+ collectScmInfo: true,
56
+ collectCiInfo: true,
57
+ collectPerformanceMetrics: true,
58
+ captureLocators: true,
59
+ capturePageState: true,
60
+ streaming: true,
61
+ streamingBatchSize: 5,
62
+ streamingBatchDelay: 2e3,
63
+ username: null,
64
+ password: null,
65
+ apiKey: null,
66
+ verbose: false
67
+ };
68
+ var PIWI_ENV_KEYS = {
69
+ serverUrl: "PIWI_DASHBOARD_URL",
70
+ projectName: "PIWI_PROJECT_NAME",
71
+ verbose: "PIWI_VERBOSE",
72
+ apiKey: "PIWI_API_KEY",
73
+ username: "PIWI_USERNAME",
74
+ password: "PIWI_PASSWORD",
75
+ environment: "PIWI_ENVIRONMENT",
76
+ label: "PIWI_LABEL",
77
+ runLabel: "PIWI_RUN_LABEL",
78
+ streaming: "PIWI_STREAMING",
79
+ streamingBatchSize: "PIWI_STREAMING_BATCH_SIZE",
80
+ streamingBatchDelay: "PIWI_STREAMING_BATCH_DELAY",
81
+ liveFileUploads: "PIWI_LIVE_FILE_UPLOADS",
82
+ uploadTraces: "PIWI_UPLOAD_TRACES",
83
+ uploadReport: "PIWI_UPLOAD_REPORT",
84
+ captureLocators: "PIWI_CAPTURE_LOCATORS",
85
+ capturePageState: "PIWI_CAPTURE_PAGE_STATE",
86
+ inspectOnFailure: "PIWI_INSPECT_ON_FAIL",
87
+ pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL"
88
+ };
89
+ function readBool(val) {
90
+ if (val === void 0) return void 0;
91
+ return val === "true";
92
+ }
93
+ var ENV_FALLBACK_SPECS = [
94
+ { option: "serverUrl", env: PIWI_ENV_KEYS.serverUrl, kind: "string" },
95
+ { option: "projectName", env: PIWI_ENV_KEYS.projectName, kind: "string" },
96
+ { option: "apiKey", env: PIWI_ENV_KEYS.apiKey, kind: "string" },
97
+ { option: "username", env: PIWI_ENV_KEYS.username, kind: "string" },
98
+ { option: "password", env: PIWI_ENV_KEYS.password, kind: "string" },
99
+ { option: "environment", env: PIWI_ENV_KEYS.environment, kind: "string" },
100
+ { option: "label", env: PIWI_ENV_KEYS.label, kind: "string" },
101
+ { option: "runLabel", env: PIWI_ENV_KEYS.runLabel, kind: "string" },
102
+ { option: "streaming", env: PIWI_ENV_KEYS.streaming, kind: "bool" },
103
+ { option: "streamingBatchSize", env: PIWI_ENV_KEYS.streamingBatchSize, kind: "number" },
104
+ { option: "streamingBatchDelay", env: PIWI_ENV_KEYS.streamingBatchDelay, kind: "number" },
105
+ { option: "liveFileUploads", env: PIWI_ENV_KEYS.liveFileUploads, kind: "bool" },
106
+ { option: "uploadTraces", env: PIWI_ENV_KEYS.uploadTraces, kind: "bool" },
107
+ { option: "uploadReport", env: PIWI_ENV_KEYS.uploadReport, kind: "bool" },
108
+ { option: "captureLocators", env: PIWI_ENV_KEYS.captureLocators, kind: "bool" },
109
+ { option: "capturePageState", env: PIWI_ENV_KEYS.capturePageState, kind: "bool" },
110
+ { option: "inspectOnFailure", env: PIWI_ENV_KEYS.inspectOnFailure, kind: "bool" },
111
+ { option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" }
112
+ ];
113
+ function resolveOptions(raw) {
114
+ const env = process.env;
115
+ const mergedRaw = { ...raw };
116
+ for (const spec of ENV_FALLBACK_SPECS) {
117
+ if (mergedRaw[spec.option] !== void 0) continue;
118
+ const value = env[spec.env];
119
+ if (spec.kind === "bool") {
120
+ if (value !== void 0) mergedRaw[spec.option] = readBool(value);
121
+ } else if (value) {
122
+ mergedRaw[spec.option] = spec.kind === "number" ? Number(value) : value;
123
+ }
124
+ }
125
+ const opts = { ...DEFAULTS, ...mergedRaw };
126
+ if (env[PIWI_ENV_KEYS.verbose] !== void 0) opts.verbose = env[PIWI_ENV_KEYS.verbose] === "true";
127
+ return opts;
128
+ }
129
+
130
+ // src/internal/transport/http-client.ts
131
+ var https = __toESM(require("https"));
132
+ var http = __toESM(require("http"));
133
+ var import_node_url = require("url");
134
+ var import_form_data = __toESM(require("form-data"));
135
+
136
+ // src/internal/support/logger.ts
137
+ var Logger = class {
138
+ constructor(verbose = false) {
139
+ this.verbose = verbose;
140
+ this.prefix = "[Piwi Dashboard] ";
141
+ }
142
+ info(msg) {
143
+ console.log(this.prefix + msg);
144
+ }
145
+ warn(msg) {
146
+ console.warn(this.prefix + msg);
147
+ }
148
+ error(msg) {
149
+ console.error(this.prefix + msg);
150
+ }
151
+ debug(msg) {
152
+ if (this.verbose) console.log(this.prefix + msg);
153
+ }
154
+ debugError(msg) {
155
+ if (this.verbose) console.error(this.prefix + msg);
156
+ }
157
+ };
158
+
159
+ // src/internal/transport/http-client.ts
160
+ function parseErrorMessage(text) {
161
+ try {
162
+ const parsed = JSON.parse(text);
163
+ const message = parsed?.message;
164
+ return typeof message === "string" ? message : void 0;
165
+ } catch {
166
+ return void 0;
167
+ }
168
+ }
169
+ var HttpError = class extends Error {
170
+ constructor(status, message = `Request failed with status ${status}`) {
171
+ super(message);
172
+ this.status = status;
173
+ this.name = "HttpError";
174
+ }
175
+ };
176
+ var HttpClient = class {
177
+ /**
178
+ * @param serverUrl Base URL of the Piwi Dashboard server (e.g. `http://localhost:3000`).
179
+ * @param logger Prefixed logger for verbose diagnostics.
180
+ * @param timeout Socket inactivity timeout in ms (default 30s). A hung
181
+ * server now fails fast instead of stalling the reporter.
182
+ */
183
+ constructor(serverUrl, logger = new Logger(), timeout = 3e4) {
184
+ this.serverUrl = serverUrl;
185
+ this.logger = logger;
186
+ this.timeout = timeout;
187
+ }
188
+ /** Base URL of the Piwi Dashboard server this client talks to (used to print run links). */
189
+ get baseUrl() {
190
+ return this.serverUrl;
191
+ }
192
+ /**
193
+ * Resolve an auth credential: prefer `apiKey`, fall back to `username`/`password` login,
194
+ * or return `null` when neither is configured.
195
+ */
196
+ async resolveAuth(options) {
197
+ if (options.apiKey) return options.apiKey;
198
+ if (options.username && options.password) {
199
+ this.logger.info(`Authenticating as ${options.username}...`);
200
+ return this.login(options.username, options.password);
201
+ }
202
+ return null;
203
+ }
204
+ /** Authenticate with username/password and return the session cookie string */
205
+ async login(username, password) {
206
+ const body = JSON.stringify({ username, password });
207
+ const res = await this.request("POST", "/api/auth/login", {
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ "Content-Length": Buffer.byteLength(body)
211
+ },
212
+ body
213
+ });
214
+ if (res.status < 200 || res.status >= 300) {
215
+ this.logger.debugError(`Login response: ${res.text}`);
216
+ throw new HttpError(res.status, `Login failed with status ${res.status}`);
217
+ }
218
+ const setCookie = res.headers["set-cookie"];
219
+ if (!setCookie || setCookie.length === 0) {
220
+ throw new Error("Login succeeded but no session cookie was returned");
221
+ }
222
+ const cookie = setCookie.map((c) => c.split(";")[0]).join("; ");
223
+ this.logger.debug("Logged in successfully");
224
+ return cookie;
225
+ }
226
+ /** Send a JSON POST request. `auth` can be an API key (prefix `pd_`) or a session cookie string. */
227
+ async postJSON(pathname, payload, auth) {
228
+ const body = JSON.stringify(payload);
229
+ const res = await this.request("POST", pathname, {
230
+ headers: {
231
+ "Content-Type": "application/json",
232
+ "Content-Length": Buffer.byteLength(body)
233
+ },
234
+ body,
235
+ auth
236
+ });
237
+ if (res.status < 200 || res.status >= 300) {
238
+ this.logger.debugError(`Response: ${res.text}`);
239
+ throw new HttpError(res.status);
240
+ }
241
+ try {
242
+ return JSON.parse(res.text);
243
+ } catch {
244
+ return {};
245
+ }
246
+ }
247
+ /** Send a multipart form-data POST request. Used for report and trace uploads. */
248
+ async postFormData(pathname, form, auth) {
249
+ const headers = form.getHeaders();
250
+ const res = await this.request("POST", pathname, { headers, form, auth });
251
+ if (res.status < 200 || res.status >= 300) {
252
+ this.logger.debugError(`Response: ${res.text}`);
253
+ const detail = parseErrorMessage(res.text);
254
+ throw new HttpError(
255
+ res.status,
256
+ detail ? `Request failed with status ${res.status}: ${detail}` : `Request failed with status ${res.status}`
257
+ );
258
+ }
259
+ try {
260
+ return JSON.parse(res.text);
261
+ } catch {
262
+ return {};
263
+ }
264
+ }
265
+ /** Unified request core: transport, headers, auth, response accumulation, timeout. */
266
+ request(method, pathname, opts) {
267
+ return new Promise((resolve2, reject) => {
268
+ const url = new import_node_url.URL(pathname, this.serverUrl);
269
+ const transport = url.protocol === "https:" ? https : http;
270
+ const headers = { ...opts.headers };
271
+ this.applyAuth(headers, opts.auth);
272
+ const req = transport.request(
273
+ {
274
+ hostname: url.hostname,
275
+ port: url.port || (url.protocol === "https:" ? 443 : 80),
276
+ path: url.pathname,
277
+ method,
278
+ headers
279
+ },
280
+ (res) => {
281
+ let data = "";
282
+ res.on("data", (chunk) => {
283
+ data += chunk;
284
+ });
285
+ res.on("end", () => {
286
+ resolve2({ status: res.statusCode ?? 0, text: data, headers: res.headers });
287
+ });
288
+ }
289
+ );
290
+ req.on("error", reject);
291
+ req.setTimeout(this.timeout, () => {
292
+ req.destroy(new Error(`Request to ${pathname} timed out after ${this.timeout}ms`));
293
+ });
294
+ if (opts.form) {
295
+ opts.form.pipe(req);
296
+ } else if (opts.body !== void 0) {
297
+ req.write(opts.body);
298
+ req.end();
299
+ } else {
300
+ req.end();
301
+ }
302
+ });
303
+ }
304
+ applyAuth(headers, auth) {
305
+ if (!auth) return;
306
+ if (auth.startsWith("pd_")) {
307
+ headers["Authorization"] = `Bearer ${auth}`;
308
+ } else {
309
+ headers["Cookie"] = auth;
310
+ }
311
+ }
312
+ };
313
+
314
+ // src/internal/support/instance-id.ts
315
+ var crypto = __toESM(require("crypto"));
316
+ var os = __toESM(require("os"));
317
+ function hashForProject(projectName) {
318
+ return crypto.createHash("sha1").update(projectName).digest("hex").slice(0, 16);
319
+ }
320
+ function computeInstanceId(projectName, runLabel) {
321
+ const seed = runLabel ? `${projectName}|${runLabel}` : `${os.hostname()}|${projectName}`;
322
+ return crypto.createHash("sha256").update(seed).digest("hex").slice(0, 16);
323
+ }
324
+
325
+ // src/internal/support/ci.ts
326
+ function detectCiRunLabel() {
327
+ const env = process.env;
328
+ if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) return env.GITHUB_RUN_ID;
329
+ if (env.GITLAB_CI && env.CI_PIPELINE_ID) return env.CI_PIPELINE_ID;
330
+ if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return env.CIRCLE_WORKFLOW_ID;
331
+ if (env.TRAVIS && env.TRAVIS_BUILD_ID) return env.TRAVIS_BUILD_ID;
332
+ if (env.TF_BUILD && env.BUILD_BUILDID) return env.BUILD_BUILDID;
333
+ if (env.JENKINS_URL && env.BUILD_ID) return env.BUILD_ID;
334
+ if (env.BUILDKITE_BUILD_ID) return env.BUILDKITE_BUILD_ID;
335
+ if (env.TEAMCITY_BUILD_ID) return env.TEAMCITY_BUILD_ID;
336
+ if (env.BITBUCKET_BUILD_NUMBER) return env.BITBUCKET_BUILD_NUMBER;
337
+ if (env.SEMAPHORE_WORKFLOW_ID) return env.SEMAPHORE_WORKFLOW_ID;
338
+ if (env.APPVEYOR_BUILD_ID) return env.APPVEYOR_BUILD_ID;
339
+ if (env.DRONE_BUILD_NUMBER) return env.DRONE_BUILD_NUMBER;
340
+ return null;
341
+ }
342
+
343
+ // src/internal/support/setup-file.ts
344
+ var path = __toESM(require("path"));
345
+ var os2 = __toESM(require("os"));
346
+ var fs = __toESM(require("fs"));
347
+ function getSetupFilePath(projectName) {
348
+ return path.join(os2.tmpdir(), `piwi-dashboard-setup-${hashForProject(projectName)}.json`);
349
+ }
350
+
351
+ // src/public/global-setup.ts
352
+ function createGlobalSetup(options, userSetup) {
353
+ return async function globalSetupFn(config) {
354
+ const piwiReporterPath = path2.resolve(__dirname, "./index.js");
355
+ let inlineReporterOptions = {};
356
+ if (Array.isArray(config?.reporter)) {
357
+ for (const r of config.reporter) {
358
+ if (!Array.isArray(r) || typeof r[0] !== "string") continue;
359
+ const isPiwi = r[0].toLowerCase().includes("piwi") || (() => {
360
+ try {
361
+ return path2.resolve(require.resolve(r[0])) === piwiReporterPath;
362
+ } catch {
363
+ return false;
364
+ }
365
+ })();
366
+ if (isPiwi && r[1] && typeof r[1] === "object") {
367
+ inlineReporterOptions = r[1];
368
+ break;
369
+ }
370
+ }
371
+ }
372
+ const opts = resolveOptions({ ...inlineReporterOptions, ...options });
373
+ const logger = new Logger(opts.verbose ?? false);
374
+ if (opts.enabled === false || !opts.serverUrl) {
375
+ logger.info("Not enabled \u2014 set PIWI_DASHBOARD_URL or serverUrl to enable.");
376
+ if (userSetup) return userSetup(config);
377
+ return;
378
+ }
379
+ const hasPiwi = Object.keys(inlineReporterOptions).length > 0 || Array.isArray(config?.reporter) && config.reporter.some((r) => {
380
+ if (!Array.isArray(r) || typeof r[0] !== "string") return false;
381
+ if (r[0].toLowerCase().includes("piwi")) return true;
382
+ try {
383
+ return path2.resolve(require.resolve(r[0])) === piwiReporterPath;
384
+ } catch {
385
+ return false;
386
+ }
387
+ });
388
+ if (!hasPiwi) {
389
+ logger.debug("Not reporting \u2014 Piwi is not in the Playwright reporters list.");
390
+ if (userSetup) return userSetup(config);
391
+ return;
392
+ }
393
+ const httpClient = new HttpClient(opts.serverUrl, logger);
394
+ try {
395
+ const auth = await httpClient.resolveAuth(opts);
396
+ const runLabel = opts.runLabel || detectCiRunLabel();
397
+ const pwShard = config.shard;
398
+ const shardIndex = pwShard?.current;
399
+ const shardTotal = pwShard?.total;
400
+ const response = await httpClient.postJSON(
401
+ "/api/test-runs/setup",
402
+ {
403
+ projectName: opts.projectName,
404
+ projectDescription: opts.projectDescription,
405
+ environment: opts.environment || null,
406
+ label: opts.label || null,
407
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
408
+ instanceId: computeInstanceId(opts.projectName, runLabel),
409
+ shardIndex,
410
+ shardTotal
411
+ },
412
+ auth
413
+ );
414
+ if (response?.runId && response?.setupToken) {
415
+ fs2.writeFileSync(
416
+ getSetupFilePath(opts.projectName),
417
+ JSON.stringify({
418
+ runId: response.runId,
419
+ setupToken: response.setupToken,
420
+ projectName: opts.projectName
421
+ })
422
+ );
423
+ logger.debug(`Global setup: initialising run #${response.runId}`);
424
+ }
425
+ } catch (error) {
426
+ logger.warn(`Could not register global setup: ${errorMessage(error)}`);
427
+ }
428
+ if (userSetup) return userSetup(config);
429
+ };
430
+ }
431
+
432
+ // src/global-setup-module.ts
433
+ var global_setup_module_default = createGlobalSetup();
package/dist/index.d.ts CHANGED
@@ -1,3 +1,205 @@
1
+ import { FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
+ import { PlaywrightTestConfig } from '@playwright/test';
3
+ export { PlaywrightTestConfig } from '@playwright/test';
4
+ export { PiwiFixtures, extendPiwiFixtures, piwiFixtures } from './internal/capture/capture-fixtures.js';
5
+
6
+ /** Options for configuring the Piwi Dashboard reporter */
7
+ interface PiwiDashboardOptions extends PlaywrightTestConfig {
8
+ /** Explicitly enable or disable the reporter. Defaults to `true` when `serverUrl` is set. Set to `false` to disable even if `serverUrl` is provided. */
9
+ enabled?: boolean;
10
+ /** URL of the Piwi Dashboard server */
11
+ serverUrl?: string;
12
+ /** Name of the project to report results under. Defaults to `'default-project'`. */
13
+ projectName?: string;
14
+ /** Optional description of the project */
15
+ projectDescription?: string;
16
+ /** Upload trace files to the dashboard. Defaults to `true`. */
17
+ uploadTraces?: boolean;
18
+ /** Upload the Playwright HTML report. Defaults to `true`. */
19
+ uploadReport?: boolean;
20
+ /** Upload each test's trace and attachments as soon as the test finishes (streaming mode only). Defaults to `true`. */
21
+ liveFileUploads?: boolean;
22
+ /** Auto-collect git commit, branch, author. Defaults to `true`. */
23
+ collectScmInfo?: boolean;
24
+ /** Auto-collect CI environment info. Defaults to `true`. */
25
+ collectCiInfo?: boolean;
26
+ /** Collect step timings, network requests and web vitals. Defaults to `true`. */
27
+ collectPerformanceMetrics?: boolean;
28
+ /**
29
+ * Capture per-action locator snapshots that power failure-time healing
30
+ * suggestions. Adds a small per-action cost (one DOM read, sometimes an ARIA
31
+ * snapshot) in the test worker. Defaults to `true`; automatically disabled
32
+ * when `collectPerformanceMetrics` is `false` (the reporter discards the data
33
+ * in that case anyway). Can also be forced off with `PIWI_CAPTURE_LOCATORS=false`.
34
+ */
35
+ captureLocators?: boolean;
36
+ /**
37
+ * Capture the page's state at test end (URL, history state, localStorage/
38
+ * sessionStorage key names + value lengths, cookie names + flags). Values of
39
+ * storage entries and cookies are never captured. Defaults to `true`;
40
+ * automatically disabled when `collectPerformanceMetrics` is `false`. Can
41
+ * also be forced off with `PIWI_CAPTURE_PAGE_STATE=false`.
42
+ */
43
+ capturePageState?: boolean;
44
+ /**
45
+ * Open Piwi's own failure-time overlay on the failing page — for inspecting
46
+ * the page and picking a locator for any element (click an element → confirm
47
+ * a ranked replacement locator). This is Piwi's own in-page overlay, not
48
+ * Playwright's native inspector, so a confirmed pick flows back into the
49
+ * dashboard. `pickLocatorOnFailure` opens the same overlay targeted at the
50
+ * broken locator; this opens it for the whole page. Local debugging aid: only
51
+ * takes effect in a headed browser (`headless: false` / `--headed`), never
52
+ * under CI, and only on a test's final attempt when retries are configured.
53
+ * The run waits until the overlay is resolved. Defaults to `false`. Can also
54
+ * be enabled with `PIWI_INSPECT_ON_FAIL=true`.
55
+ */
56
+ inspectOnFailure?: boolean;
57
+ /**
58
+ * Open Piwi's locator picker on the failing page when a locator action
59
+ * fails: click the element the locator should have matched, confirm one of
60
+ * the ranked replacement locators generated for it, and the choice is
61
+ * recorded — folded into the run's locator snapshots (so the dashboard's
62
+ * healing panel shows it) and attached as `piwi-user-pick` with a report
63
+ * annotation. Same gate as `inspectOnFailure`: headed browser only, never
64
+ * under CI, final attempt only. Defaults to `false`. Can also be enabled
65
+ * with `PIWI_PICK_LOCATOR_ON_FAIL=true`.
66
+ */
67
+ pickLocatorOnFailure?: boolean;
68
+ /** Enable live streaming of results (falls back to batch if unsupported). Defaults to `true`. */
69
+ streaming?: boolean;
70
+ /** Number of test results to batch before sending during streaming. Defaults to `5`. */
71
+ streamingBatchSize?: number;
72
+ /** Max delay (ms) before flushing pending events during streaming. Defaults to `2000`. */
73
+ streamingBatchDelay?: number;
74
+ /** Username for dashboard login (use `apiKey` instead when possible) */
75
+ username?: string | null;
76
+ /** Password for dashboard login (used with `username`) */
77
+ password?: string | null;
78
+ /** API key for authentication (preferred over `username`/`password` for CI) */
79
+ apiKey?: string | null;
80
+ /** Additional report types to upload. Each entry can specify `type`, optional `dir`, and optional `label`. */
81
+ reports?: Array<{
82
+ type: string;
83
+ dir?: string;
84
+ label?: string;
85
+ }>;
86
+ /** Stable label that ties shards together (e.g. CI run ID). Auto-detected from CI env; override if needed. */
87
+ runLabel?: string;
88
+ /** Deployment environment for this run, e.g. `"production"`, `"staging"`, `"integration"` */
89
+ environment?: string;
90
+ /** Optional display label for the test run (e.g. "v2.3.1 release") */
91
+ label?: string;
92
+ /** Related issue reference, e.g. `"JIRA-123"` */
93
+ relatedIssue?: string;
94
+ /** CI job information */
95
+ ciInfo?: string;
96
+ /** Tags to categorize the test run */
97
+ tags?: string[];
98
+ /** Additional custom metadata as key-value pairs */
99
+ customData?: Record<string, unknown>;
100
+ /** Enable verbose logging for debugging. Defaults to `false`. */
101
+ verbose?: boolean;
102
+ }
103
+
104
+ /**
105
+ * Create a Playwright `globalSetup` function that registers a test run on the
106
+ * Piwi Dashboard server at the start of the run. An optional `userSetup` is
107
+ * called afterward so existing setup logic is preserved.
108
+ *
109
+ * The server-run ID and a one-time token are written to a temp file so the
110
+ * reporter instance can pick them up during the streaming handshake.
111
+ *
112
+ * @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
113
+ * @param userSetup An existing global setup to chain after the Piwi registration.
114
+ */
115
+ declare function createGlobalSetup(options?: PiwiDashboardOptions, userSetup?: (config: any) => any): (config: any) => Promise<any>;
116
+
117
+ /**
118
+ * Wrap a Playwright config to auto-inject the Piwi Dashboard reporter and
119
+ * chain its global setup module. Returns a new config object (shallow merge)
120
+ * without mutating the original.
121
+ *
122
+ * The `globalSetup` field is set to a `string` (or `string[]` if the user
123
+ * already has a global setup) referencing the Piwi global setup module,
124
+ * which registers the run on the server. The original setup path(s) are
125
+ * preserved and executed first.
126
+ *
127
+ * Playwright options required in `globalSetup` are forwarded via `PIWI_*`
128
+ * environment variables (see `applyOptionsToEnv` in `config.ts` for the
129
+ * supported set — `serverUrl`, `projectName`, `verbose`, `apiKey`,
130
+ * `username`, `password`, `environment`, `label`, `runLabel`).
131
+ *
132
+ * @param config The user's Playwright config.
133
+ * @param piwiOptions Optional Piwi Dashboard options (serverUrl, projectName, …).
134
+ */
135
+ declare function wrapConfig<T extends PlaywrightTestConfig>(config: T, piwiOptions?: PiwiDashboardOptions): T;
136
+
137
+ /**
138
+ * Piwi Dashboard Playwright reporter.
139
+ *
140
+ * Collects test results, metadata, performance metrics and trace files, then
141
+ * hands the collected run to a `RunSubmitter` which drives the JSON / multipart
142
+ * / streaming submit ladder. The reporter itself only owns the Playwright hooks
143
+ * and the running counters.
144
+ */
145
+ declare class PiwiDashboardReporter {
146
+ private options;
147
+ private testCases;
148
+ private startTime;
149
+ private playwrightVersion;
150
+ private readonly reporterVersion;
151
+ private totalTests;
152
+ private passedTests;
153
+ private failedTests;
154
+ private skippedTests;
155
+ private timedOutTests;
156
+ private didNotRunTests;
157
+ /** Full set of tests Playwright planned to run this shard (captured in `onBegin`). */
158
+ private plannedTests;
159
+ /** Ids of tests that actually reported via `onTestEnd`, to find the ones that never ran. */
160
+ private reportedTestIds;
161
+ private instanceId;
162
+ private runLabel;
163
+ private shardInfo;
164
+ private metadata;
165
+ private enabled;
166
+ private isFullRun;
167
+ private filterDetails;
168
+ private httpClient;
169
+ private uploader;
170
+ private fileHandler;
171
+ private metadataCollector;
172
+ private streamManager;
173
+ private recovery;
174
+ private submitter;
175
+ private readonly logger;
176
+ static wrapConfig: typeof wrapConfig;
177
+ static createGlobalSetup: typeof createGlobalSetup;
178
+ constructor(rawOptions?: Record<string, any>);
179
+ /** Playwright reporter hook: called once at the start of the test run */
180
+ onBegin(config: FullConfig, suite: Suite): void;
181
+ /** Playwright reporter hook: called when an individual test begins */
182
+ onTestBegin(test: TestCase, result: TestResult): void;
183
+ /** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
184
+ private setupSteps;
185
+ /** Playwright reporter hook: called when a step (including hook/fixture) begins */
186
+ onStepBegin(test: TestCase | undefined, _result: TestResult | undefined, step: any): void;
187
+ /** Playwright reporter hook: called when a step (including hook/fixture) ends */
188
+ onStepEnd(test: TestCase | undefined, _result: TestResult | undefined, step: any): void;
189
+ /** Playwright reporter hook: called when an individual test finishes */
190
+ onTestEnd(test: TestCase, result: TestResult): void;
191
+ /**
192
+ * Synthesize `didnotrun` cases for tests Playwright planned but never reported
193
+ * (no `onTestEnd`) — typically because `maxFailures` cut the run short. These
194
+ * carry no result, so they're emitted with zero duration and no error. In
195
+ * streaming mode they're queued as complete events so the pre-finish drain
196
+ * sends them alongside the rest.
197
+ */
198
+ private materializeUnrunTests;
199
+ /** Playwright reporter hook: called when the full test run finishes */
200
+ onEnd(result: FullResult): Promise<void>;
201
+ }
202
+
1
203
  /**
2
204
  * Public API of `@piwitests/reporter`.
3
205
  *
@@ -6,11 +208,5 @@
6
208
  * Everything under `internal/` is deliberately NOT re-exported here; if it isn't
7
209
  * exported from this file, it isn't part of the supported API.
8
210
  */
9
- import { PiwiDashboardReporter } from './public/reporter.js';
10
- export default PiwiDashboardReporter;
11
- export { PiwiDashboardReporter };
12
- export { wrapConfig } from './public/config-wrapper.js';
13
- export { createGlobalSetup } from './public/global-setup.js';
14
- export { piwiFixtures, extendPiwiFixtures } from './internal/capture/capture-fixtures.js';
15
- export type { PiwiFixtures } from './internal/capture/capture-fixtures.js';
16
- export type { PiwiDashboardOptions, PlaywrightTestConfig } from './public/options.js';
211
+
212
+ export { type PiwiDashboardOptions, PiwiDashboardReporter, createGlobalSetup, PiwiDashboardReporter as default, wrapConfig };