@stanterprise/playwright-reporter 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,6 +45,8 @@ export default defineConfig({
45
45
  grpcAddress: "localhost:50051", // gRPC server address
46
46
  grpcEnabled: true, // Enable/disable gRPC reporting
47
47
  grpcTimeout: 1000, // Timeout for gRPC calls in milliseconds
48
+ grpcMaxMessageSize: 104857600, // Max message size (100MB default)
49
+ maxAttachmentSize: 10485760, // Max attachment size (10MB default)
48
50
  verbose: false, // Enable verbose logging
49
51
  },
50
52
  ],
@@ -66,6 +68,7 @@ You can also configure the reporter using environment variables:
66
68
  Any environment variable with the `STANTERPRISE_META_` prefix will be automatically included in the test run metadata. The prefix is stripped from the key name.
67
69
 
68
70
  For example:
71
+
69
72
  - `STANTERPRISE_META_BUILD_ID=12345` becomes `BUILD_ID: 12345` in metadata
70
73
  - `STANTERPRISE_META_BRANCH=main` becomes `BRANCH: main` in metadata
71
74
  - `STANTERPRISE_META_COMMIT_SHA=abc123` becomes `COMMIT_SHA: abc123` in metadata
@@ -81,12 +84,14 @@ npx playwright test
81
84
 
82
85
  ## Configuration Options
83
86
 
84
- | Option | Type | Default | Description |
85
- | -------------- | ------- | ---------------- | ----------------------------------------- |
86
- | `grpcAddress` | string | `localhost:50051`| gRPC server address |
87
- | `grpcEnabled` | boolean | `true` | Enable/disable gRPC reporting |
88
- | `grpcTimeout` | number | `1000` | Timeout for gRPC calls in milliseconds |
89
- | `verbose` | boolean | `false` | Enable verbose logging |
87
+ | Option | Type | Default | Description |
88
+ | -------------------- | ------- | ----------------- | --------------------------------------------------- |
89
+ | `grpcAddress` | string | `localhost:50051` | gRPC server address |
90
+ | `grpcEnabled` | boolean | `true` | Enable/disable gRPC reporting |
91
+ | `grpcTimeout` | number | `1000` | Timeout for gRPC calls in milliseconds |
92
+ | `grpcMaxMessageSize` | number | `104857600` | Max message size in bytes (100MB default) |
93
+ | `maxAttachmentSize` | number | `10485760` | Max attachment content size in bytes (10MB default) |
94
+ | `verbose` | boolean | `false` | Enable verbose logging |
90
95
 
91
96
  ## What Gets Reported
92
97
 
@@ -104,6 +109,7 @@ npx playwright test
104
109
  ### Attachments
105
110
 
106
111
  The reporter automatically processes and sends:
112
+
107
113
  - Screenshots
108
114
  - Videos
109
115
  - Trace files
@@ -146,6 +152,8 @@ const reporter = new StanterpriseReporter({
146
152
  grpcAddress: "localhost:50051",
147
153
  grpcEnabled: true,
148
154
  grpcTimeout: 1000,
155
+ grpcMaxMessageSize: 104857600, // 100MB
156
+ maxAttachmentSize: 10485760, // 10MB
149
157
  verbose: false,
150
158
  });
