@piwitests/reporter 0.14.0 → 0.16.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 CHANGED
@@ -92,6 +92,7 @@ export default defineConfig({
92
92
  | `collectScmInfo` | boolean | `true` | Auto-collect git commit, branch, author |
93
93
  | `collectCiInfo` | boolean | `true` | Auto-collect CI environment info |
94
94
  | `collectPerformanceMetrics` | boolean | `true` | Collect step timings, network requests and web vitals from the fixture |
95
+ | `outputFile` | string | — | Write a JSON file with the run URL/id/status so CI can consume it (see below) |
95
96
  | `apiKey` | string | — | API key for authentication (preferred for CI) |
96
97
  | `username` | string | — | Username for dashboard login (use `apiKey` instead when possible) |
97
98
  | `password` | string | — | Password for dashboard login (used with `username`) |
@@ -209,6 +210,51 @@ When `collectCiInfo` is enabled (default), the reporter auto-detects:
209
210
  - **Travis CI** — build number/URL, job number
210
211
  - **Azure Pipelines** — build number, build ID/URL, job name
211
212
 
213
+ ## Publishing the run URL to CI
214
+
215
+ After a run is submitted, the reporter surfaces the dashboard run URL so a later
216
+ CI step (a custom email, a Slack message, a deploy gate) can pick it up without
217
+ scraping the log. The URL is always printed as `View run: <url>`, and in
218
+ addition:
219
+
220
+ - **Any CI — JSON output file.** Set `outputFile` (or `PIWI_OUTPUT_FILE`) and the
221
+ reporter writes a small JSON file when the run lands:
222
+
223
+ ```json
224
+ { "runUrl": "https://piwi.example.com/test-runs/1234", "runId": 1234, "projectId": 5, "projectName": "checkout", "status": "passed", "ciBuildUrl": "https://ci.example.com/build/9" }
225
+ ```
226
+
227
+ Read it from any pipeline, e.g. `node -e "console.log(require('./piwi-run.json').runUrl)"`
228
+ (portable) or `cat piwi-run.json` and parse it in your email step. In Jenkins,
229
+ `def run = readJSON file: 'piwi-run.json'` then use `run.runUrl`.
230
+
231
+ - **GitHub Actions (automatic).** When `GITHUB_ACTIONS` is set, the reporter
232
+ appends step outputs to `$GITHUB_OUTPUT` (`piwi_run_url`, `piwi_run_id`,
233
+ `piwi_project_id`, `piwi_run_status`), writes a markdown link to the job
234
+ summary, and prints a `::notice::` annotation. Give the test step an `id` and a
235
+ downstream step can read it:
236
+
237
+ ```yaml
238
+ - id: tests
239
+ run: npx playwright test
240
+ - run: echo "Results at ${{ steps.tests.outputs.piwi_run_url }}"
241
+ ```
242
+
243
+ - **GitLab CI (automatic).** When `GITLAB_CI` is set, the reporter writes a
244
+ dotenv report (`piwi.env` by default, override with `PIWI_DOTENV_FILE`).
245
+ Declare it so later jobs inherit `$PIWI_RUN_URL`:
246
+
247
+ ```yaml
248
+ test:
249
+ script: npx playwright test
250
+ artifacts:
251
+ reports:
252
+ dotenv: piwi.env
253
+ email:
254
+ needs: [test]
255
+ script: ./send-email.sh "$PIWI_RUN_URL"
256
+ ```
257
+
212
258
  ## How It Works
213
259
 
214
260
  1. When tests start, the reporter creates a run on the server (streaming mode) or collects results locally (batch mode)
@@ -57,6 +57,7 @@ var DEFAULTS = {
57
57
  collectPerformanceMetrics: true,
58
58
  captureLocators: true,
59
59
  capturePageState: true,
60
+ captureServerTraces: true,
60
61
  streaming: true,
61
62
  streamingBatchSize: 5,
62
63
  streamingBatchDelay: 2e3,
@@ -83,8 +84,10 @@ var PIWI_ENV_KEYS = {
83
84
  uploadReport: "PIWI_UPLOAD_REPORT",
84
85
  captureLocators: "PIWI_CAPTURE_LOCATORS",
85
86
  capturePageState: "PIWI_CAPTURE_PAGE_STATE",
87
+ captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
86
88
  inspectOnFailure: "PIWI_INSPECT_ON_FAIL",
87
- pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL"
89
+ pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL",
90
+ outputFile: "PIWI_OUTPUT_FILE"
88
91
  };
89
92
  function readBool(val) {
90
93
  if (val === void 0) return void 0;
@@ -107,8 +110,10 @@ var ENV_FALLBACK_SPECS = [
107
110
  { option: "uploadReport", env: PIWI_ENV_KEYS.uploadReport, kind: "bool" },
108
111
  { option: "captureLocators", env: PIWI_ENV_KEYS.captureLocators, kind: "bool" },
109
112
  { option: "capturePageState", env: PIWI_ENV_KEYS.capturePageState, kind: "bool" },
113
+ { option: "captureServerTraces", env: PIWI_ENV_KEYS.captureServerTraces, kind: "bool" },
110
114
  { option: "inspectOnFailure", env: PIWI_ENV_KEYS.inspectOnFailure, kind: "bool" },
111
- { option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" }
115
+ { option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
116
+ { option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
112
117
  ];
113
118
  function resolveOptions(raw) {
114
119
  const env = process.env;
package/dist/index.d.ts CHANGED
@@ -41,6 +41,16 @@ interface PiwiDashboardOptions extends PlaywrightTestConfig {
41
41
  * also be forced off with `PIWI_CAPTURE_PAGE_STATE=false`.
42
42
  */
43
43
  capturePageState?: boolean;
44
+ /**
45
+ * Capture server-side spans for each API/document request the test makes,
46
+ * read from the `X-Piwi-Trace` response header emitted by a Piwi
47
+ * instrumentation plugin (e.g. `@piwitests/instrumentation`). The spans show
48
+ * up next to the network request in the dashboard and feed AI diagnosis. Free
49
+ * when no instrumentation is present (the header is simply absent). Defaults
50
+ * to `true`; automatically disabled when `collectPerformanceMetrics` is
51
+ * `false`. Can also be forced off with `PIWI_CAPTURE_SERVER_TRACES=false`.
52
+ */
53
+ captureServerTraces?: boolean;
44
54
  /**
45
55
  * Open Piwi's own failure-time overlay on the failing page — for inspecting
46
56
  * the page and picking a locator for any element (click an element → confirm
@@ -97,6 +107,15 @@ interface PiwiDashboardOptions extends PlaywrightTestConfig {
97
107
  tags?: string[];
98
108
  /** Additional custom metadata as key-value pairs */
99
109
  customData?: Record<string, unknown>;
110
+ /**
111
+ * Write a JSON file with the submitted run's dashboard URL, id, project id and
112
+ * status after the run lands, so a CI pipeline can consume it (e.g. feed the
113
+ * run URL into a custom email step). Any CI can read the file. GitHub Actions
114
+ * step outputs / job summary and GitLab dotenv reports are emitted
115
+ * automatically when running under those systems, regardless of this option.
116
+ * Can also be set with `PIWI_OUTPUT_FILE`.
117
+ */
118
+ outputFile?: string;
100
119
  /** Enable verbose logging for debugging. Defaults to `false`. */
101
120
  verbose?: boolean;
102
121
  }
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ __export(index_exports, {
40
40
  module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/public/reporter.ts
43
- var path12 = __toESM(require("path"));
43
+ var path13 = __toESM(require("path"));
44
44
 
45
45
  // src/internal/config/env.ts
46
46
  var DEFAULTS = {
@@ -53,6 +53,7 @@ var DEFAULTS = {
53
53
  collectPerformanceMetrics: true,
54
54
  captureLocators: true,
55
55
  capturePageState: true,
56
+ captureServerTraces: true,
56
57
  streaming: true,
57
58
  streamingBatchSize: 5,
58
59
  streamingBatchDelay: 2e3,
@@ -79,8 +80,10 @@ var PIWI_ENV_KEYS = {
79
80
  uploadReport: "PIWI_UPLOAD_REPORT",
80
81
  captureLocators: "PIWI_CAPTURE_LOCATORS",
81
82
  capturePageState: "PIWI_CAPTURE_PAGE_STATE",
83
+ captureServerTraces: "PIWI_CAPTURE_SERVER_TRACES",
82
84
  inspectOnFailure: "PIWI_INSPECT_ON_FAIL",
83
- pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL"
85
+ pickLocatorOnFailure: "PIWI_PICK_LOCATOR_ON_FAIL",
86
+ outputFile: "PIWI_OUTPUT_FILE"
84
87
  };
85
88
  function readBool(val) {
86
89
  if (val === void 0) return void 0;
@@ -103,8 +106,10 @@ var ENV_FALLBACK_SPECS = [
103
106
  { option: "uploadReport", env: PIWI_ENV_KEYS.uploadReport, kind: "bool" },
104
107
  { option: "captureLocators", env: PIWI_ENV_KEYS.captureLocators, kind: "bool" },
105
108
  { option: "capturePageState", env: PIWI_ENV_KEYS.capturePageState, kind: "bool" },
109
+ { option: "captureServerTraces", env: PIWI_ENV_KEYS.captureServerTraces, kind: "bool" },
106
110
  { option: "inspectOnFailure", env: PIWI_ENV_KEYS.inspectOnFailure, kind: "bool" },
107
- { option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" }
111
+ { option: "pickLocatorOnFailure", env: PIWI_ENV_KEYS.pickLocatorOnFailure, kind: "bool" },
112
+ { option: "outputFile", env: PIWI_ENV_KEYS.outputFile, kind: "string" }
108
113
  ];
109
114
  function resolveOptions(raw) {
110
115
  const env = process.env;
@@ -139,6 +144,9 @@ function applyOptionsToEnv(options) {
139
144
  if (options.capturePageState === false || options.collectPerformanceMetrics === false)
140
145
  env[PIWI_ENV_KEYS.capturePageState] = "false";
141
146
  else if (options.capturePageState === true) env[PIWI_ENV_KEYS.capturePageState] = "true";
147
+ if (options.captureServerTraces === false || options.collectPerformanceMetrics === false)
148
+ env[PIWI_ENV_KEYS.captureServerTraces] = "false";
149
+ else if (options.captureServerTraces === true) env[PIWI_ENV_KEYS.captureServerTraces] = "true";
142
150
  if (options.inspectOnFailure !== void 0) env[PIWI_ENV_KEYS.inspectOnFailure] = String(options.inspectOnFailure);
143
151
  if (options.pickLocatorOnFailure !== void 0)
144
152
  env[PIWI_ENV_KEYS.pickLocatorOnFailure] = String(options.pickLocatorOnFailure);
@@ -353,6 +361,7 @@ function toWireTestCase(tc) {
353
361
  location: rest.location,
354
362
  status: rest.status,
355
363
  duration: rest.duration,
364
+ timeout: rest.timeout ?? null,
356
365
  error: rest.error,
357
366
  retries: rest.retries,
358
367
  workerIndex: rest.workerIndex ?? null,
@@ -407,11 +416,6 @@ function serializeRun(payload, opts) {
407
416
  return body;
408
417
  }
409
418
 
410
- // src/internal/support/run-url.ts
411
- function runUrl(serverUrl, runId) {
412
- return `${serverUrl.replace(/\/+$/, "")}/test-runs/${runId}`;
413
- }
414
-
415
419
  // src/internal/submit/uploader.ts
416
420
  var Uploader = class {
417
421
  /**
@@ -434,7 +438,6 @@ var Uploader = class {
434
438
  this.logger.info(`Successfully uploaded test results`);
435
439
  if (response.testRunId) {
436
440
  this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
437
- this.logger.info(`View run: ${runUrl(this.httpClient.baseUrl, response.testRunId)}`);
438
441
  }
439
442
  return response;
440
443
  }
@@ -450,7 +453,6 @@ var Uploader = class {
450
453
  this.logger.info(`Successfully uploaded test results with files`);
451
454
  if (response.testRunId) {
452
455
  this.logger.info(`Test Run ID: ${response.testRunId}, Project ID: ${response.projectId}`);
453
- this.logger.info(`View run: ${runUrl(this.httpClient.baseUrl, response.testRunId)}`);
454
456
  }
455
457
  if (response.reports) {
456
458
  for (const r of response.reports) this.logger.info(`${r.label}: ${r.path}`);
@@ -1157,6 +1159,11 @@ function readSetupInfo(projectName) {
1157
1159
  return null;
1158
1160
  }
1159
1161
 
1162
+ // src/internal/support/run-url.ts
1163
+ function runUrl(serverUrl, runId) {
1164
+ return `${serverUrl.replace(/\/+$/, "")}/test-runs/${runId}`;
1165
+ }
1166
+
1160
1167
  // src/internal/streaming/stream-manager.ts
1161
1168
  var StreamManager = class {
1162
1169
  /**
@@ -2001,6 +2008,90 @@ function buildErrorText(result) {
2001
2008
  return text;
2002
2009
  }
2003
2010
 
2011
+ // src/internal/support/ci-output.ts
2012
+ var fs12 = __toESM(require("fs"));
2013
+ var path12 = __toESM(require("path"));
2014
+ function emitRunOutputs(output, logger, outputFile, env = process.env) {
2015
+ logger.info(`View run: ${output.runUrl}`);
2016
+ if (outputFile) writeOutputFile(outputFile, output, logger);
2017
+ if (env.GITHUB_ACTIONS) emitGitHubActions(output, env, logger);
2018
+ if (env.GITLAB_CI) emitGitLabDotenv(output, env, logger);
2019
+ }
2020
+ function writeOutputFile(file, output, logger) {
2021
+ try {
2022
+ const dir = path12.dirname(file);
2023
+ if (dir && dir !== ".") fs12.mkdirSync(dir, { recursive: true });
2024
+ fs12.writeFileSync(
2025
+ file,
2026
+ JSON.stringify(
2027
+ {
2028
+ runUrl: output.runUrl,
2029
+ runId: output.runId,
2030
+ projectId: output.projectId ?? null,
2031
+ projectName: output.projectName,
2032
+ status: output.status,
2033
+ ciBuildUrl: output.ciBuildUrl ?? null
2034
+ },
2035
+ null,
2036
+ 2
2037
+ ) + "\n"
2038
+ );
2039
+ logger.info(`Wrote run output to ${file}`);
2040
+ } catch (error) {
2041
+ logger.warn(`Failed to write run output file '${file}': ${errorMessage(error)}`);
2042
+ }
2043
+ }
2044
+ function emitGitHubActions(output, env, logger) {
2045
+ const pairs = [
2046
+ ["piwi_run_url", output.runUrl],
2047
+ ["piwi_run_id", String(output.runId)],
2048
+ ["piwi_run_status", output.status]
2049
+ ];
2050
+ if (output.projectId != null) pairs.push(["piwi_project_id", String(output.projectId)]);
2051
+ if (env.GITHUB_OUTPUT) {
2052
+ appendFileLines(
2053
+ env.GITHUB_OUTPUT,
2054
+ pairs.map(([k, v]) => `${k}=${v}`),
2055
+ logger,
2056
+ "step output"
2057
+ );
2058
+ }
2059
+ if (env.GITHUB_STEP_SUMMARY) {
2060
+ appendFileLines(
2061
+ env.GITHUB_STEP_SUMMARY,
2062
+ ["### Piwi test run", "", `[View run](${output.runUrl}) \u2014 **${output.status}**`, ""],
2063
+ logger,
2064
+ "step summary"
2065
+ );
2066
+ }
2067
+ process.stdout.write(`::notice title=Piwi test run::${output.runUrl}
2068
+ `);
2069
+ }
2070
+ function emitGitLabDotenv(output, env, logger) {
2071
+ const file = env.PIWI_DOTENV_FILE || "piwi.env";
2072
+ const lines = [`PIWI_RUN_URL=${output.runUrl}`, `PIWI_RUN_ID=${output.runId}`, `PIWI_RUN_STATUS=${output.status}`];
2073
+ if (output.projectId != null) lines.push(`PIWI_PROJECT_ID=${output.projectId}`);
2074
+ if (output.ciBuildUrl) lines.push(`PIWI_CI_BUILD_URL=${output.ciBuildUrl}`);
2075
+ try {
2076
+ fs12.writeFileSync(file, lines.join("\n") + "\n");
2077
+ logger.info(`Wrote GitLab dotenv report to ${file} (declare it as artifacts:reports:dotenv)`);
2078
+ } catch (error) {
2079
+ logger.warn(`Failed to write GitLab dotenv file '${file}': ${errorMessage(error)}`);
2080
+ }
2081
+ }
2082
+ function appendFileLines(file, lines, logger, label) {
2083
+ try {
2084
+ fs12.appendFileSync(file, lines.join("\n") + "\n");
2085
+ } catch (error) {
2086
+ logger.warn(`Failed to write GitHub Actions ${label}: ${errorMessage(error)}`);
2087
+ }
2088
+ }
2089
+ function ciBuildUrlFromMetadata(metadata) {
2090
+ const ci = metadata?.ci;
2091
+ if (!ci) return void 0;
2092
+ return ci.buildUrl || ci.pipelineUrl || ci.jobUrl || void 0;
2093
+ }
2094
+
2004
2095
  // src/internal/submit/run-submitter.ts
2005
2096
  var RunSubmitter = class {
2006
2097
  /**
@@ -2045,13 +2136,29 @@ var RunSubmitter = class {
2045
2136
  this.logger.error(`Authentication failed: ${errorMessage(error)}`);
2046
2137
  throw error;
2047
2138
  }
2139
+ let outcome = { done: false, output: null };
2048
2140
  if (sm?.enabled && sm?.runId != null) {
2049
- if (await this.tryFinishStreaming(run, overallStatus, duration, auth)) return;
2141
+ outcome = await this.tryFinishStreaming(run, overallStatus, duration, auth);
2142
+ }
2143
+ if (!outcome.done && (this.hasReports(run) || run.options.uploadTraces)) {
2144
+ outcome = await this.tryUploadWithFiles(run, overallStatus, duration, auth);
2050
2145
  }
2051
- if (this.hasReports(run) || run.options.uploadTraces) {
2052
- if (await this.tryUploadWithFiles(run, overallStatus, duration, auth)) return;
2146
+ if (!outcome.done) {
2147
+ outcome = await this.tryUploadJSON(run, overallStatus, duration, auth);
2053
2148
  }
2054
- await this.tryUploadJSON(run, overallStatus, duration, auth);
2149
+ if (outcome.output) emitRunOutputs(outcome.output, this.logger, run.options.outputFile);
2150
+ }
2151
+ /** Assemble a CI-facing run output, or `null` when the server returned no run id. */
2152
+ buildOutput(runId, projectId, run, status) {
2153
+ if (runId == null) return null;
2154
+ return {
2155
+ runUrl: runUrl(this.httpClient.baseUrl, runId),
2156
+ runId,
2157
+ projectId,
2158
+ projectName: run.options.projectName,
2159
+ status,
2160
+ ciBuildUrl: ciBuildUrlFromMetadata(run.metadata)
2161
+ };
2055
2162
  }
2056
2163
  hasReports(run) {
2057
2164
  return !!run.options.uploadReport || (run.options.reports?.length ?? 0) > 0;
@@ -2122,9 +2229,6 @@ var RunSubmitter = class {
2122
2229
  }
2123
2230
  await this.httpClient.postJSON(`/api/test-runs/${sm.runId}/finish`, finishBody, auth);
2124
2231
  this.logger.info(`Successfully finalized streaming run #${sm.runId}`);
2125
- if (run.options.serverUrl) {
2126
- this.logger.info(`View run: ${runUrl(run.options.serverUrl, sm.runId)}`);
2127
- }
2128
2232
  this.recovery.clear();
2129
2233
  if (this.hasReports(run)) {
2130
2234
  try {
@@ -2139,22 +2243,22 @@ var RunSubmitter = class {
2139
2243
  this.logger.warn(`Failed to upload reports for streaming run: ${errorMessage(error)}`);
2140
2244
  }
2141
2245
  }
2142
- return true;
2246
+ return { done: true, output: this.buildOutput(sm.runId, void 0, run, overallStatus) };
2143
2247
  } catch (error) {
2144
2248
  this.logger.warn(`Failed to finalize streaming run: ${errorMessage(error)}`);
2145
2249
  this.logger.info("Falling back to batch upload...");
2146
- return false;
2250
+ return { done: false, output: null };
2147
2251
  }
2148
2252
  }
2149
2253
  async tryUploadWithFiles(run, overallStatus, duration, auth) {
2150
2254
  try {
2151
- await this.uploader.uploadWithFiles(
2255
+ const response = await this.uploader.uploadWithFiles(
2152
2256
  this.buildRunPayload(run, overallStatus, duration),
2153
2257
  this.reportOptions(run),
2154
2258
  auth
2155
2259
  );
2156
2260
  this.recovery.clear();
2157
- return true;
2261
+ return { done: true, output: this.buildOutput(response?.testRunId, response?.projectId, run, overallStatus) };
2158
2262
  } catch (error) {
2159
2263
  if (error instanceof HttpError && error.status === 401 && !auth) {
2160
2264
  this.logAuthRequired(run.options.serverUrl);
@@ -2162,14 +2266,15 @@ var RunSubmitter = class {
2162
2266
  }
2163
2267
  this.logger.warn(`Failed to upload with files: ${errorMessage(error)}`);
2164
2268
  this.logger.info("Falling back to JSON upload...");
2165
- return false;
2269
+ return { done: false, output: null };
2166
2270
  }
2167
2271
  }
2168
2272
  async tryUploadJSON(run, overallStatus, duration, auth) {
2169
2273
  const payload = this.buildRunPayload(run, overallStatus, duration);
2170
2274
  try {
2171
- await this.uploader.uploadJSON(payload, auth);
2275
+ const response = await this.uploader.uploadJSON(payload, auth);
2172
2276
  this.recovery.clear();
2277
+ return { done: true, output: this.buildOutput(response?.testRunId, response?.projectId, run, overallStatus) };
2173
2278
  } catch (error) {
2174
2279
  if (error instanceof HttpError && error.status === 401 && !auth) {
2175
2280
  this.logAuthRequired(run.options.serverUrl);
@@ -2180,6 +2285,7 @@ var RunSubmitter = class {
2180
2285
  `Saved a local recovery copy \u2014 it will be uploaded automatically on your next test run. If this keeps happening, check that serverUrl (${run.options.serverUrl ?? "not set"}) is correct and reachable.`
2181
2286
  );
2182
2287
  this.recovery.save(serializeRun(payload, { includeTestCases: true }));
2288
+ return { done: true, output: null };
2183
2289
  }
2184
2290
  }
2185
2291
  /** Log one actionable line explaining how to fix a 401 caused by a missing credential. */
@@ -2192,7 +2298,7 @@ var RunSubmitter = class {
2192
2298
 
2193
2299
  // src/public/reporter.ts
2194
2300
  function testLocation(test) {
2195
- const relativeFilePath = path12.relative(process.cwd(), test.location.file).split(path12.sep).join("/");
2301
+ const relativeFilePath = path13.relative(process.cwd(), test.location.file).split(path13.sep).join("/");
2196
2302
  return `${relativeFilePath}:${test.location.line}:${test.location.column}`;
2197
2303
  }
2198
2304
  var PiwiDashboardReporter = class {
@@ -2373,6 +2479,9 @@ var PiwiDashboardReporter = class {
2373
2479
  location: testLocation(test),
2374
2480
  status,
2375
2481
  duration: result.duration,
2482
+ // Effective per-test timeout (reflects project config + describe-level
2483
+ // overrides). `0` means unbounded; kept as-is so the dashboard can flag it.
2484
+ timeout: test.timeout ?? null,
2376
2485
  error: buildErrorText(result),
2377
2486
  retries: result.retry,
2378
2487
  workerIndex: workerIndexOf(result),
@@ -2448,6 +2557,7 @@ var PiwiDashboardReporter = class {
2448
2557
  location: testLocation(test),
2449
2558
  status: "didnotrun",
2450
2559
  duration: 0,
2560
+ timeout: test.timeout ?? null,
2451
2561
  error: null,
2452
2562
  retries: 0,
2453
2563
  workerIndex: null,
@@ -2500,7 +2610,7 @@ var PiwiDashboardReporter = class {
2500
2610
  var import_node_zlib = require("zlib");
2501
2611
 
2502
2612
  // src/internal/capture/locator-healing.ts
2503
- var path13 = __toESM(require("path"));
2613
+ var path14 = __toESM(require("path"));
2504
2614
 
2505
2615
  // ../packages/core/src/locator-fingerprint.ts
2506
2616
  var PRESENT_SIMILARITY = 0.8;
@@ -3057,10 +3167,10 @@ function captureCallerLocation(stack = new Error().stack ?? "") {
3057
3167
  }
3058
3168
  let rel = file;
3059
3169
  try {
3060
- rel = path13.relative(process.cwd(), file);
3170
+ rel = path14.relative(process.cwd(), file);
3061
3171
  } catch {
3062
3172
  }
3063
- rel = rel.split(path13.sep).join("/");
3173
+ rel = rel.split(path14.sep).join("/");
3064
3174
  if (rel.startsWith("./")) rel = rel.slice(2);
3065
3175
  return `${rel}:${m[3]}:${m[4]}`;
3066
3176
  }
@@ -3103,7 +3213,7 @@ function inspectionGateFromTestInfo(testInfo, enabled = process.env.PIWI_INSPECT
3103
3213
  }
3104
3214
 
3105
3215
  // src/internal/capture/pick-on-failure.ts
3106
- var path14 = __toESM(require("path"));
3216
+ var path15 = __toESM(require("path"));
3107
3217
  var ANSI_RE = /\[[0-9;]*m/g;
3108
3218
  function endOfString(s, start) {
3109
3219
  const q = s[start];
@@ -3197,7 +3307,7 @@ ${err.stack ?? ""}`.replace(ANSI_RE, "");
3197
3307
  const parsed = parseLeafLocatorExpression(line[1].trim());
3198
3308
  if (!parsed) continue;
3199
3309
  const loc = err.location;
3200
- const location = loc ? `${path14.relative(process.cwd(), loc.file).split(path14.sep).join("/")}:${loc.line}:${loc.column}` : null;
3310
+ const location = loc ? `${path15.relative(process.cwd(), loc.file).split(path15.sep).join("/")}:${loc.line}:${loc.column}` : null;
3201
3311
  return { method: parsed.method, args: parsed.args, location };
3202
3312
  }
3203
3313
  return null;
@@ -4528,6 +4638,13 @@ function instrumentPage(page) {
4528
4638
  } catch {
4529
4639
  }
4530
4640
  }
4641
+ const traceHeader = headers["x-piwi-trace"];
4642
+ if (traceHeader && process.env.PIWI_CAPTURE_SERVER_TRACES !== "false") {
4643
+ try {
4644
+ entry.serverTraces = JSON.parse((0, import_node_zlib.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf-8"));
4645
+ } catch {
4646
+ }
4647
+ }
4531
4648
  }
4532
4649
  sink.networkRequests.push(entry);
4533
4650
  } catch {
@@ -2085,6 +2085,13 @@ function instrumentPage(page) {
2085
2085
  } catch {
2086
2086
  }
2087
2087
  }
2088
+ const traceHeader = headers["x-piwi-trace"];
2089
+ if (traceHeader && process.env.PIWI_CAPTURE_SERVER_TRACES !== "false") {
2090
+ try {
2091
+ entry.serverTraces = JSON.parse((0, import_node_zlib.gunzipSync)(Buffer.from(traceHeader, "base64")).toString("utf-8"));
2092
+ } catch {
2093
+ }
2094
+ }
2088
2095
  }
2089
2096
  sink.networkRequests.push(entry);
2090
2097
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piwitests/reporter",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Playwright reporter for sending test results to Piwi Dashboard",
5
5
  "url": "https://github.com/PiwiTests/platform",
6
6
  "homepage": "https://piwitests.github.io",