@piwitests/reporter 0.41.0 → 0.43.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.
@@ -90,6 +90,7 @@ var DEFAULTS = {
90
90
  streamingBatchSize: 5,
91
91
  streamingBatchDelay: 2e3,
92
92
  maxStreamBufferBytes: 100 * 1024 * 1024,
93
+ submitTimeout: 15 * 60 * 1e3,
93
94
  failOnFlakyTests: false,
94
95
  username: null,
95
96
  password: null,
@@ -111,6 +112,7 @@ var PIWI_ENV_KEYS = {
111
112
  streamingBatchSize: "PIWI_STREAMING_BATCH_SIZE",
112
113
  streamingBatchDelay: "PIWI_STREAMING_BATCH_DELAY",
113
114
  maxStreamBufferBytes: "PIWI_MAX_STREAM_BUFFER_BYTES",
115
+ submitTimeout: "PIWI_SUBMIT_TIMEOUT",
114
116
  liveFileUploads: "PIWI_LIVE_FILE_UPLOADS",
115
117
  failOnFlakyTests: "PIWI_FAIL_ON_FLAKY_TESTS",
116
118
  uploadTraces: "PIWI_UPLOAD_TRACES",
@@ -137,6 +139,7 @@ var PIWI_ENV_KEYS = {
137
139
  aiScreenshotFallback: "PIWI_AI_SCREENSHOT_FALLBACK"
138
140
  };
139
141
  var PIWI_DESKTOP_CONFIG_ENV = "PIWI_DESKTOP_CONFIG";
142
+ var PIWI_SHARD_ENV = "PIWI_SHARD";
140
143
  function readBool(val) {
141
144
  if (val === void 0) return void 0;
142
145
  return val === "true";
@@ -155,6 +158,7 @@ var ENV_FALLBACK_SPECS = [
155
158
  { option: "streamingBatchSize", env: PIWI_ENV_KEYS.streamingBatchSize, kind: "number" },
156
159
  { option: "streamingBatchDelay", env: PIWI_ENV_KEYS.streamingBatchDelay, kind: "number" },
157
160
  { option: "maxStreamBufferBytes", env: PIWI_ENV_KEYS.maxStreamBufferBytes, kind: "number" },
161
+ { option: "submitTimeout", env: PIWI_ENV_KEYS.submitTimeout, kind: "number" },
158
162
  { option: "liveFileUploads", env: PIWI_ENV_KEYS.liveFileUploads, kind: "bool" },
159
163
  { option: "failOnFlakyTests", env: PIWI_ENV_KEYS.failOnFlakyTests, kind: "bool" },
160
164
  { option: "uploadTraces", env: PIWI_ENV_KEYS.uploadTraces, kind: "bool" },
@@ -250,13 +254,17 @@ var HttpClient = class {
250
254
  /**
251
255
  * @param serverUrl Base URL of the Piwi Dashboard server (e.g. `http://localhost:3000`).
252
256
  * @param logger Prefixed logger for verbose diagnostics.
253
- * @param timeout Socket inactivity timeout in ms (default 30s). A hung
254
- * server now fails fast instead of stalling the reporter.
257
+ * @param timeout Socket inactivity timeout in ms (default 30s), so a hung
258
+ * server fails fast rather than stalling the reporter.
255
259
  */
256
260
  constructor(serverUrl, logger = new Logger(), timeout = 3e4) {
257
261
  this.serverUrl = serverUrl;
258
262
  this.logger = logger;
259
263
  this.timeout = timeout;
264
+ /** Requests in flight, so `close` can abort them. */
265
+ this.inFlight = /* @__PURE__ */ new Set();
266
+ /** Set by `close`: every later request fails with this reason. */
267
+ this.closedReason = null;
260
268
  this.warnIfInsecureTransport();
261
269
  }
262
270
  /**
@@ -267,7 +275,7 @@ var HttpClient = class {
267
275
  * endpoint exposes the key and payloads on the wire. Loopback (localhost /
268
276
  * 127.0.0.1 / ::1) is exempt: that traffic never leaves the machine. This
269
277
  * warns rather than refuses so internal HTTP deployments (e.g. behind a VPN)
270
- * still work, but the exposure is no longer silent.
278
+ * work, while the exposure is still reported.
271
279
  */
272
280
  warnIfInsecureTransport() {
273
281
  let url;
@@ -288,6 +296,16 @@ var HttpClient = class {
288
296
  get baseUrl() {
289
297
  return this.serverUrl;
290
298
  }
299
+ /** Whether `close` was called. */
300
+ get closed() {
301
+ return this.closedReason !== null;
302
+ }
303
+ /** Abort every request in flight and fail every later one with `reason`. */
304
+ close(reason) {
305
+ this.closedReason = reason;
306
+ for (const req of this.inFlight) req.destroy(new Error(reason));
307
+ this.inFlight.clear();
308
+ }
291
309
  /**
292
310
  * Resolve an auth credential: prefer `apiKey`, fall back to `username`/`password` login,
293
311
  * or return `null` when neither is configured.
@@ -408,6 +426,10 @@ var HttpClient = class {
408
426
  /** Unified request core: transport, headers, auth, response accumulation, timeout. */
409
427
  request(method, pathname, opts) {
410
428
  return new Promise((resolve2, reject) => {
429
+ if (this.closedReason !== null) {
430
+ reject(new Error(this.closedReason));
431
+ return;
432
+ }
411
433
  const url = new import_node_url.URL(pathname, this.serverUrl);
412
434
  const transport = url.protocol === "https:" ? https : http;
413
435
  const headers = { ...opts.headers };
@@ -436,11 +458,14 @@ var HttpClient = class {
436
458
  });
437
459
  }
438
460
  );
461
+ this.inFlight.add(req);
462
+ req.on("close", () => this.inFlight.delete(req));
439
463
  req.on("error", reject);
440
464
  req.setTimeout(this.timeout, () => {
441
465
  req.destroy(new Error(`Request to ${pathname} timed out after ${this.timeout}ms`));
442
466
  });
443
467
  if (opts.form) {
468
+ opts.form.on("error", (error) => req.destroy(error));
444
469
  opts.form.pipe(req);
445
470
  } else if (opts.body !== void 0) {
446
471
  req.write(opts.body);
@@ -472,22 +497,44 @@ function computeInstanceId(projectName, runLabel) {
472
497
  }
473
498
 
474
499
  // src/internal/support/ci.ts
475
- function detectCiRunLabel() {
476
- const env = process.env;
477
- if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) return env.GITHUB_RUN_ID;
478
- if (env.GITLAB_CI && env.CI_PIPELINE_ID) return env.CI_PIPELINE_ID;
479
- if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return env.CIRCLE_WORKFLOW_ID;
480
- if (env.TRAVIS && env.TRAVIS_BUILD_ID) return env.TRAVIS_BUILD_ID;
481
- if (env.TF_BUILD && env.BUILD_BUILDID) return env.BUILD_BUILDID;
482
- if (env.JENKINS_URL && env.BUILD_ID) return env.BUILD_ID;
483
- if (env.BUILDKITE_BUILD_ID) return env.BUILDKITE_BUILD_ID;
484
- if (env.TEAMCITY_BUILD_ID) return env.TEAMCITY_BUILD_ID;
485
- if (env.BITBUCKET_BUILD_NUMBER) return env.BITBUCKET_BUILD_NUMBER;
486
- if (env.SEMAPHORE_WORKFLOW_ID) return env.SEMAPHORE_WORKFLOW_ID;
487
- if (env.APPVEYOR_BUILD_ID) return env.APPVEYOR_BUILD_ID;
488
- if (env.DRONE_BUILD_NUMBER) return env.DRONE_BUILD_NUMBER;
500
+ function detectCiIds(env) {
501
+ if (env.GITHUB_ACTIONS && env.GITHUB_RUN_ID) {
502
+ const run = env.GITHUB_RUN_ATTEMPT ? `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` : env.GITHUB_RUN_ID;
503
+ return { run, job: env.GITHUB_JOB || null };
504
+ }
505
+ if (env.GITLAB_CI && env.CI_PIPELINE_ID) return { run: env.CI_PIPELINE_ID, job: env.CI_JOB_ID || null };
506
+ if (env.CIRCLECI && env.CIRCLE_WORKFLOW_ID) return { run: env.CIRCLE_WORKFLOW_ID, job: env.CIRCLE_BUILD_NUM || null };
507
+ if (env.TRAVIS && env.TRAVIS_BUILD_ID) return { run: env.TRAVIS_BUILD_ID, job: env.TRAVIS_JOB_ID || null };
508
+ if (env.TF_BUILD && env.BUILD_BUILDID) return { run: env.BUILD_BUILDID, job: env.SYSTEM_JOBID || null };
509
+ if (env.JENKINS_URL && env.BUILD_ID) return { run: env.BUILD_ID, job: null };
510
+ if (env.BUILDKITE_BUILD_ID) return { run: env.BUILDKITE_BUILD_ID, job: env.BUILDKITE_JOB_ID || null };
511
+ if (env.TEAMCITY_BUILD_ID) return { run: env.TEAMCITY_BUILD_ID, job: null };
512
+ if (env.BITBUCKET_BUILD_NUMBER) return { run: env.BITBUCKET_BUILD_NUMBER, job: env.BITBUCKET_STEP_UUID || null };
513
+ if (env.SEMAPHORE_WORKFLOW_ID) return { run: env.SEMAPHORE_WORKFLOW_ID, job: env.SEMAPHORE_JOB_ID || null };
514
+ if (env.APPVEYOR_BUILD_ID) return { run: env.APPVEYOR_BUILD_ID, job: env.APPVEYOR_JOB_ID || null };
515
+ if (env.DRONE_BUILD_NUMBER) return { run: env.DRONE_BUILD_NUMBER, job: null };
489
516
  return null;
490
517
  }
518
+ function resolveRunLabel(configured, sharded) {
519
+ if (configured) return configured;
520
+ const ci = detectCiIds(process.env);
521
+ if (!ci) return null;
522
+ return !sharded && ci.job ? `${ci.run}|${ci.job}` : ci.run;
523
+ }
524
+
525
+ // src/internal/support/shard-info.ts
526
+ function parseShardSpec(raw) {
527
+ const match = raw?.trim().match(/^(\d+)\s*\/\s*(\d+)$/);
528
+ if (!match) return null;
529
+ const current = Number(match[1]);
530
+ const total = Number(match[2]);
531
+ return current >= 1 && current <= total && total > 1 ? { current, total } : null;
532
+ }
533
+ function resolveShardInfo(config, env = process.env) {
534
+ const pwShard = config.shard;
535
+ if (pwShard && pwShard.total > 1) return { current: pwShard.current, total: pwShard.total };
536
+ return parseShardSpec(env[PIWI_SHARD_ENV]);
537
+ }
491
538
 
492
539
  // src/internal/collect/metadata-collector.ts
493
540
  var import_node_child_process = require("child_process");
@@ -687,10 +734,7 @@ function createGlobalSetup(options, userSetup) {
687
734
  const httpClient = new HttpClient(opts.serverUrl, logger);
688
735
  try {
689
736
  const auth = await httpClient.resolveAuth(opts);
690
- const runLabel = opts.runLabel || detectCiRunLabel();
691
- const pwShard = config.shard;
692
- const shardIndex = pwShard?.current;
693
- const shardTotal = pwShard?.total;
737
+ const shard = resolveShardInfo(config);
694
738
  const response = await httpClient.postJSON(
695
739
  "/api/test-runs/setup",
696
740
  {
@@ -700,9 +744,9 @@ function createGlobalSetup(options, userSetup) {
700
744
  label: opts.label || null,
701
745
  keep: opts.keep === true,
702
746
  startTime: (/* @__PURE__ */ new Date()).toISOString(),
703
- instanceId: computeInstanceId(opts.projectName, runLabel),
704
- shardIndex,
705
- shardTotal
747
+ instanceId: computeInstanceId(opts.projectName, resolveRunLabel(opts.runLabel, shard !== null)),
748
+ shardIndex: shard?.current,
749
+ shardTotal: shard?.total
706
750
  },
707
751
  auth
708
752
  );
package/dist/index.d.ts CHANGED
@@ -22,6 +22,15 @@ interface PiwiDashboardOptions {
22
22
  projectName?: string;
23
23
  /** Optional description of the project */
24
24
  projectDescription?: string;
25
+ /**
26
+ * Time budget (ms) for the reporter's end-of-run work: delivering the last
27
+ * live events, uploading the remaining traces and attachments, and submitting
28
+ * the run. When it runs out, the reporter stops waiting for the dashboard and
29
+ * saves the run's results locally; the next run for the project uploads them,
30
+ * without traces or attachments. Defaults to `900000` (15 minutes). Set to `0`
31
+ * for no limit. Can also be set with `PIWI_SUBMIT_TIMEOUT`.
32
+ */
33
+ submitTimeout?: number;
25
34
  /** Upload trace files to the dashboard. Defaults to `true`. */
26
35
  uploadTraces?: boolean;
27
36
  /** Upload the Playwright HTML report. Defaults to `true`. */
@@ -189,7 +198,12 @@ interface PiwiDashboardOptions {
189
198
  dir?: string;
190
199
  label?: string;
191
200
  }>;
192
- /** Stable label that ties shards together (e.g. CI run ID). Auto-detected from CI env; override if needed. */
201
+ /**
202
+ * Stable label that ties shards together (e.g. CI run ID). Auto-detected from
203
+ * CI env, with the CI job's id added for a run that is not sharded; a label
204
+ * set here is used as it is. Override it when your CI is not detected, or to
205
+ * keep matrix legs that report to one project apart.
206
+ */
193
207
  runLabel?: string;
194
208
  /** Deployment environment for this run, e.g. `"production"`, `"staging"`, `"integration"` */
195
209
  environment?: string;
@@ -303,9 +317,8 @@ declare function createGlobalSetup(options?: PiwiDashboardOptions, userSetup?: (
303
317
  * executed after it.
304
318
  *
305
319
  * Playwright options required in `globalSetup` are forwarded via `PIWI_*`
306
- * environment variables (see `applyOptionsToEnv` in `config.ts` for the
307
- * supported set — `serverUrl`, `projectName`, `verbose`, `apiKey`,
308
- * `username`, `password`, `environment`, `label`, `runLabel`).
320
+ * environment variables (see `applyOptionsToEnv` in `internal/config/env.ts`
321
+ * for the supported set).
309
322
  *
310
323
  * The top-level `use` block's `screenshot` and `trace` are defaulted to
311
324
  * `'only-on-failure'` / `'retain-on-failure'` when unset so failure evidence is
@@ -345,8 +358,8 @@ declare class PiwiDashboardReporter {
345
358
  private reportedTestIds;
346
359
  /** Per-test attempt history, keyed by `test.id`; snapshotted onto every attempt's payload. */
347
360
  private attemptsByTest;
361
+ /** Derived in `onBegin`, once the shard is known. */
348
362
  private instanceId;
349
- private runLabel;
350
363
  private shardInfo;
351
364
  private metadata;
352
365
  private enabled;