@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.
- package/README.md +261 -11
- package/dist/client/grpcClient.d.ts +16 -0
- package/dist/client/grpcClient.js +172 -0
- package/dist/client/messageQueue.d.ts +27 -0
- package/dist/client/messageQueue.js +71 -0
- package/dist/handlers/index.d.ts +9 -0
- package/dist/handlers/index.js +19 -0
- package/dist/handlers/onBeginHandler.d.ts +6 -0
- package/dist/handlers/onBeginHandler.js +98 -0
- package/dist/handlers/onEndHandler.d.ts +5 -0
- package/dist/handlers/onEndHandler.js +53 -0
- package/dist/handlers/onErrorHandler.d.ts +5 -0
- package/dist/handlers/onErrorHandler.js +16 -0
- package/dist/handlers/onStepBeginHandler.d.ts +5 -0
- package/dist/handlers/onStepBeginHandler.js +39 -0
- package/dist/handlers/onStepEndHandler.d.ts +5 -0
- package/dist/handlers/onStepEndHandler.js +62 -0
- package/dist/handlers/onTestBeginHandler.d.ts +5 -0
- package/dist/handlers/onTestBeginHandler.js +33 -0
- package/dist/handlers/onTestEndHandler.d.ts +5 -0
- package/dist/handlers/onTestEndHandler.js +58 -0
- package/dist/handlers/onTestFailHandler.d.ts +5 -0
- package/dist/handlers/onTestFailHandler.js +25 -0
- package/dist/reporter.d.ts +4 -15
- package/dist/reporter.js +97 -386
- package/dist/types.d.ts +56 -0
- package/dist/utils/attachmentProcessor.d.ts +6 -2
- package/dist/utils/attachmentProcessor.js +33 -4
- package/dist/utils/debugLogger.d.ts +13 -0
- package/dist/utils/debugLogger.js +87 -0
- package/dist/utils/envRetriever.d.ts +9 -0
- package/dist/utils/envRetriever.js +23 -0
- package/dist/utils/idGenerators.d.ts +20 -0
- package/dist/utils/idGenerators.js +40 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/metadataHelpers.d.ts +42 -0
- package/dist/utils/metadataHelpers.js +91 -0
- package/dist/utils/optionsMapper.d.ts +7 -0
- package/dist/utils/optionsMapper.js +51 -0
- package/dist/utils/statusMapper.js +7 -6
- package/dist/utils/timeHelpers.d.ts +5 -5
- package/dist/utils/timeHelpers.js +24 -8
- package/package.json +6 -7
package/dist/reporter.js
CHANGED
|
@@ -1,95 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
this.
|
|
52
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
288
|
-
if (this.verbose) {
|
|
134
|
+
if (this.options.verbose) {
|
|
289
135
|
console.log(`Stanterprise Reporter: Test failed - ${test.title}`);
|
|
290
136
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
|
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 {};
|