@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.
- package/README.md +3 -3
- package/dist/cli/index.js +1275 -469
- package/dist/global-setup-module.js +68 -24
- package/dist/index.d.ts +18 -5
- package/dist/index.js +418 -103
- package/dist/internal/capture/capture-fixtures.js +50 -7
- package/dist/internal/capture/locator-healing.d.ts +1 -7
- package/dist/internal/capture/locator-healing.js +0 -2
- package/dist/internal/capture/pick-on-failure.js +3 -3
- package/package.json +1 -1
- package/templates/skills/investigate-failure/SKILL.md +1 -1
- package/templates/skills/setup-piwi/SKILL.md +1 -1
- package/templates/skills/stabilize-flaky-tests/SKILL.md +30 -15
|
@@ -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)
|
|
254
|
-
* server
|
|
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
|
-
*
|
|
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
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (env.
|
|
481
|
-
if (env.
|
|
482
|
-
if (env.
|
|
483
|
-
if (env.
|
|
484
|
-
if (env.
|
|
485
|
-
if (env.
|
|
486
|
-
if (env.
|
|
487
|
-
if (env.
|
|
488
|
-
if (env.
|
|
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
|
|
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
|
-
/**
|
|
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`
|
|
307
|
-
* supported set
|
|
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;
|