151
159
  ```
@@ -200,6 +208,59 @@ The reporter is organized into several modules:
200
208
 
201
209
  ## Troubleshooting
202
210
 
211
+ ### gRPC Error 13 INTERNAL: failed to publish event
212
+
213
+ This error typically occurs due to one of two issues:
214
+
215
+ **1. Metadata Serialization (Most Common)**
216
+
217
+ If you see this error with the stack trace mentioning "metadata is not iterable" or serialization issues, it was a bug in v0.0.13 and earlier versions. **Solution: Upgrade to v0.0.14 or later.**
218
+
219
+ **2. Large Attachments**
220
+
221
+ This error can also occur when test event payloads exceed the gRPC message size limit. Common causes:
222
+
223
+ 1. **Large attachments**: Tests with large screenshots, videos, or trace files
224
+ 2. **Default size limits**: gRPC has a default 4MB message size limit
225
+
226
+ **Solutions:**
227
+
228
+ 1. **Increase message size limits** (recommended):
229
+
230
+ ```typescript
231
+ reporter: [
232
+ [
233
+ "stanterprise-playwright-reporter",
234
+ {
235
+ grpcMaxMessageSize: 104857600, // 100MB (default)
236
+ maxAttachmentSize: 10485760, // 10MB (default)
237
+ },
238
+ ],
239
+ ],
240
+ ```
241
+
242
+ 2. **Reduce attachment sizes**:
243
+
244
+ - Configure Playwright to save attachments to disk instead of embedding them
245
+ - The reporter automatically uses file paths when available instead of content
246
+
247
+ ```typescript
248
+ use: {
249
+ screenshot: "only-on-failure",
250
+ video: "retain-on-failure",
251
+ trace: "retain-on-failure",
252
+ },
253
+ ```
254
+
255
+ 3. **Monitor payload sizes**: Enable verbose logging to see actual payload sizes:
256
+ ```typescript
257
+ reporter: [
258
+ ["stanterprise-playwright-reporter", { verbose: true }],
259
+ ],
260
+ ```
261
+
262
+ The reporter will log warnings when attachments exceed the size limit and automatically skip their content while preserving metadata.
263
+
203
264
  ### Connection Issues
204
265
 
205
266
  If you see gRPC connection errors:
@@ -229,6 +290,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
229
290
  The repository uses GitHub Actions to automatically publish to NPM when a version tag is pushed:
230
291
 
231
292
  1. Update the version following [semantic versioning](https://semver.org/):
293
+
232
294
  ```bash
233
295
  npm version patch # for bug fixes (1.0.0 → 1.0.1)
234
296
  npm version minor # for new features (1.0.0 → 1.1.0)
@@ -236,6 +298,7 @@ The repository uses GitHub Actions to automatically publish to NPM when a versio
236
298
  ```
237
299
 
238
300
  2. Push the version tag to GitHub:
301
+
239
302
  ```bash
240
303
  git push --follow-tags
241
304
  ```
@@ -252,16 +315,19 @@ The repository uses GitHub Actions to automatically publish to NPM when a versio
252
315
  Alternatively, you can publish manually:
253
316
 
254
317
  1. Ensure all tests pass:
318
+
255
319
  ```bash
256
320
  npm test
257
321
  ```
258
322
 
259
323
  2. Update the version:
324
+
260
325
  ```bash
261
326
  npm version patch|minor|major
262
327
  ```
263
328
 
264
329
  3. Publish to NPM (prepublishOnly script will build automatically):
330
+
265
331
  ```bash
266
332
  npm publish
