@stanterprise/playwright-reporter 0.0.17 → 0.0.19
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 +114 -1
- package/dist/client/grpcClient.d.ts +3 -0
- package/dist/client/grpcClient.js +78 -5
- package/dist/handlers/onBeginHandler.js +5 -0
- package/dist/reporter.js +3 -0
- package/dist/types.d.ts +11 -0
- package/dist/utils/optionsMapper.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,120 @@ npx playwright test
|
|
|
92
92
|
| `grpcMaxMessageSize` | number | `104857600` | Max message size in bytes (100MB default) |
|
|
93
93
|
| `maxAttachmentSize` | number | `10485760` | Max attachment content size in bytes (10MB default) |
|
|
94
94
|
| `verbose` | boolean | `false` | Enable verbose logging |
|
|
95
|
+
| `grpcMaxRetries` | number | `3` | Maximum number of retry attempts for failed gRPC calls |
|
|
96
|
+
| `grpcRetryDelay` | number | `100` | Initial delay for retries in ms (uses exponential backoff) |
|
|
97
|
+
|
|
98
|
+
### Retry Configuration
|
|
99
|
+
|
|
100
|
+
The reporter automatically retries failed gRPC calls for transient errors using exponential backoff:
|
|
101
|
+
|
|
102
|
+
- **Retryable errors**: `UNAVAILABLE`, `DEADLINE_EXCEEDED`, `INTERNAL`, `UNKNOWN`
|
|
103
|
+
- **Non-retryable errors**: `INVALID_ARGUMENT`, `NOT_FOUND`, `PERMISSION_DENIED`, `UNAUTHENTICATED`, `ALREADY_EXISTS`
|
|
104
|
+
|
|
105
|
+
**Exponential backoff formula**: `grpcRetryDelay * (2 ^ attemptNumber)`
|
|
106
|
+
|
|
107
|
+
Example with default settings (`grpcRetryDelay: 100`, `grpcMaxRetries: 3`):
|
|
108
|
+
- Initial attempt: Immediate
|
|
109
|
+
- Retry 1: Wait 100ms (100 * 2^0)
|
|
110
|
+
- Retry 2: Wait 200ms (100 * 2^1)
|
|
111
|
+
- Retry 3: Wait 400ms (100 * 2^2)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
export default defineConfig({
|
|
115
|
+
reporter: [
|
|
116
|
+
[
|
|
117
|
+
"stanterprise-playwright-reporter",
|
|
118
|
+
{
|
|
119
|
+
grpcMaxRetries: 5, // Try up to 5 times
|
|
120
|
+
grpcRetryDelay: 200, // Start with 200ms delay
|
|
121
|
+
verbose: true, // Log retry attempts
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
To disable retries:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
{
|
|
132
|
+
grpcMaxRetries: 0 // No retries, fail fast
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Sharding Support
|
|
137
|
+
|
|
138
|
+
The reporter automatically detects and reports when tests are running in sharded mode. When sharding is configured, the reporter includes the following metadata in test run events:
|
|
139
|
+
|
|
140
|
+
- `shard.current`: The current shard number (1-based)
|
|
141
|
+
- `shard.total`: The total number of shards
|
|
142
|
+
|
|
143
|
+
### Configuring Sharding
|
|
144
|
+
|
|
145
|
+
In your `playwright.config.ts`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { defineConfig } from "@playwright/test";
|
|
149
|
+
|
|
150
|
+
export default defineConfig({
|
|
151
|
+
shard: { total: 5, current: 1 },
|
|
152
|
+
reporter: [["stanterprise-playwright-reporter"]],
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or via CLI:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx playwright test --shard=1/5
|
|
160
|
+
npx playwright test --shard=2/5
|
|
161
|
+
# ... etc
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Aggregating Results Across Shards
|
|
165
|
+
|
|
166
|
+
**Important:** To have a complete set of tests accumulated in the observability server, you must specify the same `STANTERPRISE_RUN_ID` environment variable for all shards. This ensures that results from all shards are associated with the same test run.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Set the same run ID for all shards
|
|
170
|
+
export STANTERPRISE_RUN_ID="my-test-run-123"
|
|
171
|
+
|
|
172
|
+
# Run all shards with the same run ID
|
|
173
|
+
npx playwright test --shard=1/5
|
|
174
|
+
npx playwright test --shard=2/5
|
|
175
|
+
npx playwright test --shard=3/5
|
|
176
|
+
npx playwright test --shard=4/5
|
|
177
|
+
npx playwright test --shard=5/5
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
In CI/CD environments, you can use a unique identifier from your CI system:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# GitHub Actions example
|
|
184
|
+
STANTERPRISE_RUN_ID="${GITHUB_RUN_ID}" npx playwright test --shard=1/5
|
|
185
|
+
|
|
186
|
+
# GitLab CI example
|
|
187
|
+
STANTERPRISE_RUN_ID="${CI_PIPELINE_ID}" npx playwright test --shard=1/5
|
|
188
|
+
|
|
189
|
+
# Jenkins example
|
|
190
|
+
STANTERPRISE_RUN_ID="${BUILD_ID}" npx playwright test --shard=1/5
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Without a shared `STANTERPRISE_RUN_ID`, each shard will generate its own unique run ID, and the results will be reported as separate test runs rather than a single aggregated run.
|
|
194
|
+
|
|
195
|
+
The shard information is automatically included in the metadata sent to the server, allowing you to:
|
|
196
|
+
|
|
197
|
+
- Track which tests ran on which shard
|
|
198
|
+
- Aggregate results from multiple shards
|
|
199
|
+
- Debug shard-specific issues
|
|
200
|
+
|
|
201
|
+
When verbose logging is enabled, you'll see shard information in the console output:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
Stanterprise Reporter: Test run started with ID: 123e4567-e89b-12d3-a456-426614174000
|
|
205
|
+
Number of tests: 42
|
|
206
|
+
Run started at: 2026-01-25T10:30:00.000Z
|
|
207
|
+
Shard: 1 of 5
|
|
208
|
+
```
|
|
95
209
|
|
|
96
210
|
## What Gets Reported
|
|
97
211
|
|
|
@@ -240,7 +354,6 @@ This error can also occur when test event payloads exceed the gRPC message size
|
|
|
240
354
|
```
|
|
241
355
|
|
|
242
356
|
2. **Reduce attachment sizes**:
|
|
243
|
-
|
|
244
357
|
- Configure Playwright to save attachments to disk instead of embedding them
|
|
245
358
|
- The reporter automatically uses file paths when available instead of content
|
|
246
359
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { StanterpriseReporterOptions } from "../types";
|
|
2
2
|
import * as grpc from "@grpc/grpc-js";
|
|
3
3
|
export default function getClient(options: StanterpriseReporterOptions): grpc.Client | null;
|
|
4
|
+
/**
|
|
5
|
+
* Helper: generic unary call using raw method path with retry support
|
|
6
|
+
*/
|
|
4
7
|
export declare function reportUnary(options: StanterpriseReporterOptions, grpcClient: grpc.Client, path: string, message: {
|
|
5
8
|
serialize?: (w?: any) => Uint8Array;
|
|
6
9
|
serializeBinary?: () => Uint8Array;
|
|
@@ -49,11 +49,28 @@ function getClient(options) {
|
|
|
49
49
|
}
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Determines if a gRPC error code represents a transient failure worth retrying
|
|
54
|
+
*/
|
|
55
|
+
function shouldAttemptRetryForCode(errorCode) {
|
|
56
|
+
const transientCodes = new Set([
|
|
57
|
+
grpc.status.UNAVAILABLE,
|
|
58
|
+
grpc.status.DEADLINE_EXCEEDED,
|
|
59
|
+
grpc.status.INTERNAL,
|
|
60
|
+
grpc.status.UNKNOWN,
|
|
61
|
+
]);
|
|
62
|
+
return transientCodes.has(errorCode);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Calculates wait time using power-of-two exponential backoff
|
|
66
|
+
*/
|
|
67
|
+
function calculateWaitDuration(baseDelayMs, attemptsSoFar) {
|
|
68
|
+
return baseDelayMs * Math.pow(2, attemptsSoFar);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Performs a single gRPC unary request attempt
|
|
72
|
+
*/
|
|
73
|
+
function executeSingleAttempt(grpcClient, path, message, deadlineMs) {
|
|
57
74
|
const reqSerialize = (arg) => {
|
|
58
75
|
const m = arg;
|
|
59
76
|
const bytes = m?.serializeBinary
|
|
@@ -81,3 +98,59 @@ async function reportUnary(options, grpcClient, path, message, deadlineMs = 1000
|
|
|
81
98
|
}
|
|
82
99
|
});
|
|
83
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Pauses execution for specified milliseconds with unref'd timer to avoid blocking shutdown
|
|
103
|
+
*/
|
|
104
|
+
function pauseExecution(durationMs) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
const timer = setTimeout(resolve, durationMs);
|
|
107
|
+
// Unref the timer so it doesn't keep the process alive
|
|
108
|
+
timer.unref();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Attempts gRPC call with exponential backoff retry logic using iterative approach
|
|
113
|
+
*/
|
|
114
|
+
async function attemptWithRetries(options, grpcClient, path, message, deadlineMs, maxRetries) {
|
|
115
|
+
let attemptNumber = 0;
|
|
116
|
+
let lastError = null;
|
|
117
|
+
// Try initial attempt + retries
|
|
118
|
+
for (let retriesRemaining = maxRetries; retriesRemaining >= 0; retriesRemaining--) {
|
|
119
|
+
try {
|
|
120
|
+
return await executeSingleAttempt(grpcClient, path, message, deadlineMs);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
lastError = err;
|
|
124
|
+
const grpcErr = err;
|
|
125
|
+
const statusCode = grpcErr?.code ?? grpc.status.UNKNOWN;
|
|
126
|
+
// Check if we should retry this error
|
|
127
|
+
const isRetryable = shouldAttemptRetryForCode(statusCode);
|
|
128
|
+
const hasRetriesLeft = retriesRemaining > 0;
|
|
129
|
+
if (!isRetryable || !hasRetriesLeft) {
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
// Log retry attempt if verbose
|
|
133
|
+
if (options.verbose) {
|
|
134
|
+
console.log(`gRPC call failed (code: ${statusCode}), retrying... ` +
|
|
135
|
+
`(attempt ${attemptNumber + 1}, ${retriesRemaining} retries left)`);
|
|
136
|
+
}
|
|
137
|
+
// Calculate and wait for backoff duration
|
|
138
|
+
const baseDelay = options.grpcRetryDelay ?? 100;
|
|
139
|
+
const waitTime = calculateWaitDuration(baseDelay, attemptNumber);
|
|
140
|
+
await pauseExecution(waitTime);
|
|
141
|
+
attemptNumber++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// This should never be reached due to the throw in the loop, but TypeScript needs it
|
|
145
|
+
throw lastError || new Error("Retry loop exhausted without result");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Helper: generic unary call using raw method path with retry support
|
|
149
|
+
*/
|
|
150
|
+
async function reportUnary(options, grpcClient, path, message, deadlineMs = 1000) {
|
|
151
|
+
if (!options.grpcEnabled || !grpcClient) {
|
|
152
|
+
return Buffer.alloc(0);
|
|
153
|
+
}
|
|
154
|
+
const maxRetries = options.grpcMaxRetries ?? 3;
|
|
155
|
+
return attemptWithRetries(options, grpcClient, path, message, deadlineMs, maxRetries);
|
|
156
|
+
}
|
|
@@ -19,6 +19,11 @@ function handleOnBeginEvent(config, suite, name, runId, client, options) {
|
|
|
19
19
|
variables.forEach((value, key) => {
|
|
20
20
|
metadata[key] = value;
|
|
21
21
|
});
|
|
22
|
+
// Add shard information if present
|
|
23
|
+
if (config.shard) {
|
|
24
|
+
metadata["shard.current"] = String(config.shard.current);
|
|
25
|
+
metadata["shard.total"] = String(config.shard.total);
|
|
26
|
+
}
|
|
22
27
|
const request = new events_1.ReportRunStartEventRequest({
|
|
23
28
|
run_id: runId,
|
|
24
29
|
name: name,
|
package/dist/reporter.js
CHANGED
|
@@ -45,6 +45,9 @@ class StanterpriseReporter {
|
|
|
45
45
|
console.log(`Stanterprise Reporter: Test run started with ID: ${this.runId}`);
|
|
46
46
|
console.log(`Number of tests: ${suite.allTests().length}`);
|
|
47
47
|
console.log(`Run started at: ${this.runStartTime.toISOString()}`);
|
|
48
|
+
if (config.shard) {
|
|
49
|
+
console.log(`Shard: ${config.shard.current} of ${config.shard.total}`);
|
|
50
|
+
}
|
|
48
51
|
}
|
|
49
52
|
if (this.options.grpcEnabled) {
|
|
50
53
|
(0, handlers_1.handleOnBeginEvent)(config, suite, process.env.STANTERPRISE_TEST_RUN_NAME || "Playwright Test Run", this.runId, this.grpcClient, this.options);
|
package/dist/types.d.ts
CHANGED
|
@@ -36,6 +36,17 @@ export interface StanterpriseReporterOptions {
|
|
|
36
36
|
* @default false
|
|
37
37
|
*/
|
|
38
38
|
verbose?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum number of retry attempts for failed gRPC calls
|
|
41
|
+
* @default 3
|
|
42
|
+
*/
|
|
43
|
+
grpcMaxRetries?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Initial delay between retry attempts in milliseconds
|
|
46
|
+
* Actual delay uses exponential backoff: initialDelay * (2 ^ attemptNumber)
|
|
47
|
+
* @default 100
|
|
48
|
+
*/
|
|
49
|
+
grpcRetryDelay?: number;
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
52
|
* Internal type for managing test execution state
|
|
@@ -16,6 +16,10 @@ function defineOptions(providedOptions) {
|
|
|
16
16
|
providedOptions.grpcEnabled ??
|
|
17
17
|
(process.env.STANTERPRISE_GRPC_ENABLED || "true").toLowerCase() !== "false";
|
|
18
18
|
result.grpcTimeout = providedOptions.grpcTimeout || 1000;
|
|
19
|
+
result.grpcMaxMessageSize = providedOptions.grpcMaxMessageSize || 104857600;
|
|
20
|
+
result.maxAttachmentSize = providedOptions.maxAttachmentSize || 10485760;
|
|
19
21
|
result.verbose = providedOptions.verbose || false;
|
|
22
|
+
result.grpcMaxRetries = providedOptions.grpcMaxRetries ?? 3;
|
|
23
|
+
result.grpcRetryDelay = providedOptions.grpcRetryDelay || 100;
|
|
20
24
|
return result;
|
|
21
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stanterprise/playwright-reporter",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
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": {
|