@piwitests/reporter 0.5.0 → 0.7.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 (76) hide show
  1. package/README.md +37 -26
  2. package/dist/global-setup-module.js +2 -2
  3. package/dist/index.d.ts +13 -4
  4. package/dist/index.js +19 -5
  5. package/dist/internal/capture/attachments.d.ts +19 -0
  6. package/dist/internal/capture/attachments.js +22 -0
  7. package/dist/internal/capture/capture-fixtures.d.ts +41 -0
  8. package/dist/internal/capture/capture-fixtures.js +562 -0
  9. package/dist/{locator-healing.d.ts → internal/capture/locator-healing.d.ts} +47 -1
  10. package/dist/{locator-healing.js → internal/capture/locator-healing.js} +113 -29
  11. package/dist/internal/collect/error-text.d.ts +18 -0
  12. package/dist/internal/collect/error-text.js +79 -0
  13. package/dist/{metadata-collector.d.ts → internal/collect/metadata-collector.d.ts} +2 -2
  14. package/dist/{metadata-collector.js → internal/collect/metadata-collector.js} +12 -9
  15. package/dist/{skip-classify.d.ts → internal/collect/skip-classify.d.ts} +1 -1
  16. package/dist/{step-analyzer.d.ts → internal/collect/step-analyzer.d.ts} +6 -0
  17. package/dist/{step-analyzer.js → internal/collect/step-analyzer.js} +7 -2
  18. package/dist/internal/config/env.d.ts +45 -0
  19. package/dist/{config.js → internal/config/env.js} +50 -35
  20. package/dist/internal/files/compression.js +69 -0
  21. package/dist/{file-handler.d.ts → internal/files/file-handler.d.ts} +4 -4
  22. package/dist/{file-handler.js → internal/files/file-handler.js} +61 -29
  23. package/dist/{crash-recovery.d.ts → internal/streaming/crash-recovery.d.ts} +5 -5
  24. package/dist/{crash-recovery.js → internal/streaming/crash-recovery.js} +9 -8
  25. package/dist/{stream-buffer.d.ts → internal/streaming/stream-buffer.d.ts} +2 -2
  26. package/dist/{stream-buffer.js → internal/streaming/stream-buffer.js} +5 -5
  27. package/dist/{stream-manager.d.ts → internal/streaming/stream-manager.d.ts} +8 -8
  28. package/dist/{stream-manager.js → internal/streaming/stream-manager.js} +16 -13
  29. package/dist/{run-submitter.d.ts → internal/submit/run-submitter.d.ts} +7 -7
  30. package/dist/{run-submitter.js → internal/submit/run-submitter.js} +11 -9
  31. package/dist/{serializer.d.ts → internal/submit/serializer.d.ts} +1 -1
  32. package/dist/{uploader.d.ts → internal/submit/uploader.d.ts} +8 -8
  33. package/dist/{uploader.js → internal/submit/uploader.js} +45 -10
  34. package/dist/internal/support/ci.d.ts +2 -0
  35. package/dist/internal/support/ci.js +32 -0
  36. package/dist/internal/support/cli-filters.d.ts +1 -0
  37. package/dist/internal/support/cli-filters.js +51 -0
  38. package/dist/internal/support/errors.d.ts +8 -0
  39. package/dist/internal/support/errors.js +15 -0
  40. package/dist/internal/support/instance-id.d.ts +4 -0
  41. package/dist/internal/support/instance-id.js +48 -0
  42. package/dist/internal/support/limiter.d.ts +2 -0
  43. package/dist/internal/support/limiter.js +27 -0
  44. package/dist/internal/support/setup-file.d.ts +13 -0
  45. package/dist/internal/support/setup-file.js +61 -0
  46. package/dist/internal/support/source-snippet.d.ts +12 -0
  47. package/dist/internal/support/source-snippet.js +97 -0
  48. package/dist/internal/support/worker-index.d.ts +7 -0
  49. package/dist/internal/support/worker-index.js +14 -0
  50. package/dist/{http-client.d.ts → internal/transport/http-client.d.ts} +11 -1
  51. package/dist/{http-client.js → internal/transport/http-client.js} +57 -10
  52. package/dist/{config-wrapper.d.ts → public/config-wrapper.d.ts} +1 -1
  53. package/dist/{config-wrapper.js → public/config-wrapper.js} +4 -4
  54. package/dist/public/global-setup.d.ts +13 -0
  55. package/dist/public/global-setup.js +146 -0
  56. package/dist/{config.d.ts → public/options.d.ts} +7 -45
  57. package/dist/public/options.js +2 -0
  58. package/dist/{reporter.d.ts → public/reporter.d.ts} +1 -1
  59. package/dist/{reporter.js → public/reporter.js} +44 -54
  60. package/dist/types/collected.d.ts +96 -0
  61. package/dist/types/collected.js +10 -0
  62. package/dist/types/wire.d.ts +174 -0
  63. package/dist/types/wire.js +14 -0
  64. package/dist/types.d.ts +8 -253
  65. package/dist/types.js +23 -10
  66. package/package.json +1 -6
  67. package/dist/compression.js +0 -39
  68. package/dist/fixtures.d.ts +0 -25
  69. package/dist/fixtures.js +0 -336
  70. package/dist/helpers.d.ts +0 -44
  71. package/dist/helpers.js +0 -312
  72. /package/dist/{skip-classify.js → internal/collect/skip-classify.js} +0 -0
  73. /package/dist/{compression.d.ts → internal/files/compression.d.ts} +0 -0
  74. /package/dist/{serializer.js → internal/submit/serializer.js} +0 -0
  75. /package/dist/{logger.d.ts → internal/support/logger.d.ts} +0 -0
  76. /package/dist/{logger.js → internal/support/logger.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.wrapConfig = wrapConfig;
4
- const config_js_1 = require("./config.js");
4
+ const env_js_1 = require("../internal/config/env.js");
5
5
  const PIWI_MODULE = '@piwitests/reporter';
6
6
  function isPiwiReporterEntry(entry) {
7
7
  if (typeof entry === 'string')
@@ -23,10 +23,10 @@ function injectReporter(reporter, piwiOptions) {
23
23
  }
24
24
  function resolveSetupModule() {
25
25
  try {
26
- return require.resolve('./global-setup-module.js');
26
+ return require.resolve('../global-setup-module.js');
27
27
  }
28
28
  catch {
29
- return require.resolve('./global-setup-module.ts');
29
+ return require.resolve('../global-setup-module.ts');
30
30
  }
31
31
  }
32
32
  /**
@@ -49,7 +49,7 @@ function resolveSetupModule() {
49
49
  */
50
50
  function wrapConfig(config, piwiOptions) {
51
51
  if (piwiOptions)
52
- (0, config_js_1.applyOptionsToEnv)(piwiOptions);
52
+ (0, env_js_1.applyOptionsToEnv)(piwiOptions);
53
53
  const globalSetupModules = [];
54
54
  if (config.globalSetup) {
55
55
  const orig = Array.isArray(config.globalSetup) ? config.globalSetup : [config.globalSetup];
@@ -0,0 +1,13 @@
1
+ import type { PiwiDashboardOptions } from './options.js';
2
+ /**
3
+ * Create a Playwright `globalSetup` function that registers a test run on the
4
+ * Piwi Dashboard server at the start of the run. An optional `userSetup` is
5
+ * called afterward so existing setup logic is preserved.
6
+ *
7
+ * The server-run ID and a one-time token are written to a temp file so the
8
+ * reporter instance can pick them up during the streaming handshake.
9
+ *
10
+ * @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
11
+ * @param userSetup An existing global setup to chain after the Piwi registration.
12
+ */
13
+ export declare function createGlobalSetup(options?: PiwiDashboardOptions, userSetup?: (config: any) => any): (config: any) => Promise<any>;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createGlobalSetup = createGlobalSetup;
37
+ const path = __importStar(require("node:path"));
38
+ const errors_js_1 = require("../internal/support/errors.js");
39
+ const fs = __importStar(require("node:fs"));
40
+ const env_js_1 = require("../internal/config/env.js");
41
+ const http_client_js_1 = require("../internal/transport/http-client.js");
42
+ const logger_js_1 = require("../internal/support/logger.js");
43
+ const instance_id_js_1 = require("../internal/support/instance-id.js");
44
+ const ci_js_1 = require("../internal/support/ci.js");
45
+ const setup_file_js_1 = require("../internal/support/setup-file.js");
46
+ /**
47
+ * Create a Playwright `globalSetup` function that registers a test run on the
48
+ * Piwi Dashboard server at the start of the run. An optional `userSetup` is
49
+ * called afterward so existing setup logic is preserved.
50
+ *
51
+ * The server-run ID and a one-time token are written to a temp file so the
52
+ * reporter instance can pick them up during the streaming handshake.
53
+ *
54
+ * @param options Piwi Dashboard options (uses `serverUrl`, `projectName`, …).
55
+ * @param userSetup An existing global setup to chain after the Piwi registration.
56
+ */
57
+ function createGlobalSetup(options, userSetup) {
58
+ return async function globalSetupFn(config) {
59
+ // This module compiles to `dist/public/global-setup.js`; the package entry
60
+ // point is `dist/index.js`, one level up.
61
+ const piwiReporterPath = path.resolve(__dirname, '../index.js');
62
+ // Extract options from the Piwi reporter entry in the Playwright config so
63
+ // that serverUrl / projectName etc. set inline in the reporters array are
64
+ // visible here without requiring PIWI_* env vars or a separate wrapConfig call.
65
+ let inlineReporterOptions = {};
66
+ if (Array.isArray(config?.reporter)) {
67
+ for (const r of config.reporter) {
68
+ if (!Array.isArray(r) || typeof r[0] !== 'string')
69
+ continue;
70
+ const isPiwi = r[0].toLowerCase().includes('piwi') ||
71
+ (() => {
72
+ try {
73
+ return path.resolve(require.resolve(r[0])) === piwiReporterPath;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ })();
79
+ if (isPiwi && r[1] && typeof r[1] === 'object') {
80
+ inlineReporterOptions = r[1];
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ const opts = (0, env_js_1.resolveOptions)({ ...inlineReporterOptions, ...options });
86
+ const logger = new logger_js_1.Logger(opts.verbose ?? false);
87
+ if (opts.enabled === false || !opts.serverUrl) {
88
+ logger.info('Not enabled — set PIWI_DASHBOARD_URL or serverUrl to enable.');
89
+ if (userSetup)
90
+ return userSetup(config);
91
+ return;
92
+ }
93
+ const hasPiwi = Object.keys(inlineReporterOptions).length > 0 ||
94
+ (Array.isArray(config?.reporter) &&
95
+ config.reporter.some((r) => {
96
+ if (!Array.isArray(r) || typeof r[0] !== 'string')
97
+ return false;
98
+ if (r[0].toLowerCase().includes('piwi'))
99
+ return true;
100
+ try {
101
+ return path.resolve(require.resolve(r[0])) === piwiReporterPath;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }));
107
+ if (!hasPiwi) {
108
+ logger.debug('Not reporting — Piwi is not in the Playwright reporters list.');
109
+ if (userSetup)
110
+ return userSetup(config);
111
+ return;
112
+ }
113
+ const httpClient = new http_client_js_1.HttpClient(opts.serverUrl, logger);
114
+ try {
115
+ const auth = await httpClient.resolveAuth(opts);
116
+ const runLabel = opts.runLabel || (0, ci_js_1.detectCiRunLabel)();
117
+ // Detect shard info from Playwright config (--shard=1/3)
118
+ const pwShard = config.shard;
119
+ const shardIndex = pwShard?.current;
120
+ const shardTotal = pwShard?.total;
121
+ const response = await httpClient.postJSON('/api/test-runs/setup', {
122
+ projectName: opts.projectName,
123
+ projectDescription: opts.projectDescription,
124
+ environment: opts.environment || null,
125
+ label: opts.label || null,
126
+ startTime: new Date().toISOString(),
127
+ instanceId: (0, instance_id_js_1.computeInstanceId)(opts.projectName, runLabel),
128
+ shardIndex,
129
+ shardTotal,
130
+ }, auth);
131
+ if (response?.runId && response?.setupToken) {
132
+ fs.writeFileSync((0, setup_file_js_1.getSetupFilePath)(opts.projectName), JSON.stringify({
133
+ runId: response.runId,
134
+ setupToken: response.setupToken,
135
+ projectName: opts.projectName,
136
+ }));
137
+ logger.debug(`Global setup: initialising run #${response.runId}`);
138
+ }
139
+ }
140
+ catch (error) {
141
+ logger.warn(`Could not register global setup: ${(0, errors_js_1.errorMessage)(error)}`);
142
+ }
143
+ if (userSetup)
144
+ return userSetup(config);
145
+ };
146
+ }
@@ -1,6 +1,12 @@
1
1
  import type { PlaywrightTestConfig } from '@playwright/test';
2
2
  export type { PlaywrightTestConfig } from '@playwright/test';
3
- /** Playwright shard info — mirrors `config.shard` shape */
3
+ /**
4
+ * Playwright shard info — mirrors `config.shard` shape.
5
+ *
6
+ * Internal shape: it is NOT re-exported from the package entry point, but it
7
+ * lives here next to the configuration types because it describes the same
8
+ * `config.shard` surface the options come from. May move to `types/wire.ts`.
9
+ */
4
10
  export interface ShardInfo {
5
11
  current: number;
6
12
  total: number;
@@ -70,47 +76,3 @@ export interface PiwiDashboardOptions extends PlaywrightTestConfig {
70
76
  /** Enable verbose logging for debugging. Defaults to `false`. */
71
77
  verbose?: boolean;
72
78
  }
73
- /**
74
- * Single source of truth for the `PIWI_*` env-var → option mapping. Both
75
- * `resolveOptions` (env → option) and `applyOptionsToEnv` (option → env, used
76
- * by `wrapConfig` to bridge into the global-setup process) read these names so
77
- * the mapping lives in exactly one place.
78
- */
79
- export declare const PIWI_ENV_KEYS: {
80
- readonly serverUrl: "PIWI_DASHBOARD_URL";
81
- readonly projectName: "PIWI_PROJECT_NAME";
82
- readonly verbose: "PIWI_VERBOSE";
83
- readonly apiKey: "PIWI_API_KEY";
84
- readonly username: "PIWI_USERNAME";
85
- readonly password: "PIWI_PASSWORD";
86
- readonly environment: "PIWI_ENVIRONMENT";
87
- readonly label: "PIWI_LABEL";
88
- readonly runLabel: "PIWI_RUN_LABEL";
89
- readonly streaming: "PIWI_STREAMING";
90
- readonly streamingBatchSize: "PIWI_STREAMING_BATCH_SIZE";
91
- readonly streamingBatchDelay: "PIWI_STREAMING_BATCH_DELAY";
92
- readonly liveFileUploads: "PIWI_LIVE_FILE_UPLOADS";
93
- readonly uploadTraces: "PIWI_UPLOAD_TRACES";
94
- readonly uploadReport: "PIWI_UPLOAD_REPORT";
95
- readonly captureLocators: "PIWI_CAPTURE_LOCATORS";
96
- };
97
- /**
98
- * Merge raw user options with defaults, reading from `PIWI_*` env vars when
99
- * options are not provided.
100
- *
101
- * Env semantics: env vars fill in values the caller didn't provide (fallback).
102
- * They're applied to `raw` *before* the `DEFAULTS` merge so a built-in default
103
- * never masks an env var (the pre-Phase-4 `PIWI_PROJECT_NAME` was masked by the
104
- * `default-project` default — that quirk is now fixed).
105
- *
106
- * One preserved quirk: `PIWI_VERBOSE` wins over both the default *and* an
107
- * explicit user option, matching the pre-Phase-4 behavior.
108
- */
109
- export declare function resolveOptions(raw: Record<string, any>): PiwiDashboardOptions;
110
- /**
111
- * Write the options that the isolated `global-setup-module` process needs into
112
- * `PIWI_*` env vars. `wrapConfig` calls this so the global setup (which runs
113
- * `resolveOptions({})` in a separate module) picks up the same server/auth
114
- * config the reporter instance uses. Only writes values that are actually set.
115
- */
116
- export declare function applyOptionsToEnv(options: PiwiDashboardOptions): void;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,5 @@
1
1
  import type { FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
- import { createGlobalSetup } from './helpers.js';
2
+ import { createGlobalSetup } from './global-setup.js';
3
3
  import { wrapConfig } from './config-wrapper.js';
4
4
  /**
5
5
  * Piwi Dashboard Playwright reporter.
@@ -34,45 +34,37 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PiwiDashboardReporter = void 0;
37
- const path = __importStar(require("path"));
38
- const config_js_1 = require("./config.js");
39
- const http_client_js_1 = require("./http-client.js");
40
- const uploader_js_1 = require("./uploader.js");
41
- const stream_buffer_js_1 = require("./stream-buffer.js");
42
- const crash_recovery_js_1 = require("./crash-recovery.js");
43
- const file_handler_js_1 = require("./file-handler.js");
44
- const metadata_collector_js_1 = require("./metadata-collector.js");
45
- const stream_manager_js_1 = require("./stream-manager.js");
46
- const step_analyzer_js_1 = require("./step-analyzer.js");
47
- const helpers_js_1 = require("./helpers.js");
37
+ const path = __importStar(require("node:path"));
38
+ const env_js_1 = require("../internal/config/env.js");
39
+ const http_client_js_1 = require("../internal/transport/http-client.js");
40
+ const uploader_js_1 = require("../internal/submit/uploader.js");
41
+ const stream_buffer_js_1 = require("../internal/streaming/stream-buffer.js");
42
+ const crash_recovery_js_1 = require("../internal/streaming/crash-recovery.js");
43
+ const file_handler_js_1 = require("../internal/files/file-handler.js");
44
+ const attachments_js_1 = require("../internal/capture/attachments.js");
45
+ const metadata_collector_js_1 = require("../internal/collect/metadata-collector.js");
46
+ const stream_manager_js_1 = require("../internal/streaming/stream-manager.js");
47
+ const step_analyzer_js_1 = require("../internal/collect/step-analyzer.js");
48
+ const instance_id_js_1 = require("../internal/support/instance-id.js");
49
+ const source_snippet_js_1 = require("../internal/support/source-snippet.js");
50
+ const ci_js_1 = require("../internal/support/ci.js");
51
+ const worker_index_js_1 = require("../internal/support/worker-index.js");
52
+ const cli_filters_js_1 = require("../internal/support/cli-filters.js");
53
+ const global_setup_js_1 = require("./global-setup.js");
48
54
  const config_wrapper_js_1 = require("./config-wrapper.js");
49
- const serializer_js_1 = require("./serializer.js");
50
- const skip_classify_js_1 = require("./skip-classify.js");
51
- const run_submitter_js_1 = require("./run-submitter.js");
52
- const logger_js_1 = require("./logger.js");
55
+ const serializer_js_1 = require("../internal/submit/serializer.js");
56
+ const skip_classify_js_1 = require("../internal/collect/skip-classify.js");
57
+ const error_text_js_1 = require("../internal/collect/error-text.js");
58
+ const run_submitter_js_1 = require("../internal/submit/run-submitter.js");
59
+ const logger_js_1 = require("../internal/support/logger.js");
53
60
  /**
54
- * Build the stored error text for a test result.
55
- *
56
- * Playwright's `error.message` carries the failure message and call log but no
57
- * stack frames — those live on `error.stack`/`error.location`. The server's
58
- * locator-healing lookup needs the failing call site to find the pre-captured
59
- * snapshot for that locator, so when the message has no `at …` frame we append
60
- * a synthetic one from `error.location`, relativized to the cwd so it matches
61
- * the format the fixture records at capture time. The frame is appended after
62
- * the message, where `extractMessageHead` already trims it off before
63
- * fingerprinting — so failure clustering is unaffected.
61
+ * Relative `file:line:column` location string for a test, normalized to POSIX
62
+ * path separators so Playwright's CLI file filter (and the dashboard's copyable
63
+ * retry command) match on every platform `path.relative` yields backslashes on Windows.
64
64
  */
65
- function buildErrorText(result) {
66
- const err = result.error;
67
- if (!err)
68
- return null;
69
- let text = err.message ?? '';
70
- const loc = err.location;
71
- if (loc?.file && !/\n\s+at\s/.test(text)) {
72
- const rel = path.relative(process.cwd(), loc.file).split(path.sep).join('/');
73
- text += `\n at ${rel}:${loc.line}:${loc.column}`;
74
- }
75
- return text;
65
+ function testLocation(test) {
66
+ const relativeFilePath = path.relative(process.cwd(), test.location.file).split(path.sep).join('/');
67
+ return `${relativeFilePath}:${test.location.line}:${test.location.column}`;
76
68
  }
77
69
  /**
78
70
  * Piwi Dashboard Playwright reporter.
@@ -105,10 +97,10 @@ class PiwiDashboardReporter {
105
97
  this.streamManager = null;
106
98
  /** Track suite-level setup steps (beforeAll/afterAll) not tied to any test */
107
99
  this.setupSteps = [];
108
- this.options = (0, config_js_1.resolveOptions)(rawOptions);
100
+ this.options = (0, env_js_1.resolveOptions)(rawOptions);
109
101
  this.enabled = this.options.enabled !== false && !!this.options.serverUrl;
110
- this.runLabel = this.options.runLabel || (0, helpers_js_1.detectCiRunLabel)();
111
- this.instanceId = (0, helpers_js_1.computeInstanceId)(this.options.projectName, this.runLabel);
102
+ this.runLabel = this.options.runLabel || (0, ci_js_1.detectCiRunLabel)();
103
+ this.instanceId = (0, instance_id_js_1.computeInstanceId)(this.options.projectName, this.runLabel);
112
104
  const logger = new logger_js_1.Logger(this.options.verbose ?? false);
113
105
  this.logger = logger;
114
106
  this.httpClient = new http_client_js_1.HttpClient(this.options.serverUrl ?? 'http://localhost:3000', logger);
@@ -140,7 +132,7 @@ class PiwiDashboardReporter {
140
132
  const grep = grepRe && grepRe.source !== '.*' ? grepRe.source : undefined;
141
133
  const grepInvert = grepInvertRe?.source;
142
134
  // File/path filters come from the CLI invocation, not config.grep.
143
- const fileFilters = (0, helpers_js_1.detectCliFileFilters)();
135
+ const fileFilters = (0, cli_filters_js_1.detectCliFileFilters)();
144
136
  if (grep || grepInvert || fileFilters.length > 0) {
145
137
  this.isFullRun = false;
146
138
  this.filterDetails = {
@@ -166,13 +158,12 @@ class PiwiDashboardReporter {
166
158
  }
167
159
  /** Playwright reporter hook: called when an individual test begins */
168
160
  onTestBegin(test, result) {
169
- const relativeFilePath = path.relative(process.cwd(), test.location.file);
170
161
  const { suitePath, suiteConfig } = this.metadataCollector.getSuiteInfo(test);
171
162
  const beginEvent = {
172
163
  type: 'begin',
173
164
  title: test.title,
174
- location: `${relativeFilePath}:${test.location.line}:${test.location.column}`,
175
- workerIndex: (0, helpers_js_1.workerIndexOf)(result),
165
+ location: testLocation(test),
166
+ workerIndex: (0, worker_index_js_1.workerIndexOf)(result),
176
167
  shardIndex: this.shardInfo?.current ?? null,
177
168
  browser: this.metadataCollector.getBrowserConfig(test) || undefined,
178
169
  suitePath,
@@ -195,7 +186,7 @@ class PiwiDashboardReporter {
195
186
  location: step.location ? `${step.location.file}:${step.location.line}:${step.location.column}` : 'unknown',
196
187
  stepCategory: cat,
197
188
  parentTitle: test?.title || null,
198
- workerIndex: (0, helpers_js_1.workerIndexOf)(_result),
189
+ workerIndex: (0, worker_index_js_1.workerIndexOf)(_result),
199
190
  startedAt: step.startTime instanceof Date ? step.startTime.getTime() : null,
200
191
  };
201
192
  this.streamManager?.queueBeginEvent(event);
@@ -209,7 +200,7 @@ class PiwiDashboardReporter {
209
200
  return;
210
201
  if (cat !== 'hook' && cat !== 'fixture')
211
202
  return;
212
- const workerIndex = (0, helpers_js_1.workerIndexOf)(_result);
203
+ const workerIndex = (0, worker_index_js_1.workerIndexOf)(_result);
213
204
  const startedAt = step.startTime instanceof Date ? step.startTime.getTime() : null;
214
205
  const event = {
215
206
  type: 'step-end',
@@ -239,7 +230,6 @@ class PiwiDashboardReporter {
239
230
  /** Playwright reporter hook: called when an individual test finishes */
240
231
  onTestEnd(test, result) {
241
232
  this.totalTests++;
242
- const relativeFilePath = path.relative(process.cwd(), test.location.file);
243
233
  this.reportedTestIds.add(test.id);
244
234
  const { suitePath, suiteConfig } = this.metadataCollector.getSuiteInfo(test);
245
235
  const annotations = (0, skip_classify_js_1.mergeAnnotations)(test, result);
@@ -247,12 +237,12 @@ class PiwiDashboardReporter {
247
237
  const testCase = {
248
238
  type: 'complete',
249
239
  title: test.title,
250
- location: `${relativeFilePath}:${test.location.line}:${test.location.column}`,
240
+ location: testLocation(test),
251
241
  status,
252
242
  duration: result.duration,
253
- error: buildErrorText(result),
243
+ error: (0, error_text_js_1.buildErrorText)(result),
254
244
  retries: result.retry,
255
- workerIndex: (0, helpers_js_1.workerIndexOf)(result),
245
+ workerIndex: (0, worker_index_js_1.workerIndexOf)(result),
256
246
  shardIndex: this.shardInfo?.current ?? null,
257
247
  startedAt: result.startTime ? result.startTime.getTime() : null,
258
248
  attachments: result.attachments || [],
@@ -262,7 +252,8 @@ class PiwiDashboardReporter {
262
252
  testAnnotations: annotations.length ? annotations : null,
263
253
  };
264
254
  if (result.status === 'failed' || result.status === 'timedOut') {
265
- const snippet = (0, helpers_js_1.readSourceSnippet)(test.location.file, test.location.line, 30);
255
+ const failingLine = (0, source_snippet_js_1.extractFailingLine)(testCase.error, test.location.file, test.location.line);
256
+ const snippet = (0, source_snippet_js_1.readSourceSnippet)(test.location.file, test.location.line, 30, failingLine);
266
257
  if (snippet)
267
258
  testCase.testSource = snippet;
268
259
  }
@@ -280,7 +271,7 @@ class PiwiDashboardReporter {
280
271
  // (captured in the fixture at action call time). No index correlation
281
272
  // with pw:api steps — that was unreliable across workers/concurrent calls.
282
273
  const locatorAttachment = this.options.captureLocators !== false
283
- ? result.attachments.find((a) => a.name === 'piwi-dashboard-locators')
274
+ ? result.attachments.find((a) => a.name === attachments_js_1.ATTACHMENT_NAMES.locators)
284
275
  : undefined;
285
276
  if (locatorAttachment?.body) {
286
277
  try {
@@ -326,12 +317,11 @@ class PiwiDashboardReporter {
326
317
  for (const test of this.plannedTests) {
327
318
  if (this.reportedTestIds.has(test.id))
328
319
  continue;
329
- const relativeFilePath = path.relative(process.cwd(), test.location.file);
330
320
  const { suitePath, suiteConfig } = this.metadataCollector.getSuiteInfo(test);
331
321
  const testCase = {
332
322
  type: 'complete',
333
323
  title: test.title,
334
- location: `${relativeFilePath}:${test.location.line}:${test.location.column}`,
324
+ location: testLocation(test),
335
325
  status: 'didnotrun',
336
326
  duration: 0,
337
327
  error: null,
@@ -380,4 +370,4 @@ class PiwiDashboardReporter {
380
370
  }
381
371
  exports.PiwiDashboardReporter = PiwiDashboardReporter;
382
372
  PiwiDashboardReporter.wrapConfig = config_wrapper_js_1.wrapConfig;
383
- PiwiDashboardReporter.createGlobalSetup = helpers_js_1.createGlobalSetup;
373
+ PiwiDashboardReporter.createGlobalSetup = global_setup_js_1.createGlobalSetup;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Internal collection model — the shapes the reporter accumulates *in process*
3
+ * while a run executes.
4
+ *
5
+ * These are NOT sent to the server verbatim: the wire projection happens in
6
+ * `serializer.ts` (`toWireTestCase` / `serializeRun`) at the JSON boundary. For
7
+ * the external server contract, see `./wire.ts`.
8
+ */
9
+ import type { BrowserConfig, SuiteConfigEntry, TestAnnotation, TestStepEvent } from './wire.js';
10
+ import type { LocatorSnapshot } from '../internal/capture/locator-healing.js';
11
+ /**
12
+ * A raw Playwright attachment as exposed on `TestResult.attachments`.
13
+ * Carried verbatim on `CollectedTestCase.attachments` so `FileHandler` can
14
+ * resolve trace/attachment paths; never sent to the server.
15
+ */
16
+ export interface RawAttachment {
17
+ name: string;
18
+ path?: string;
19
+ contentType?: string;
20
+ body?: Buffer;
21
+ originalName?: string;
22
+ }
23
+ /** Performance metrics collected from `result.steps` by `step-analyzer`. */
24
+ export interface CollectedPerformanceMetrics {
25
+ steps: Array<{
26
+ title: string;
27
+ duration: number;
28
+ category: string;
29
+ error?: {
30
+ message: string;
31
+ };
32
+ failed?: boolean;
33
+ }>;
34
+ totalStepDuration: number;
35
+ slowestStep: {
36
+ title: string;
37
+ duration: number;
38
+ } | null;
39
+ navigationCount: number;
40
+ navigationTotalDuration: number;
41
+ waitTotalDuration: number;
42
+ waitCount: number;
43
+ }
44
+ /**
45
+ * What `onTestEnd` accumulates per test case. Mixes three concerns that the
46
+ * reporter must keep together during a run:
47
+ * - **wire fields** (`title`, `status`, `duration`, …) that `toWireTestCase`
48
+ * projects onto `WireTestCase` before sending,
49
+ * - **collection-only state** (`attachments`, `performanceMetrics`,
50
+ * `stepEvents`) consumed by `FileHandler` and the run-level summary,
51
+ * - the `type` discriminant so the same collected object can be queued as a
52
+ * stream event.
53
+ *
54
+ * Upload bookkeeping (`_filesUploaded`) is deliberately NOT on this object —
55
+ * `StreamManager` tracks it in a side `Set` so the data model stays clean.
56
+ */
57
+ export interface CollectedTestCase {
58
+ /** Stream-event discriminant: `'begin'` or `'complete'`. Omitted for batch-only runs. */
59
+ type?: 'begin' | 'complete';
60
+ title: string;
61
+ location: string;
62
+ status?: string;
63
+ duration?: number;
64
+ error?: string | null;
65
+ retries?: number;
66
+ workerIndex?: number | null;
67
+ shardIndex?: number | null;
68
+ startedAt?: number | null;
69
+ /** Raw Playwright attachments — never sent on the wire. */
70
+ attachments?: RawAttachment[];
71
+ browser?: BrowserConfig | null;
72
+ suitePath?: string[] | null;
73
+ suiteConfig?: SuiteConfigEntry[] | null;
74
+ testAnnotations?: TestAnnotation[] | null;
75
+ /** Source snippet around the failing line (failed/timedOut only). */
76
+ testSource?: string;
77
+ /** Step metrics from `collectStepMetrics`. Consumed by the run summary + `toWireTestCase`. */
78
+ performanceMetrics?: CollectedPerformanceMetrics;
79
+ stepEvents?: TestStepEvent[];
80
+ /** Parsed from `piwi-network` attachments by `FileHandler`. */
81
+ networkRequests?: unknown;
82
+ /** Parsed from `piwi-web-vitals` attachments. */
83
+ webVitals?: unknown;
84
+ /** Parsed from `piwi-console` attachments. */
85
+ consoleLogs?: unknown;
86
+ /** Parsed from `piwi-aria-snapshot` attachment. */
87
+ ariaSnapshot?: string;
88
+ /** Parsed from `piwi-locators` attachment. */
89
+ locatorSnapshots?: LocatorSnapshot[];
90
+ }
91
+ /** Hash + size of a single trace file, used for dedup against the server. */
92
+ export interface TraceHashInfo {
93
+ tracePath: string;
94
+ hash: string;
95
+ size: number;
96
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Internal collection model — the shapes the reporter accumulates *in process*
4
+ * while a run executes.
5
+ *
6
+ * These are NOT sent to the server verbatim: the wire projection happens in
7
+ * `serializer.ts` (`toWireTestCase` / `serializeRun`) at the JSON boundary. For
8
+ * the external server contract, see `./wire.ts`.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });