@stanterprise/playwright-reporter 1.0.0 → 1.0.1

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 (44) hide show
  1. package/README.md +261 -11
  2. package/dist/client/grpcClient.d.ts +16 -0
  3. package/dist/client/grpcClient.js +172 -0
  4. package/dist/client/messageQueue.d.ts +27 -0
  5. package/dist/client/messageQueue.js +71 -0
  6. package/dist/handlers/index.d.ts +9 -0
  7. package/dist/handlers/index.js +19 -0
  8. package/dist/handlers/onBeginHandler.d.ts +6 -0
  9. package/dist/handlers/onBeginHandler.js +98 -0
  10. package/dist/handlers/onEndHandler.d.ts +5 -0
  11. package/dist/handlers/onEndHandler.js +53 -0
  12. package/dist/handlers/onErrorHandler.d.ts +5 -0
  13. package/dist/handlers/onErrorHandler.js +16 -0
  14. package/dist/handlers/onStepBeginHandler.d.ts +5 -0
  15. package/dist/handlers/onStepBeginHandler.js +39 -0
  16. package/dist/handlers/onStepEndHandler.d.ts +5 -0
  17. package/dist/handlers/onStepEndHandler.js +62 -0
  18. package/dist/handlers/onTestBeginHandler.d.ts +5 -0
  19. package/dist/handlers/onTestBeginHandler.js +33 -0
  20. package/dist/handlers/onTestEndHandler.d.ts +5 -0
  21. package/dist/handlers/onTestEndHandler.js +58 -0
  22. package/dist/handlers/onTestFailHandler.d.ts +5 -0
  23. package/dist/handlers/onTestFailHandler.js +25 -0
  24. package/dist/reporter.d.ts +4 -15
  25. package/dist/reporter.js +97 -386
  26. package/dist/types.d.ts +56 -0
  27. package/dist/utils/attachmentProcessor.d.ts +6 -2
  28. package/dist/utils/attachmentProcessor.js +33 -4
  29. package/dist/utils/debugLogger.d.ts +13 -0
  30. package/dist/utils/debugLogger.js +87 -0
  31. package/dist/utils/envRetriever.d.ts +9 -0
  32. package/dist/utils/envRetriever.js +23 -0
  33. package/dist/utils/idGenerators.d.ts +20 -0
  34. package/dist/utils/idGenerators.js +40 -0
  35. package/dist/utils/index.d.ts +4 -0
  36. package/dist/utils/index.js +4 -0
  37. package/dist/utils/metadataHelpers.d.ts +42 -0
  38. package/dist/utils/metadataHelpers.js +91 -0
  39. package/dist/utils/optionsMapper.d.ts +7 -0
  40. package/dist/utils/optionsMapper.js +51 -0
  41. package/dist/utils/statusMapper.js +7 -6
  42. package/dist/utils/timeHelpers.d.ts +5 -5
  43. package/dist/utils/timeHelpers.js +24 -8
  44. package/package.json +6 -7
package/dist/reporter.js CHANGED
@@ -1,95 +1,74 @@
1
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
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const protobuf_1 = require("@stanterprise/protobuf");
37
- const grpc = __importStar(require("@grpc/grpc-js"));
38
6
  const crypto_1 = require("crypto");
39
- const utils_1 = require("./utils");
7
+ const optionsMapper_1 = __importDefault(require("./utils/optionsMapper"));
8
+ const debugLogger_1 = require("./utils/debugLogger");
9
+ const grpcClient_1 = __importDefault(require("./client/grpcClient"));
10
+ const messageQueue_1 = require("./client/messageQueue");
11
+ const handlers_1 = require("./handlers");
40
12
  class StanterpriseReporter {
41
13
  constructor(options = {}) {
42
14
  // Generic gRPC client (we call unary methods by path directly).
43
15
  this.grpcClient = null;
44
- this.grpcFirstErrorLogged = false;
16
+ this.options = {};
45
17
  // Generate a unique run ID for this test run
46
18
  this.runId = "";
19
+ this.executionId = "";
47
20
  this.runStartTime = new Date();
48
- this.rootSuite = null;
49
- // Track reported suites to ensure we only report each suite once and can reference correct IDs
50
- this.reportedSuites = new Map();
51
- this.grpcAddress =
52
- options.grpcAddress ||
53
- process.env.STANTERPRISE_GRPC_ADDRESS ||
54
- "localhost:50051";
55
- this.grpcEnabled =
56
- options.grpcEnabled ??
57
- (process.env.STANTERPRISE_GRPC_ENABLED || "true").toLowerCase() !==
58
- "false";
59
- this.grpcTimeout = options.grpcTimeout || 1000;
60
- this.verbose = options.verbose || false;
61
- if (this.verbose) {
21
+ // Sequential message queue – guarantees ordered delivery to the server
22
+ this.messageQueue = new messageQueue_1.MessageQueue();
23
+ this.metadata = {};
24
+ this.options = (0, optionsMapper_1.default)(options);
25
+ if (this.options.verbose) {
62
26
  console.log("Stanterprise Reporter: Initialized with options:", {
63
- grpcAddress: this.grpcAddress,
64
- grpcEnabled: this.grpcEnabled,
65
- grpcTimeout: this.grpcTimeout,
66
- verbose: this.verbose,
27
+ grpcAddress: this.options.grpcAddress,
28
+ grpcEnabled: this.options.grpcEnabled,
29
+ grpcTimeout: this.options.grpcTimeout,
30
+ verbose: this.options.verbose,
67
31
  });
68
32
  }
33
+ // Generate a UUID for runId
34
+ this.runId = process.env.STANTERPRISE_RUN_ID || (0, crypto_1.randomUUID)();
35
+ this.executionId = process.env.STANTERPRISE_EXECUTION_ID || (0, crypto_1.randomUUID)();
36
+ // Initialize debug file (creates/truncates it) when debug mode is enabled
37
+ (0, debugLogger_1.initDebugFile)(this.options);
69
38
  }
70
39
  onBegin(config, suite) {
71
- // Generate a UUID for runId
72
- this.runId = (0, crypto_1.randomUUID)();
73
- this.rootSuite = suite;
40
+ if (config.shard) {
41
+ this.metadata["shard.current"] = String(config.shard.current);
42
+ this.metadata["shard.total"] = String(config.shard.total);
43
+ }
74
44
  // Lazily create the client if enabled.
75
- if (this.grpcEnabled) {
76
- try {
77
- this.grpcClient = new grpc.Client(this.grpcAddress, grpc.credentials.createInsecure());
78
- }
79
- catch (e) {
80
- this.logGrpcErrorOnce("Failed to create gRPC client", e);
45
+ if (this.options.grpcEnabled) {
46
+ this.grpcClient = (0, grpcClient_1.default)(this.options);
47
+ if (!this.grpcClient) {
48
+ console.warn(`Stanterprise Reporter: Failed to create gRPC client. Disabling gRPC reporting.`);
49
+ this.options.grpcEnabled = false;
81
50
  }
82
51
  }
83
52
  else {
84
- console.log("Stanterprise Reporter: gRPC disabled via STANTERPRISE_GRPC_ENABLED=false");
53
+ if (this.options.verbose) {
54
+ console.log("Stanterprise Reporter: gRPC disabled via STANTERPRISE_GRPC_ENABLED=false");
55
+ }
56
+ }
57
+ if (this.options.verbose) {
58
+ console.log(`Stanterprise Reporter: Test run started with ID: ${this.runId}`);
59
+ console.log(`Number of tests: ${suite.allTests().length}`);
60
+ console.log(`Run started at: ${this.runStartTime.toISOString()}`);
61
+ if (config.shard) {
62
+ console.log(`Shard: ${config.shard.current} of ${config.shard.total}`);
63
+ }
64
+ }
65
+ if (this.options.grpcEnabled) {
66
+ (0, handlers_1.handleOnBeginEvent)(config, suite, process.env.STANTERPRISE_TEST_RUN_NAME || "Playwright Test Run", this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
85
67
  }
86
- console.log(`Stanterprise Reporter: Test run started with ID: ${this.runId}`);
87
- console.log(`Number of tests: ${suite.allTests().length}`);
88
- console.log(`Run started at: ${this.runStartTime.toISOString()}`);
89
- // Report root suite begin and track its ID
90
- const rootSuiteId = this.getSuiteId(suite);
91
68
  }
92
69
  async onExit() {
70
+ // Wait for all enqueued messages to be sent before closing the connection.
71
+ await this.messageQueue.drain();
93
72
  console.log(`Stanterprise Reporter: Test run completed - Run ID: ${this.runId}`);
94
73
  // Cleanup gRPC client
95
74
  try {
@@ -99,209 +78,65 @@ class StanterpriseReporter {
99
78
  console.error("Stanterprise Reporter: Error during gRPC client cleanup in onExit:", e);
100
79
  }
101
80
  }
102
- // Getter to access the current run ID
103
- getRunId() {
104
- return this.runId;
105
- }
106
- // Getter to access the run start time
107
- getRunStartTime() {
108
- return this.runStartTime;
109
- }
110
81
  onEnd(result) {
111
82
  const runDuration = Date.now() - this.runStartTime.getTime();
112
- console.log(`Stanterprise Reporter: Test run ended - Run ID: ${this.runId}`);
113
- console.log(`Final result: ${result.status}`);
114
- console.log(`Run duration: ${runDuration}ms (Playwright duration: ${result.duration}ms)`);
115
- console.log(`Run start time: ${this.runStartTime.toISOString()}`);
116
- console.log(`Playwright start time: ${result.startTime.toISOString()}`);
117
- // Report root suite end
118
- if (this.rootSuite) {
119
- this.reportSuiteEnd(this.rootSuite, result);
83
+ if (this.options.verbose) {
84
+ console.log(`Stanterprise Reporter: Test run ended - Run ID: ${this.runId}`);
85
+ console.log(`Final result: ${result.status}`);
86
+ console.log(`Run duration: ${runDuration}ms (Playwright duration: ${result.duration}ms)`);
87
+ console.log(`Run start time: ${this.runStartTime.toISOString()}`);
88
+ console.log(`Playwright start time: ${result.startTime.toISOString()}`);
89
+ }
90
+ if (this.options.grpcEnabled) {
91
+ (0, handlers_1.handleOnEndEvent)(result, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue, this.metadata);
120
92
  }
121
93
  return Promise.resolve();
122
94
  }
123
95
  onTestBegin(test, result) {
124
- // Create unique test execution ID combining run ID and test ID
125
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
126
- console.log(`Stanterprise Reporter: Test started - ${test.title}`);
127
- console.log(` Run ID: ${this.runId}`);
128
- console.log(` Test ID (static): ${test.id}`);
129
- console.log(` Unique Execution ID: ${uniqueTestExecutionId}`);
130
- // Get test suite run ID from parent suite if available, ensuring suite is reported
131
- const testSuiteRunId = test.parent ? this.getSuiteId(test.parent) : "";
132
- // Build metadata from test annotations
133
- const metadata = new Map();
134
- test.annotations.forEach((annotation, index) => {
135
- metadata.set(`annotation_${index}_type`, annotation.type);
136
- if (annotation.description) {
137
- metadata.set(`annotation_${index}_description`, annotation.description);
138
- }
139
- });
140
- // Build and send the TestBegin event via generic unary call.
141
- const request = new protobuf_1.events.TestBeginEventRequest({
142
- test_case: new protobuf_1.entities.TestCaseRun({
143
- id: uniqueTestExecutionId,
144
- title: test.title,
145
- run_id: this.runId,
146
- test_id: test.id,
147
- test_suite_run_id: testSuiteRunId,
148
- start_time: (0, utils_1.createTimestamp)(result.startTime),
149
- metadata: metadata,
150
- actual_tags: test.tags,
151
- }),
152
- });
153
- // Fire-and-forget to avoid slowing tests; log errors once.
154
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test begin", e));
96
+ if (this.options.verbose) {
97
+ console.log(`Stanterprise Reporter: Test started - ${test.title}`);
98
+ console.log(` Run ID: ${this.runId}`);
99
+ console.log(` Test ID: ${test.id}`);
100
+ }
101
+ if (this.options.grpcEnabled) {
102
+ (0, handlers_1.handleOnTestBeginEvent)(test, result, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
103
+ }
155
104
  }
156
105
  async onStepBegin(test, result, step) {
157
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
158
- const uniqueStepId = `${uniqueTestExecutionId}-${step.title}-${step.startTime.getTime()}`;
159
- console.log(`Stanterprise Reporter: Step started - ${step.title}`);
160
- console.log(` Category: ${step.category}`);
161
- // Build metadata from step annotations
162
- const metadata = new Map();
163
- metadata.set("category", step.category);
164
- step.annotations.forEach((annotation, index) => {
165
- metadata.set(`annotation_${index}_type`, annotation.type);
166
- if (annotation.description) {
167
- metadata.set(`annotation_${index}_description`, annotation.description);
168
- }
169
- });
170
- // Get parent step ID if this step has a parent
171
- const parentStepId = step.parent
172
- ? `${uniqueTestExecutionId}-${step.parent.title}-${step.parent.startTime.getTime()}`
173
- : "";
174
- // Build and send the StepBegin event
175
- const request = new protobuf_1.events.StepBeginEventRequest({
176
- step: new protobuf_1.entities.StepRun({
177
- id: uniqueStepId,
178
- run_id: this.runId,
179
- test_case_run_id: uniqueTestExecutionId,
180
- title: step.title,
181
- type: step.category,
182
- start_time: (0, utils_1.createTimestamp)(step.startTime),
183
- location: step.location
184
- ? `${step.location.file}:${step.location.line}:${step.location.column}`
185
- : "",
186
- metadata: metadata,
187
- parent_step_id: parentStepId,
188
- worker_index: result.workerIndex.toString(),
189
- }),
190
- });
191
- // Fire-and-forget to avoid slowing tests
192
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportStepBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report step begin", e));
106
+ if (this.options.verbose) {
107
+ console.log(`Stanterprise Reporter: Step started - ${step.title}`);
108
+ console.log(` Category: ${step.category}`);
109
+ }
110
+ if (this.options.grpcEnabled) {
111
+ (0, handlers_1.handleOnStepBeginEvent)(test, result, step, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
112
+ }
193
113
  }
194
114
  onStepEnd(test, result, step) {
195
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
196
- const uniqueStepId = `${uniqueTestExecutionId}-${step.title}-${step.startTime.getTime()}`;
197
- console.log(`Stanterprise Reporter: Step ended - ${step.title}`);
198
- console.log(` Duration: ${step.duration}ms`);
199
- // Map step error to status
200
- const stepStatus = (0, utils_1.mapStepStatus)(!!step.error);
201
- // Build metadata from step annotations
202
- const metadata = new Map();
203
- metadata.set("category", step.category);
204
- step.annotations.forEach((annotation, index) => {
205
- metadata.set(`annotation_${index}_type`, annotation.type);
206
- if (annotation.description) {
207
- metadata.set(`annotation_${index}_description`, annotation.description);
208
- }
209
- });
210
- // Get parent step ID if this step has a parent
211
- const parentStepId = step.parent
212
- ? `${uniqueTestExecutionId}-${step.parent.title}-${step.parent.startTime.getTime()}`
213
- : "";
214
- // Build and send the StepEnd event
215
- const request = new protobuf_1.events.StepEndEventRequest({
216
- step: new protobuf_1.entities.StepRun({
217
- id: uniqueStepId,
218
- run_id: this.runId,
219
- test_case_run_id: uniqueTestExecutionId,
220
- title: step.title,
221
- type: step.category,
222
- start_time: (0, utils_1.createTimestamp)(step.startTime),
223
- duration: (0, utils_1.createDuration)(step.duration),
224
- status: stepStatus,
225
- error: step.error ? step.error.message || "" : "",
226
- errors: step.error ? [step.error.message || ""] : [],
227
- location: step.location
228
- ? `${step.location.file}:${step.location.line}:${step.location.column}`
229
- : "",
230
- metadata: metadata,
231
- parent_step_id: parentStepId,
232
- worker_index: result.workerIndex.toString(),
233
- }),
234
- });
235
- // Fire-and-forget to avoid slowing tests
236
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportStepEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report step end", e));
115
+ if (this.options.verbose) {
116
+ console.log(`Stanterprise Reporter: Step ended - ${step.title}`);
117
+ console.log(` Duration: ${step.duration}ms`);
118
+ }
119
+ if (this.options.grpcEnabled) {
120
+ (0, handlers_1.handleOnStepEndEvent)(test, result, step, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
121
+ }
237
122
  }
238
123
  onTestEnd(test, result) {
239
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
240
- console.log(`Stanterprise Reporter: Test ended - ${test.title}`);
241
- console.log(` Status: ${result.status}`);
242
- console.log(` Duration: ${result.duration}ms`);
243
- // Map Playwright test status to protobuf TestStatus
244
- const testStatus = (0, utils_1.mapTestStatus)(result.status);
245
- // Process attachments (screenshots, videos, etc.)
246
- const attachments = (0, utils_1.processAttachments)(result);
247
- // Extract error information if the test failed
248
- const { errorMessage, stackTrace, errors } = (0, utils_1.extractErrorInfo)(result);
249
- // Get test suite run ID from parent suite if available, ensuring suite is reported
250
- const testSuiteRunId = test.parent ? this.getSuiteId(test.parent) : "";
251
- // Build metadata from test annotations and result metadata
252
- const metadata = new Map();
253
- test.annotations.forEach((annotation, index) => {
254
- metadata.set(`annotation_${index}_type`, annotation.type);
255
- if (annotation.description) {
256
- metadata.set(`annotation_${index}_description`, annotation.description);
257
- }
258
- });
259
- result.annotations.forEach((annotation, index) => {
260
- metadata.set(`result_annotation_${index}_type`, annotation.type);
261
- if (annotation.description) {
262
- metadata.set(`result_annotation_${index}_description`, annotation.description);
263
- }
264
- });
265
- // Build and send the TestEnd event
266
- const request = new protobuf_1.events.TestEndEventRequest({
267
- test_case: new protobuf_1.entities.TestCaseRun({
268
- id: uniqueTestExecutionId,
269
- title: test.title,
270
- run_id: this.runId,
271
- test_id: test.id,
272
- test_suite_run_id: testSuiteRunId,
273
- status: testStatus,
274
- start_time: (0, utils_1.createTimestamp)(result.startTime),
275
- attachments: attachments,
276
- error_message: errorMessage,
277
- stack_trace: stackTrace,
278
- errors: errors,
279
- metadata: metadata,
280
- actual_tags: test.tags,
281
- }),
282
- });
283
- // Fire-and-forget to avoid slowing tests
284
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test end", e));
124
+ if (this.options.verbose) {
125
+ console.log(`Stanterprise Reporter: Test ended - ${test.title}`);
126
+ console.log(` Status: ${result.status}`);
127
+ console.log(` Duration: ${result.duration}ms`);
128
+ }
129
+ if (this.options.grpcEnabled) {
130
+ (0, handlers_1.handleOnTestEndEvent)(test, result, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
131
+ }
285
132
  }
286
133
  onTestFail(test, result) {
287
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
288
- if (this.verbose) {
134
+ if (this.options.verbose) {
289
135
  console.log(`Stanterprise Reporter: Test failed - ${test.title}`);
290
136
  }
291
- // Extract failure details
292
- const { errorMessage: failureMessage, stackTrace } = (0, utils_1.extractErrorInfo)(result);
293
- // Process attachments for failed tests
294
- const attachments = (0, utils_1.processAttachments)(result);
295
- // Build and send the TestFailure event
296
- const request = new protobuf_1.events.TestFailureEventRequest({
297
- test_id: uniqueTestExecutionId,
298
- failure_message: failureMessage,
299
- stack_trace: stackTrace,
300
- timestamp: (0, utils_1.createTimestampFromMs)(Date.now()),
301
- attachments: attachments,
302
- });
303
- // Fire-and-forget to avoid slowing tests
304
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestFailure", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test failure", e));
137
+ if (this.options.grpcEnabled) {
138
+ (0, handlers_1.handleOnTestFailEvent)(test, result, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
139
+ }
305
140
  }
306
141
  onError(error) {
307
142
  console.error("Stanterprise Reporter: An error occurred during the test run");
@@ -309,141 +144,17 @@ class StanterpriseReporter {
309
144
  if (error.stack) {
310
145
  console.error(`Stack trace: ${error.stack}`);
311
146
  }
147
+ if (this.options.grpcEnabled) {
148
+ (0, handlers_1.handleOnErrorEvent)(error, this.runId, this.executionId, this.grpcClient, this.options, this.messageQueue);
149
+ }
312
150
  }
313
151
  onStdErr(chunk, test, result) {
314
152
  console.error(`Stanterprise Reporter: Standard error output - ${chunk.toString()}`);
315
153
  }
316
154
  onStdOut(chunk, test, result) {
317
- // console.log(`Stanterprise Reporter: Standard output - ${chunk.toString()}`);
318
- }
319
- // Helper: Get suite ID for a suite, ensuring it's been reported
320
- getSuiteId(suite) {
321
- // Check if we've already reported this suite
322
- if (this.reportedSuites.has(suite)) {
323
- return this.reportedSuites.get(suite);
324
- }
325
- // Generate a unique suite ID based on suite hierarchy
326
- const titlePath = suite.titlePath().join('/') || 'root';
327
- const suiteId = `${this.runId}-suite-${titlePath}`;
328
- // Report the suite begin event
329
- this.reportSuiteBegin(suite, suiteId);
330
- // Track that we've reported this suite
331
- this.reportedSuites.set(suite, suiteId);
332
- return suiteId;
333
- }
334
- // Helper: Report suite begin event
335
- reportSuiteBegin(suite, suiteId) {
336
- const id = suiteId || `${this.runId}-suite-${suite.title || 'root'}`;
337
- console.log(`Stanterprise Reporter: Suite started - ${suite.title || 'root'}`);
338
- console.log(` Suite type: ${suite.type}`);
339
- // Build metadata from suite location
340
- const metadata = new Map();
341
- metadata.set("suite_type", suite.type);
342
- if (suite.location) {
343
- metadata.set("location_file", suite.location.file);
344
- metadata.set("location_line", suite.location.line.toString());
345
- metadata.set("location_column", suite.location.column.toString());
155
+ if (this.options.verbose) {
156
+ console.log(`Stanterprise Reporter: Standard output - ${chunk.toString()}`);
346
157
  }
347
- // Build and send the SuiteBegin event
348
- const request = new protobuf_1.events.SuiteBeginEventRequest({
349
- suite: new protobuf_1.entities.TestSuiteRun({
350
- id: id,
351
- name: suite.title || 'root',
352
- start_time: (0, utils_1.createTimestamp)(this.runStartTime),
353
- metadata: metadata,
354
- }),
355
- });
356
- // Fire-and-forget to avoid slowing tests
357
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportSuiteBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report suite begin", e));
358
- }
359
- // Helper: Report suite end event
360
- reportSuiteEnd(suite, result) {
361
- const suiteId = `${this.runId}-suite-${suite.title || 'root'}`;
362
- const endTime = new Date();
363
- const duration = endTime.getTime() - this.runStartTime.getTime();
364
- console.log(`Stanterprise Reporter: Suite ended - ${suite.title || 'root'}`);
365
- console.log(` Duration: ${duration}ms`);
366
- // Map result status to protobuf TestStatus
367
- const suiteStatus = this.mapSuiteStatus(result.status);
368
- // Build metadata from suite location
369
- const metadata = new Map();
370
- metadata.set("suite_type", suite.type);
371
- metadata.set("final_result", result.status);
372
- if (suite.location) {
373
- metadata.set("location_file", suite.location.file);
374
- metadata.set("location_line", suite.location.line.toString());
375
- metadata.set("location_column", suite.location.column.toString());
376
- }
377
- // Build and send the SuiteEnd event
378
- const request = new protobuf_1.events.SuiteEndEventRequest({
379
- suite: new protobuf_1.entities.TestSuiteRun({
380
- id: suiteId,
381
- name: suite.title || 'root',
382
- start_time: (0, utils_1.createTimestamp)(this.runStartTime),
383
- end_time: (0, utils_1.createTimestamp)(endTime),
384
- duration: (0, utils_1.createDuration)(duration),
385
- status: suiteStatus,
386
- metadata: metadata,
387
- }),
388
- });
389
- // Fire-and-forget to avoid slowing tests
390
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportSuiteEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report suite end", e));
391
- }
392
- // Helper: Map FullResult status to protobuf TestStatus
393
- mapSuiteStatus(status) {
394
- switch (status) {
395
- case "passed":
396
- return protobuf_1.common.TestStatus.PASSED;
397
- case "failed":
398
- return protobuf_1.common.TestStatus.FAILED;
399
- case "timedout":
400
- return protobuf_1.common.TestStatus.FAILED;
401
- case "interrupted":
402
- return protobuf_1.common.TestStatus.BROKEN;
403
- default:
404
- return protobuf_1.common.TestStatus.UNKNOWN;
405
- }
406
- }
407
- // Helper: log first gRPC error once and disable further attempts
408
- logGrpcErrorOnce(prefix, err) {
409
- if (this.grpcFirstErrorLogged)
410
- return;
411
- this.grpcFirstErrorLogged = true;
412
- const details = err instanceof Error ? `${err.message}` : String(err);
413
- console.warn(`${prefix}. gRPC disabled for the remainder of the run. Address=${this.grpcAddress}. Details: ${details}`);
414
- this.grpcEnabled = false;
415
- }
416
- // Helper: generic unary call using raw method path
417
- async reportUnary(path, message, deadlineMs = 1000) {
418
- if (!this.grpcEnabled || !this.grpcClient) {
419
- return Buffer.alloc(0);
420
- }
421
- const reqSerialize = (arg) => {
422
- const m = arg;
423
- const bytes = m?.serializeBinary
424
- ? m.serializeBinary()
425
- : m?.serialize
426
- ? m.serialize()
427
- : new Uint8Array(0);
428
- return Buffer.from(bytes);
429
- };
430
- const resDeserialize = (bytes) => bytes;
431
- const metadata = new grpc.Metadata();
432
- const callOptions = {
433
- deadline: new Date(Date.now() + deadlineMs),
434
- };
435
- return new Promise((resolve, reject) => {
436
- try {
437
- this.grpcClient.makeUnaryRequest(path, reqSerialize, resDeserialize, message, metadata, callOptions, (err, response) => {
438
- if (err)
439
- return reject(err);
440
- resolve(response);
441
- });
442
- }
443
- catch (e) {
444
- reject(e);
445
- }
446
- });
447
158
  }
448
159
  }
449
160
  exports.default = StanterpriseReporter;
package/dist/types.d.ts CHANGED
@@ -10,6 +10,19 @@ export interface StanterpriseReporterOptions {
10
10
  * @default "localhost:50051" or process.env.STANTERPRISE_GRPC_ADDRESS
11
11
  */
12
12
  grpcAddress?: string;
13
+ /**
14
+ * Optional separate host for gRPC when `grpcAddress` is not provided.
15
+ * This allows specifying gRPC connection details separately from other potential uses of `grpcAddress`.
16
+ * If `grpcAddress` is provided, it takes precedence over this field.
17
+ */
18
+ grpcHost?: string;
19
+ /**
20
+ * Optional separate port for gRPC when `grpcAddress` is not provided.
21
+ * This allows specifying gRPC connection details separately from other potential uses of `grpcAddress`.
22
+ * If `grpcAddress` is provided, it takes precedence over this field.
23
+ * @default 50051
24
+ */
25
+ grpcPort?: number;
13
26
  /**
14
27
  * Whether gRPC reporting is enabled
15
28
  * @default true or process.env.STANTERPRISE_GRPC_ENABLED !== "false"
@@ -20,11 +33,54 @@ export interface StanterpriseReporterOptions {
20
33
  * @default 1000
21
34
  */
22
35
  grpcTimeout?: number;
36
+ /**
37
+ * Maximum message size for gRPC calls in bytes
38
+ * @default 104857600 (100MB)
39
+ */
40
+ grpcMaxMessageSize?: number;
41
+ /**
42
+ * Maximum size for attachment content in bytes
43
+ * Attachments larger than this will only include the path reference
44
+ * @default 10485760 (10MB)
45
+ */
46
+ maxAttachmentSize?: number;
23
47
  /**
24
48
  * Whether to include verbose logging
25
49
  * @default false
26
50
  */
27
51
  verbose?: boolean;
52
+ /**
53
+ * Maximum number of retry attempts for failed gRPC calls
54
+ * @default 3
55
+ */
56
+ grpcMaxRetries?: number;
57
+ /**
58
+ * Initial delay between retry attempts in milliseconds
59
+ * Actual delay uses exponential backoff: initialDelay * (2 ^ attemptNumber)
60
+ * @default 100
61
+ */
62
+ grpcRetryDelay?: number;
63
+ /**
64
+ * Whether to enable debug mode, which writes all outgoing gRPC messages to a JSONL file.
65
+ * Warning: debug output may include full request payloads, including attachment content
66
+ * and metadata, which can contain sensitive values. Debug files may also grow very large.
67
+ * Enable this only for troubleshooting in controlled environments.
68
+ * @default false or process.env.STANTERPRISE_DEBUG === "true"
69
+ */
70
+ debug?: boolean;
71
+ /**
72
+ * File path for debug JSONL output when debug mode is enabled.
73
+ * Warning: the file may contain sensitive request data and can become very large,
74
+ * especially when attachments are included. Use only in controlled environments and
75
+ * handle the output file securely.
76
+ * @default "stanterprise-debug.jsonl" or process.env.STANTERPRISE_DEBUG_FILE
77
+ */
78
+ debugFile?: string;
79
+ /**
80
+ * Whether to use TLS for gRPC connections. If true, the client will attempt to establish a secure connection.
81
+ * @default true or process.env.STANTERPRISE_GRPC_TLS === "true"
82
+ */
83
+ tls?: boolean;
28
84
  }
29
85
  /**
30
86
  * Internal type for managing test execution state
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Utility functions for processing test attachments
3
3
  */
4
- import type { TestResult } from "@playwright/test/reporter";
4
+ import type { TestResult, TestStep } from "@playwright/test/reporter";
5
5
  import { common } from "@stanterprise/protobuf";
6
+ declare const Attachment: typeof common.v1.common.Attachment;
6
7
  /**
7
8
  * Process Playwright test attachments into protobuf Attachment objects
9
+ * @param result Test result or test step containing attachments
10
+ * @param maxAttachmentSize Maximum size for attachment content (default 1MB, reduced from 10MB)
8
11
  */
9
- export declare function processAttachments(result: TestResult): InstanceType<typeof common.Attachment>[];
12
+ export declare function processAttachments(result: TestResult | TestStep, maxAttachmentSize?: number): InstanceType<typeof Attachment>[];
10
13
  /**
11
14
  * Extract error information from test results
12
15
  */
@@ -15,3 +18,4 @@ export declare function extractErrorInfo(result: TestResult): {
15
18
  stackTrace: string;
16
19
  errors: string[];
17
20
  };
21
+ export {};