@stanterprise/playwright-reporter 0.0.18 → 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 +40 -0
- package/dist/client/grpcClient.d.ts +3 -0
- package/dist/client/grpcClient.js +78 -5
- 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,46 @@ 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
|
+
```
|
|
95
135
|
|
|
96
136
|
## Sharding Support
|
|
97
137
|
|
|
@@ -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
|
+
}
|
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": {
|