267
333
  ```
@@ -274,10 +340,12 @@ Alternatively, you can publish manually:
274
340
  ### What Gets Published
275
341
 
276
342
  The package includes:
343
+
277
344
  - `dist/` - Compiled JavaScript and TypeScript declarations
278
345
  - `README.md` - Documentation
279
346
 
280
347
  The following are excluded via `.npmignore`:
348
+
281
349
  - Source files (`src/`, `tests/`, `examples/`)
282
350
  - Development configuration files
283
351
  - Build artifacts and logs
@@ -289,4 +357,3 @@ ISC - See [LICENSE](LICENSE) file for details
289
357
  ## Support
290
358
 
291
359
  For issues and questions, please visit the [GitHub repository](https://github.com/stanterprise/stanterprise-playwright-reporter).
292
-
@@ -38,7 +38,11 @@ exports.reportUnary = reportUnary;
38
38
  const grpc = __importStar(require("@grpc/grpc-js"));
39
39
  function getClient(options) {
40
40
  try {
41
- return new grpc.Client(options.grpcAddress, grpc.credentials.createInsecure());
41
+ const maxMessageSize = options.grpcMaxMessageSize || 104857600; // 100MB default
42
+ return new grpc.Client(options.grpcAddress, grpc.credentials.createInsecure(), {
43
+ "grpc.max_send_message_length": maxMessageSize,
44
+ "grpc.max_receive_message_length": maxMessageSize,
45
+ });
42
46
  }
43
47
  catch (e) {
44
48
  console.error("Failed to create gRPC client", e);
@@ -9,21 +9,22 @@ const common_1 = require("@stanterprise/protobuf/testsystem/v1/common");
9
9
  function handleOnBeginEvent(config, suite, name, runId, client, options) {
10
10
  // Report root suite and all child suites recursively
11
11
  // Convert all metadata values to strings for protobuf compatibility
12
- const metadataMap = new Map(Object.entries(config.metadata || {}).map(([key, value]) => [
13
- key,
14
- String(value),
15
- ]));
12
+ // Use plain object instead of Map - protobuf expects object literals
13
+ const metadata = {};
14
+ Object.entries(config.metadata || {}).forEach(([key, value]) => {
15
+ metadata[key] = String(value);
16
+ });
16
17
  const variables = (0, utils_1.getEnvVariables)();
17
18
  // Merge environment variables into metadata
18
19
  variables.forEach((value, key) => {
19
- metadataMap.set(key, value);
20
+ metadata[key] = value;
20
21
  });
21
22
  const request = new events_1.ReportRunStartEventRequest({
22
23
  run_id: runId,
23
24
  name: name,
24
25
  test_suites: mapSuites(suite, runId),
25
26
  total_tests: suite.allTests().length,
26
- metadata: metadataMap,
27
+ metadata: (0, utils_1.toMetadataMap)(metadata),
27
28
  });
28
29
  (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportRunStart", request, options.grpcTimeout).catch((e) => {
29
30
  const details = e instanceof Error ? `${e.message}` : String(e);
@@ -9,14 +9,7 @@ const utils_2 = require("../utils");
9
9
  const common_1 = require("@stanterprise/protobuf/testsystem/v1/common");
10
10
  function handleOnStepBeginEvent(test, result, step, runId, client, options) {
11
11
  // Build metadata from step annotations
12
- const metadata = new Map();
13
- metadata.set("category", step.category);
14
- step.annotations.forEach((annotation, index) => {
15
- metadata.set(`annotation_${index}_type`, annotation.type);
16
- if (annotation.description) {
17
- metadata.set(`annotation_${index}_description`, annotation.description);
18
- }
19
- });
12
+ const metadata = (0, utils_1.buildStepMetadata)(step);
20
13
  // Get parent step ID if this step has a parent
21
14
  const stepId = (0, utils_2.generateStepId)(step, test);
22
15
  // Build and send the StepBegin event
@@ -32,7 +25,7 @@ function handleOnStepBeginEvent(test, result, step, runId, client, options) {
32
25
  location: step.location
33
26
  ? `${step.location.file}:${step.location.line}:${step.location.column}`
34
27
  : undefined,
35
- metadata: metadata,
28
+ metadata: (0, utils_1.toMetadataMap)(metadata),
36
29
  parent_step_id: step.parent
37
30
  ? (0, utils_2.generateStepId)(step.parent, test)
38
31
  : undefined,
@@ -10,14 +10,7 @@ function handleOnStepEndEvent(test, result, step, runId, client, options) {
10
10
  // Map step error to status
11
11
  const stepStatus = (0, utils_1.mapStepStatus)(!!step.error);
12
12
  // Build metadata from step annotations
13
- const metadata = new Map();
14
- metadata.set("category", step.category);
15
- step.annotations.forEach((annotation, index) => {
16
- metadata.set(`annotation_${index}_type`, annotation.type);
17
- if (annotation.description) {
18
- metadata.set(`annotation_${index}_description`, annotation.description);
19
- }
20
- });
13
+ const metadata = (0, utils_1.buildStepMetadata)(step);
21
14
  const stepId = (0, utils_2.generateStepId)(step, test);
22
15
  // Build and send the StepEnd event
23
16
  const request = new events_1.StepEndEventRequest({
@@ -35,7 +28,7 @@ function handleOnStepEndEvent(test, result, step, runId, client, options) {
35
28
  location: step.location
36
29
  ? `${step.location.file}:${step.location.line}:${step.location.column}`
37
30
  : "",
38
- metadata: metadata,
31
+ metadata: (0, utils_1.toMetadataMap)(metadata),
39
32
  parent_step_id: step.parent
40
33
  ? (0, utils_2.generateStepId)(step.parent, test)
41
34
  : undefined,
@@ -6,29 +6,21 @@ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
6
  const utils_1 = require("../utils");
7
7
  const grpcClient_1 = require("../client/grpcClient");
8
8
  const utils_2 = require("../utils");
9
+ /**
10
+ * Constant for converting bytes to megabytes
11
+ */
12
+ const BYTES_PER_MB = 1048576;
9
13
  function handleOnTestEndEvent(test, result, runId, client, options) {
10
14
  // Map Playwright test status to protobuf TestStatus
11
15
  const testStatus = (0, utils_1.mapTestStatus)(result.status);
12
16
  // Process attachments (screenshots, videos, etc.)
13
- const attachments = (0, utils_1.processAttachments)(result);
17
+ const attachments = (0, utils_1.processAttachments)(result, options.maxAttachmentSize || 10485760);
14
18
  // Extract error information if the test failed
15
19
  const { errorMessage, stackTrace, errors } = (0, utils_1.extractErrorInfo)(result);
16
20
  // Get test suite run ID from parent suite if available, ensuring suite is reported
17
21
  const suiteId = (0, utils_2.generateSuiteId)(test.parent);
18
22
  // Build metadata from test annotations and result metadata
19
- const metadata = new Map();
20
- test.annotations.forEach((annotation, index) => {
21
- metadata.set(`annotation_${index}_type`, annotation.type);
22
- if (annotation.description) {
23
- metadata.set(`annotation_${index}_description`, annotation.description);
24
- }
25
- });
26
- result.annotations.forEach((annotation, index) => {
27
- metadata.set(`result_annotation_${index}_type`, annotation.type);
28
- if (annotation.description) {
29
- metadata.set(`result_annotation_${index}_description`, annotation.description);
30
- }
31
- });
23
+ const metadata = (0, utils_1.buildTestMetadata)(test, result);
32
24
  // Build and send the TestEnd event
33
25
  const request = new events_1.TestEndEventRequest({
34
26
  test_case: new entities_1.TestCaseRun({
@@ -43,10 +35,15 @@ function handleOnTestEndEvent(test, result, runId, client, options) {
43
35
  error_message: errorMessage,
44
36
  stack_trace: stackTrace,
45
37
  errors: errors,
46
- metadata: metadata,
38
+ metadata: (0, utils_1.toMetadataMap)(metadata),
47
39
  tags: test.tags,
48
40
  }),
49
41
  });
42
+ // Serialize once to capture payload size for potential error logging
43
+ const serializedPayload = request.serializeBinary();
44
+ const payloadSize = serializedPayload.length;
50
45
  // Fire-and-forget to avoid slowing tests
51
- (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportTestEnd", request, options.grpcTimeout).catch((e) => console.error("Failed to report test end", e));
46
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportTestEnd", request, options.grpcTimeout).catch((e) => {
47
+ console.error(`Failed to report test end for "${test.title}" (payload size: ${(payloadSize / BYTES_PER_MB).toFixed(2)}MB)`, e);
48
+ });
52
49
  }
package/dist/types.d.ts CHANGED
@@ -20,6 +20,17 @@ export interface StanterpriseReporterOptions {
20
20
  * @default 1000
21
21
  */
22
22
  grpcTimeout?: number;
23
+ /**
24
+ * Maximum message size for gRPC calls in bytes
25
+ * @default 104857600 (100MB)
26
+ */
27
+ grpcMaxMessageSize?: number;
28
+ /**
29
+ * Maximum size for attachment content in bytes
30
+ * Attachments larger than this will only include the path reference
31
+ * @default 10485760 (10MB)
32
+ */
33
+ maxAttachmentSize?: number;
23
34
  /**
24
35
  * Whether to include verbose logging
25
36
  * @default false
@@ -6,8 +6,10 @@ import { common } from "@stanterprise/protobuf";
6
6
  declare const Attachment: typeof common.v1.common.Attachment;
7
7
  /**
8
8
  * Process Playwright test attachments into protobuf Attachment objects
9
+ * @param result Test result containing attachments
10
+ * @param maxAttachmentSize Maximum size for attachment content (default 10MB)
9
11
  */
10
- export declare function processAttachments(result: TestResult): InstanceType<typeof Attachment>[];
12
+ export declare function processAttachments(result: TestResult, maxAttachmentSize?: number): InstanceType<typeof Attachment>[];
11
13
  /**
12
14
  * Extract error information from test results
13
15
  */
@@ -4,10 +4,17 @@ exports.processAttachments = processAttachments;
4
4
  exports.extractErrorInfo = extractErrorInfo;
5
5
  const protobuf_1 = require("@stanterprise/protobuf");
6
6
  const Attachment = protobuf_1.common.v1.common.Attachment;
7
+ /**
8
+ * Constant for converting bytes to megabytes
9
+ */
10
+ const BYTES_PER_MB = 1048576;
7
11
  /**
8
12
  * Process Playwright test attachments into protobuf Attachment objects
13
+ * @param result Test result containing attachments
14
+ * @param maxAttachmentSize Maximum size for attachment content (default 10MB)
9
15
  */
10
- function processAttachments(result) {
16
+ function processAttachments(result, maxAttachmentSize = 10485760 // 10MB default
17
+ ) {
11
18
  const attachments = [];
12
19
  if (!result.attachments || result.attachments.length === 0) {
13
20
  return attachments;
@@ -17,12 +24,22 @@ function processAttachments(result) {
17
24
  name: attachment.name,
18
25
  mime_type: attachment.contentType,
19
26
  });
20
- // Use path as URI if available, otherwise use the body content
27
+ // Always prefer path if available to avoid large payloads
21
28
  if (attachment.path) {
22
29
  att.uri = attachment.path;
23
30
  }
24
31
  else if (attachment.body) {
25
- att.content = attachment.body;
32
+ // Only include body content if it's within size limit
33
+ const bodySize = attachment.body.length;
34
+ if (bodySize <= maxAttachmentSize) {
35
+ att.content = attachment.body;
36
+ }
37
+ else {
38
+ // Skip large attachments without path - log warning
39
+ console.warn(`Attachment "${attachment.name}" (${(bodySize / BYTES_PER_MB).toFixed(2)}MB) exceeds max size ` +
40
+ `(${(maxAttachmentSize / BYTES_PER_MB).toFixed(2)}MB) and has no path. Skipping content.`);
41
+ // Still add the attachment metadata without content
42
+ }
26
43
  }
27
44
  attachments.push(att);
28
45
  }
@@ -6,3 +6,4 @@ export * from "./attachmentProcessor";
6
6
  export * from "./timeHelpers";
7
7
  export * from "./idGenerators";
8
8
  export * from "./envRetriever";
9
+ export * from "./metadataHelpers";
@@ -22,3 +22,4 @@ __exportStar(require("./attachmentProcessor"), exports);
22
22
  __exportStar(require("./timeHelpers"), exports);
23
23
  __exportStar(require("./idGenerators"), exports);
24
24
  __exportStar(require("./envRetriever"), exports);
25
+ __exportStar(require("./metadataHelpers"), exports);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utility functions for building metadata objects
3
+ */
4
+ import type { TestCase, TestResult, TestStep } from "@playwright/test/reporter";
5
+ /**
6
+ * Build metadata from annotations array
7
+ * @param annotations Array of annotations with type and optional description
8
+ * @param prefix Prefix for metadata keys (e.g., "annotation", "result_annotation")
9
+ * @returns Record of metadata key-value pairs
10
+ */
11
+ export declare function buildAnnotationsMetadata(annotations: Array<{
12
+ type: string;
13
+ description?: string;
14
+ }>, prefix?: string): Record<string, string>;
15
+ /**
16
+ * Build metadata from test case and result annotations
17
+ * Combines annotations from both the test case and the result
18
+ * @param test Test case containing annotations
19
+ * @param result Test result containing additional annotations
20
+ * @returns Record of metadata key-value pairs
21
+ */
22
+ export declare function buildTestMetadata(test: TestCase, result: TestResult): Record<string, string>;
23
+ /**
24
+ * Build metadata from step annotations and category
25
+ * @param step Test step containing annotations and category
26
+ * @returns Record of metadata key-value pairs
27
+ */
28
+ export declare function buildStepMetadata(step: TestStep): Record<string, string>;
29
+ /**
30
+ * Convert metadata object to Map for protobuf serialization
31
+ * Protobuf requires Map<string, string> for metadata fields
32
+ * @param metadata Record of metadata key-value pairs
33
+ * @returns Map ready for protobuf constructors
34
+ */
35
+ export declare function toMetadataMap(metadata: Record<string, string>): Map<string, string>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAnnotationsMetadata = buildAnnotationsMetadata;
4
+ exports.buildTestMetadata = buildTestMetadata;
5
+ exports.buildStepMetadata = buildStepMetadata;
6
+ exports.toMetadataMap = toMetadataMap;
7
+ /**
8
+ * Build metadata from annotations array
9
+ * @param annotations Array of annotations with type and optional description
10
+ * @param prefix Prefix for metadata keys (e.g., "annotation", "result_annotation")
11
+ * @returns Record of metadata key-value pairs
12
+ */
13
+ function buildAnnotationsMetadata(annotations, prefix = "annotation") {
14
+ const metadata = {};
15
+ annotations.forEach((annotation, index) => {
16
+ metadata[`${prefix}_${index}_type`] = annotation.type;
17
+ if (annotation.description) {
18
+ metadata[`${prefix}_${index}_description`] = annotation.description;
19
+ }
20
+ });
21
+ return metadata;
22
+ }
23
+ /**
24
+ * Build metadata from test case and result annotations
25
+ * Combines annotations from both the test case and the result
26
+ * @param test Test case containing annotations
27
+ * @param result Test result containing additional annotations
28
+ * @returns Record of metadata key-value pairs
29
+ */
30
+ function buildTestMetadata(test, result) {
31
+ return {
32
+ ...buildAnnotationsMetadata(test.annotations, "annotation"),
33
+ ...buildAnnotationsMetadata(result.annotations, "result_annotation"),
34
+ };
35
+ }
36
+ /**
37
+ * Build metadata from step annotations and category
38
+ * @param step Test step containing annotations and category
39
+ * @returns Record of metadata key-value pairs
40
+ */
41
+ function buildStepMetadata(step) {
42
+ return {
43
+ category: step.category,
44
+ ...buildAnnotationsMetadata(step.annotations, "annotation"),
45
+ };
46
+ }
47
+ /**
48
+ * Convert metadata object to Map for protobuf serialization
49
+ * Protobuf requires Map<string, string> for metadata fields
50
+ * @param metadata Record of metadata key-value pairs
51
+ * @returns Map ready for protobuf constructors
52
+ */
53
+ function toMetadataMap(metadata) {
54
+ return new Map(Object.entries(metadata));
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stanterprise/playwright-reporter",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Custom Playwright test reporter that sends test results to Stanterprise via gRPC",
5
5
  "homepage": "https://github.com/stanterprise/stanterprise-playwright-reporter#readme",
6
6
  "bugs": {