playwright-api-logger 2.0.0 → 2.2.0
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 +104 -73
- package/dist/ApiLogger.d.ts +40 -31
- package/dist/ApiLogger.d.ts.map +1 -1
- package/dist/ApiLogger.js +108 -83
- package/dist/ApiLogger.js.map +1 -1
- package/dist/LoggerRegistry.d.ts +59 -0
- package/dist/LoggerRegistry.d.ts.map +1 -0
- package/dist/LoggerRegistry.js +80 -0
- package/dist/LoggerRegistry.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +32 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/withApiLogging.d.ts +13 -3
- package/dist/withApiLogging.d.ts.map +1 -1
- package/dist/withApiLogging.js +49 -48
- package/dist/withApiLogging.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
## How It Works
|
|
22
22
|
|
|
23
23
|
```mermaid
|
|
24
|
-
flowchart
|
|
24
|
+
flowchart LR
|
|
25
25
|
subgraph T[Playwright Test]
|
|
26
26
|
F[Fixture setup]
|
|
27
27
|
C[API Call GET/POST/PUT/DELETE]
|
|
@@ -56,10 +56,10 @@ API_LOGS=false → Logging OFF (zero overhead, default)
|
|
|
56
56
|
## Features
|
|
57
57
|
|
|
58
58
|
- **One-line integration** — just wrap `request` with `withApiLogging()`, zero changes to controllers/clients
|
|
59
|
-
- **
|
|
59
|
+
- **Structured logs** — one JSON document per test with `preconditions`, `steps`, and `teardown` sections
|
|
60
|
+
- **Step descriptions** — describe what each API call does with `.describe()`
|
|
60
61
|
- **Curl Export** — copy from log, paste into terminal or import into Postman
|
|
61
62
|
- **Env Control** — `API_LOGS=true/false` (default: `false`, zero overhead when off)
|
|
62
|
-
- **Context Tracking** — setup / test / teardown phases
|
|
63
63
|
- **Token Masking** — Authorization headers are automatically masked
|
|
64
64
|
- **Form Data** — JSON, URL-encoded, and multipart/form-data support
|
|
65
65
|
- **Error Resilient** — logging never breaks your tests
|
|
@@ -79,15 +79,38 @@ import { withApiLogging } from 'playwright-api-logger';
|
|
|
79
79
|
|
|
80
80
|
export const test = base.extend({
|
|
81
81
|
apiClient: async ({ request }, use, testInfo) => {
|
|
82
|
-
|
|
83
|
-
const apiClient = new ApiClient(
|
|
82
|
+
const loggedRequest = withApiLogging(request, testInfo);
|
|
83
|
+
const apiClient = new ApiClient(loggedRequest);
|
|
84
84
|
await use(apiClient);
|
|
85
|
+
loggedRequest.__logger.finalize(
|
|
86
|
+
testInfo.status === 'passed' ? 'PASSED' : 'FAILED'
|
|
87
|
+
);
|
|
85
88
|
},
|
|
86
89
|
});
|
|
87
90
|
```
|
|
88
91
|
|
|
89
92
|
No changes to your controllers, clients, or test files.
|
|
90
93
|
|
|
94
|
+
### With preconditions and step descriptions
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
test('GET Without token (401)', async ({ apiClient, request }) => {
|
|
98
|
+
const loggedRequest = (request as any).__logger as ApiLogger;
|
|
99
|
+
|
|
100
|
+
// Mark following calls as preconditions
|
|
101
|
+
loggedRequest.startPreconditions();
|
|
102
|
+
loggedRequest.describe('Get employee ID for test');
|
|
103
|
+
const employees = await apiClient.getEmployees({ page: 1, size: 1 });
|
|
104
|
+
const employeeId = employees.items[0].id;
|
|
105
|
+
|
|
106
|
+
// Switch to test steps
|
|
107
|
+
loggedRequest.startTest();
|
|
108
|
+
loggedRequest.describe('Get children without auth token');
|
|
109
|
+
const response = await apiClient.getChildrenWithoutAuth(employeeId);
|
|
110
|
+
expect(response.status).toBe(401);
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
91
114
|
### Enable via environment variable
|
|
92
115
|
|
|
93
116
|
```bash
|
|
@@ -100,61 +123,70 @@ API_LOGS=false
|
|
|
100
123
|
API_LOGS=true npx playwright test
|
|
101
124
|
```
|
|
102
125
|
|
|
103
|
-
### Finalize with test result (optional)
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
apiClient: async ({ request }, use, testInfo) => {
|
|
107
|
-
const loggedRequest = withApiLogging(request, testInfo);
|
|
108
|
-
const apiClient = new ApiClient(loggedRequest);
|
|
109
|
-
await use(apiClient);
|
|
110
|
-
|
|
111
|
-
// Write PASSED/FAILED to log
|
|
112
|
-
loggedRequest.__logger.finalize(
|
|
113
|
-
testInfo.status === 'passed' ? 'PASSED' : 'FAILED'
|
|
114
|
-
);
|
|
115
|
-
},
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Setup / Teardown logging
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// In beforeAll
|
|
122
|
-
const loggedRequest = withApiLogging(request, { testName: 'auth-setup', context: 'setup' });
|
|
123
|
-
|
|
124
|
-
// In afterAll
|
|
125
|
-
const loggedRequest = withApiLogging(request, { testName: 'cleanup', context: 'teardown' });
|
|
126
|
-
```
|
|
127
|
-
|
|
128
126
|
## Log Output
|
|
129
127
|
|
|
130
|
-
|
|
128
|
+
One structured JSON document per test:
|
|
131
129
|
|
|
132
130
|
```
|
|
133
131
|
logs/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
TEARDOWN_cleanup_2026-03-16T12-00-00.log
|
|
132
|
+
get-without-token-401_2026-03-16T18-33-03.log
|
|
133
|
+
create-employee_2026-03-16T18-35-10.log
|
|
137
134
|
```
|
|
138
135
|
|
|
139
|
-
|
|
136
|
+
### Example log:
|
|
140
137
|
|
|
141
138
|
```json
|
|
142
139
|
{
|
|
143
|
-
"
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"body": { "name": "John" }
|
|
140
|
+
"test": {
|
|
141
|
+
"name": "GET Without token (401)",
|
|
142
|
+
"file": "tests/api/employees/children.spec.ts",
|
|
143
|
+
"startedAt": "2026-03-16T18:33:03.654Z",
|
|
144
|
+
"finishedAt": "2026-03-16T18:33:04.300Z",
|
|
145
|
+
"duration": 646,
|
|
146
|
+
"result": "PASSED"
|
|
151
147
|
},
|
|
152
|
-
"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
"preconditions": [
|
|
149
|
+
{
|
|
150
|
+
"step": 1,
|
|
151
|
+
"description": "Get employee ID for test",
|
|
152
|
+
"timestamp": "2026-03-16T18:33:04.174Z",
|
|
153
|
+
"request": {
|
|
154
|
+
"method": "GET",
|
|
155
|
+
"url": "https://api.example.com/employees?page=1&size=1"
|
|
156
|
+
},
|
|
157
|
+
"response": {
|
|
158
|
+
"status": 200,
|
|
159
|
+
"body": { "items": [{ "id": "abc-123" }], "total": 27 }
|
|
160
|
+
},
|
|
161
|
+
"duration": 501,
|
|
162
|
+
"curl": "curl -X GET 'https://api.example.com/employees?page=1&size=1' -H 'Accept: application/json'"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"steps": [
|
|
166
|
+
{
|
|
167
|
+
"step": 1,
|
|
168
|
+
"description": "Get children without auth token",
|
|
169
|
+
"timestamp": "2026-03-16T18:33:04.242Z",
|
|
170
|
+
"request": {
|
|
171
|
+
"method": "GET",
|
|
172
|
+
"url": "https://api.example.com/employees/abc-123/children"
|
|
173
|
+
},
|
|
174
|
+
"response": {
|
|
175
|
+
"status": 401,
|
|
176
|
+
"body": { "detail": "Not authenticated" }
|
|
177
|
+
},
|
|
178
|
+
"duration": 67,
|
|
179
|
+
"curl": "curl -X GET 'https://api.example.com/employees/abc-123/children'"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"teardown": [],
|
|
183
|
+
"summary": {
|
|
184
|
+
"totalRequests": 2,
|
|
185
|
+
"preconditions": 1,
|
|
186
|
+
"testSteps": 1,
|
|
187
|
+
"teardown": 0,
|
|
188
|
+
"totalDuration": 568
|
|
189
|
+
}
|
|
158
190
|
}
|
|
159
191
|
```
|
|
160
192
|
|
|
@@ -165,28 +197,22 @@ Each log entry is a JSON object:
|
|
|
165
197
|
Main integration point. Wraps `APIRequestContext` with a Proxy that logs all HTTP calls.
|
|
166
198
|
|
|
167
199
|
```typescript
|
|
168
|
-
// With TestInfo (recommended)
|
|
169
200
|
const loggedRequest = withApiLogging(request, testInfo);
|
|
170
|
-
|
|
171
|
-
// With options
|
|
172
|
-
const loggedRequest = withApiLogging(request, {
|
|
173
|
-
testName: 'my-test',
|
|
174
|
-
context: 'setup',
|
|
175
|
-
logDirectory: 'custom-logs/',
|
|
176
|
-
maskAuthTokens: true,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Access logger for finalization
|
|
180
|
-
loggedRequest.__logger.finalize('PASSED');
|
|
201
|
+
loggedRequest.__logger // access the ApiLogger instance
|
|
181
202
|
```
|
|
182
203
|
|
|
183
|
-
###
|
|
204
|
+
### `ApiLogger` — context & description
|
|
184
205
|
|
|
185
|
-
|
|
|
186
|
-
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
| `
|
|
206
|
+
| Method | Description |
|
|
207
|
+
|--------|-------------|
|
|
208
|
+
| `describe(text)` | Set description for the **next** API call |
|
|
209
|
+
| `startPreconditions()` | Following calls → `preconditions` section |
|
|
210
|
+
| `startTest()` | Following calls → `steps` section |
|
|
211
|
+
| `startTeardown()` | Following calls → `teardown` section |
|
|
212
|
+
| `setContext(ctx)` | Set context directly (`'preconditions'` / `'test'` / `'teardown'`) |
|
|
213
|
+
| `finalize(result, info?)` | Write structured JSON document to file |
|
|
214
|
+
| `isEnabled()` | Check if logging is active |
|
|
215
|
+
| `getLogFilePath()` | Get current log file path |
|
|
190
216
|
|
|
191
217
|
### `CurlGenerator`
|
|
192
218
|
|
|
@@ -205,19 +231,24 @@ loggedRequest.__logger.finalize('PASSED');
|
|
|
205
231
|
```typescript
|
|
206
232
|
{
|
|
207
233
|
testName?: string; // Test name (default: 'unknown-test')
|
|
208
|
-
|
|
234
|
+
testFile?: string; // Test file path
|
|
235
|
+
context?: LogContext; // 'preconditions' | 'test' | 'teardown'
|
|
209
236
|
logDirectory?: string; // Custom log dir (default: 'logs/')
|
|
210
237
|
maskAuthTokens?: boolean; // Mask auth headers (default: true)
|
|
238
|
+
logger?: ApiLogger; // Share logger across phases
|
|
211
239
|
}
|
|
212
240
|
```
|
|
213
241
|
|
|
214
|
-
## Migration from v1
|
|
215
|
-
|
|
216
|
-
v1 required changes to controllers, clients, and fixtures. v2 needs only **one line**:
|
|
242
|
+
## Migration from v1 → v2
|
|
217
243
|
|
|
218
244
|
```diff
|
|
219
|
-
-
|
|
220
|
-
|
|
245
|
+
- // v1: manual logger setup in controllers and clients
|
|
246
|
+
- const logger = createApiLogger(testInfo.title);
|
|
247
|
+
- apiClient.setApiLogger(logger);
|
|
248
|
+
|
|
249
|
+
+ // v2: one line, structured logs with sections
|
|
250
|
+
+ const loggedRequest = withApiLogging(request, testInfo);
|
|
251
|
+
+ const apiClient = new ApiClient(loggedRequest);
|
|
221
252
|
```
|
|
222
253
|
|
|
223
254
|
## License
|
package/dist/ApiLogger.d.ts
CHANGED
|
@@ -1,74 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API Logger for capturing and logging HTTP requests/responses
|
|
3
|
+
* Produces a single structured test document with preconditions, steps, and teardown sections.
|
|
4
|
+
*
|
|
3
5
|
* Features:
|
|
4
|
-
* -
|
|
6
|
+
* - Structured test document (one JSON per test)
|
|
7
|
+
* - Preconditions / Steps / Teardown sections
|
|
8
|
+
* - Step descriptions and numbering
|
|
5
9
|
* - Curl command generation for manual testing
|
|
6
10
|
* - Environment-based enable/disable via API_LOGS
|
|
7
|
-
* -
|
|
8
|
-
* - Test context tracking (setup/test/teardown)
|
|
11
|
+
* - Test context tracking and switching
|
|
9
12
|
*/
|
|
10
13
|
import { LogContext, LoggerConfig } from './types';
|
|
11
14
|
export declare class ApiLogger {
|
|
12
15
|
private enabled;
|
|
13
16
|
private testName;
|
|
14
|
-
private
|
|
17
|
+
private testFile?;
|
|
18
|
+
private currentContext;
|
|
15
19
|
private logDirectory;
|
|
16
20
|
private logFilePath;
|
|
17
21
|
private maskAuthTokens;
|
|
18
|
-
private
|
|
19
|
-
private
|
|
22
|
+
private startedAt;
|
|
23
|
+
private preconditions;
|
|
24
|
+
private steps;
|
|
25
|
+
private teardownSteps;
|
|
26
|
+
private nextDescription?;
|
|
20
27
|
constructor(config?: LoggerConfig);
|
|
21
|
-
/**
|
|
22
|
-
* Get default log directory path
|
|
23
|
-
*/
|
|
24
28
|
private getDefaultLogDirectory;
|
|
25
|
-
/**
|
|
26
|
-
* Initialize log file and directory
|
|
27
|
-
*/
|
|
28
29
|
private initializeLogFile;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* Set description for the next API call
|
|
32
|
+
* @example
|
|
33
|
+
* logger.describe('Get employee ID for testing');
|
|
34
|
+
* await apiClient.getEmployees(); // this call gets the description
|
|
31
35
|
*/
|
|
32
|
-
|
|
36
|
+
describe(description: string): void;
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
38
|
+
* Switch current context (preconditions → test → teardown)
|
|
39
|
+
* All subsequent API calls will be logged to the new context section
|
|
35
40
|
*/
|
|
36
|
-
|
|
41
|
+
setContext(context: LogContext): void;
|
|
37
42
|
/**
|
|
38
|
-
*
|
|
43
|
+
* Shortcut: switch to preconditions context
|
|
39
44
|
*/
|
|
40
|
-
|
|
45
|
+
startPreconditions(): void;
|
|
41
46
|
/**
|
|
42
|
-
*
|
|
47
|
+
* Shortcut: switch to test steps context
|
|
43
48
|
*/
|
|
44
|
-
|
|
49
|
+
startTest(): void;
|
|
45
50
|
/**
|
|
46
|
-
*
|
|
51
|
+
* Shortcut: switch to teardown context
|
|
47
52
|
*/
|
|
48
|
-
|
|
53
|
+
startTeardown(): void;
|
|
49
54
|
/**
|
|
50
|
-
*
|
|
55
|
+
* Log an API call with request and response
|
|
51
56
|
*/
|
|
52
|
-
|
|
57
|
+
logApiCall(method: string, url: string, requestHeaders: Record<string, string | string[]> | undefined, requestBody: any, status: number, responseHeaders: Record<string, string> | undefined, responseBody: any, duration: number): void;
|
|
53
58
|
/**
|
|
54
|
-
* Get the
|
|
59
|
+
* Get the target array for current context
|
|
55
60
|
*/
|
|
56
|
-
|
|
61
|
+
private getTargetArray;
|
|
57
62
|
/**
|
|
58
|
-
*
|
|
63
|
+
* Finalize and write the structured test document to file
|
|
59
64
|
*/
|
|
60
|
-
|
|
65
|
+
finalize(result: 'PASSED' | 'FAILED' | 'SKIPPED', additionalInfo?: Record<string, any>): void;
|
|
61
66
|
/**
|
|
62
|
-
*
|
|
67
|
+
* Write structured document to file
|
|
63
68
|
*/
|
|
64
|
-
|
|
69
|
+
private writeDocument;
|
|
70
|
+
private generateTimestamp;
|
|
71
|
+
private sanitizeTestName;
|
|
72
|
+
getLogFilePath(): string | null;
|
|
73
|
+
isEnabled(): boolean;
|
|
65
74
|
}
|
|
66
75
|
/**
|
|
67
76
|
* Factory function for creating test-context loggers
|
|
68
77
|
*/
|
|
69
78
|
export declare function createApiLogger(testName: string, context?: LogContext): ApiLogger;
|
|
70
79
|
/**
|
|
71
|
-
* Factory for setup context
|
|
80
|
+
* Factory for setup/preconditions context
|
|
72
81
|
*/
|
|
73
82
|
export declare function createSetupLogger(testName: string): ApiLogger;
|
|
74
83
|
/**
|
package/dist/ApiLogger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiLogger.d.ts","sourceRoot":"","sources":["../src/ApiLogger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ApiLogger.d.ts","sourceRoot":"","sources":["../src/ApiLogger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EACL,UAAU,EACV,YAAY,EAKb,MAAM,SAAS,CAAC;AAEjB,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,aAAa,CAAsB;IAG3C,OAAO,CAAC,eAAe,CAAC,CAAS;gBAErB,MAAM,GAAE,YAAiB;IAerC,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,iBAAiB;IAgBzB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAInC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAIrC;;OAEG;IACH,kBAAkB,IAAI,IAAI;IAI1B;;OAEG;IACH,SAAS,IAAI,IAAI;IAIjB;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,UAAU,CACR,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,EAC7D,WAAW,EAAE,GAAG,EAChB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACnD,YAAY,EAAE,GAAG,EACjB,QAAQ,EAAE,MAAM,GACf,IAAI;IAuDP;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAyC7F;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,gBAAgB;IASxB,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,SAAS,IAAI,OAAO;CAGrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,UAAmB,GAAG,SAAS,CAEzF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAE7D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAEhE"}
|
package/dist/ApiLogger.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* API Logger for capturing and logging HTTP requests/responses
|
|
4
|
+
* Produces a single structured test document with preconditions, steps, and teardown sections.
|
|
5
|
+
*
|
|
4
6
|
* Features:
|
|
5
|
-
* -
|
|
7
|
+
* - Structured test document (one JSON per test)
|
|
8
|
+
* - Preconditions / Steps / Teardown sections
|
|
9
|
+
* - Step descriptions and numbering
|
|
6
10
|
* - Curl command generation for manual testing
|
|
7
11
|
* - Environment-based enable/disable via API_LOGS
|
|
8
|
-
* -
|
|
9
|
-
* - Test context tracking (setup/test/teardown)
|
|
12
|
+
* - Test context tracking and switching
|
|
10
13
|
*/
|
|
11
14
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -22,27 +25,24 @@ const CurlGenerator_1 = require("./CurlGenerator");
|
|
|
22
25
|
class ApiLogger {
|
|
23
26
|
constructor(config = {}) {
|
|
24
27
|
this.logFilePath = null;
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
+
// Structured storage
|
|
29
|
+
this.preconditions = [];
|
|
30
|
+
this.steps = [];
|
|
31
|
+
this.teardownSteps = [];
|
|
28
32
|
this.enabled = process.env.API_LOGS === 'true';
|
|
29
33
|
this.testName = config.testName || 'unknown-test';
|
|
30
|
-
this.
|
|
34
|
+
this.testFile = config.testFile;
|
|
35
|
+
this.currentContext = config.context || 'test';
|
|
31
36
|
this.logDirectory = config.logDirectory || this.getDefaultLogDirectory();
|
|
32
37
|
this.maskAuthTokens = config.maskAuthTokens ?? true;
|
|
38
|
+
this.startedAt = new Date().toISOString();
|
|
33
39
|
if (this.enabled) {
|
|
34
40
|
this.initializeLogFile();
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Get default log directory path
|
|
39
|
-
*/
|
|
40
43
|
getDefaultLogDirectory() {
|
|
41
44
|
return path_1.default.join(process.cwd(), 'logs');
|
|
42
45
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Initialize log file and directory
|
|
45
|
-
*/
|
|
46
46
|
initializeLogFile() {
|
|
47
47
|
try {
|
|
48
48
|
if (!fs_1.default.existsSync(this.logDirectory)) {
|
|
@@ -50,17 +50,48 @@ class ApiLogger {
|
|
|
50
50
|
}
|
|
51
51
|
const timestamp = this.generateTimestamp();
|
|
52
52
|
const sanitizedTestName = this.sanitizeTestName(this.testName);
|
|
53
|
-
const filename = `${
|
|
53
|
+
const filename = `${sanitizedTestName}_${timestamp}.log`;
|
|
54
54
|
this.logFilePath = path_1.default.join(this.logDirectory, filename);
|
|
55
|
-
if (!fs_1.default.existsSync(this.logFilePath)) {
|
|
56
|
-
fs_1.default.writeFileSync(this.logFilePath, '');
|
|
57
|
-
}
|
|
58
55
|
}
|
|
59
56
|
catch (error) {
|
|
60
57
|
console.warn('[ApiLogger] Failed to initialize log file:', error);
|
|
61
58
|
this.logFilePath = null;
|
|
62
59
|
}
|
|
63
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Set description for the next API call
|
|
63
|
+
* @example
|
|
64
|
+
* logger.describe('Get employee ID for testing');
|
|
65
|
+
* await apiClient.getEmployees(); // this call gets the description
|
|
66
|
+
*/
|
|
67
|
+
describe(description) {
|
|
68
|
+
this.nextDescription = description;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Switch current context (preconditions → test → teardown)
|
|
72
|
+
* All subsequent API calls will be logged to the new context section
|
|
73
|
+
*/
|
|
74
|
+
setContext(context) {
|
|
75
|
+
this.currentContext = context;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Shortcut: switch to preconditions context
|
|
79
|
+
*/
|
|
80
|
+
startPreconditions() {
|
|
81
|
+
this.currentContext = 'preconditions';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Shortcut: switch to test steps context
|
|
85
|
+
*/
|
|
86
|
+
startTest() {
|
|
87
|
+
this.currentContext = 'test';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Shortcut: switch to teardown context
|
|
91
|
+
*/
|
|
92
|
+
startTeardown() {
|
|
93
|
+
this.currentContext = 'teardown';
|
|
94
|
+
}
|
|
64
95
|
/**
|
|
65
96
|
* Log an API call with request and response
|
|
66
97
|
*/
|
|
@@ -69,7 +100,6 @@ class ApiLogger {
|
|
|
69
100
|
return;
|
|
70
101
|
}
|
|
71
102
|
try {
|
|
72
|
-
// Extract content type from headers
|
|
73
103
|
let contentType;
|
|
74
104
|
if (requestHeaders) {
|
|
75
105
|
for (const [key, value] of Object.entries(requestHeaders)) {
|
|
@@ -91,75 +121,101 @@ class ApiLogger {
|
|
|
91
121
|
headers: responseHeaders,
|
|
92
122
|
body: responseBody,
|
|
93
123
|
};
|
|
94
|
-
// Generate curl command
|
|
95
124
|
const curl = CurlGenerator_1.CurlGenerator.generate(requestData, this.maskAuthTokens);
|
|
96
|
-
//
|
|
97
|
-
const
|
|
125
|
+
// Get target array and step number
|
|
126
|
+
const targetArray = this.getTargetArray();
|
|
127
|
+
const stepNumber = targetArray.length + 1;
|
|
128
|
+
const stepEntry = {
|
|
129
|
+
step: stepNumber,
|
|
130
|
+
description: this.nextDescription,
|
|
98
131
|
timestamp: new Date().toISOString(),
|
|
99
|
-
testName: this.testName,
|
|
100
|
-
context: this.context,
|
|
101
132
|
request: requestData,
|
|
102
133
|
response: responseData,
|
|
103
134
|
duration,
|
|
104
135
|
curl,
|
|
105
136
|
};
|
|
106
|
-
|
|
137
|
+
// Clear description after use
|
|
138
|
+
this.nextDescription = undefined;
|
|
139
|
+
targetArray.push(stepEntry);
|
|
107
140
|
}
|
|
108
141
|
catch (error) {
|
|
109
142
|
console.warn('[ApiLogger] Error logging API call:', error);
|
|
110
143
|
}
|
|
111
144
|
}
|
|
112
145
|
/**
|
|
113
|
-
*
|
|
146
|
+
* Get the target array for current context
|
|
114
147
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
getTargetArray() {
|
|
149
|
+
switch (this.currentContext) {
|
|
150
|
+
case 'preconditions':
|
|
151
|
+
return this.preconditions;
|
|
152
|
+
case 'teardown':
|
|
153
|
+
return this.teardownSteps;
|
|
154
|
+
case 'test':
|
|
155
|
+
default:
|
|
156
|
+
return this.steps;
|
|
118
157
|
}
|
|
119
|
-
this.currentRequest = { method, url, headers, body };
|
|
120
|
-
this.requestStartTime = Date.now();
|
|
121
158
|
}
|
|
122
159
|
/**
|
|
123
|
-
*
|
|
160
|
+
* Finalize and write the structured test document to file
|
|
124
161
|
*/
|
|
125
|
-
|
|
126
|
-
if (!this.enabled
|
|
162
|
+
finalize(result, additionalInfo) {
|
|
163
|
+
if (!this.enabled) {
|
|
127
164
|
return;
|
|
128
165
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
try {
|
|
167
|
+
const finishedAt = new Date().toISOString();
|
|
168
|
+
const startTime = new Date(this.startedAt).getTime();
|
|
169
|
+
const endTime = new Date(finishedAt).getTime();
|
|
170
|
+
// Calculate total API duration
|
|
171
|
+
const allSteps = [...this.preconditions, ...this.steps, ...this.teardownSteps];
|
|
172
|
+
const totalApiDuration = allSteps.reduce((sum, s) => sum + s.duration, 0);
|
|
173
|
+
const document = {
|
|
174
|
+
test: {
|
|
175
|
+
name: this.testName,
|
|
176
|
+
file: this.testFile || additionalInfo?.testFile,
|
|
177
|
+
startedAt: this.startedAt,
|
|
178
|
+
finishedAt,
|
|
179
|
+
duration: endTime - startTime,
|
|
180
|
+
result,
|
|
181
|
+
},
|
|
182
|
+
preconditions: this.preconditions,
|
|
183
|
+
steps: this.steps,
|
|
184
|
+
teardown: this.teardownSteps,
|
|
185
|
+
summary: {
|
|
186
|
+
totalRequests: allSteps.length,
|
|
187
|
+
preconditions: this.preconditions.length,
|
|
188
|
+
testSteps: this.steps.length,
|
|
189
|
+
teardown: this.teardownSteps.length,
|
|
190
|
+
totalDuration: totalApiDuration,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
this.writeDocument(document);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.warn('[ApiLogger] Error finalizing log:', error);
|
|
197
|
+
}
|
|
133
198
|
}
|
|
134
199
|
/**
|
|
135
|
-
* Write
|
|
200
|
+
* Write structured document to file
|
|
136
201
|
*/
|
|
137
|
-
|
|
202
|
+
writeDocument(document) {
|
|
138
203
|
try {
|
|
139
204
|
if (!this.logFilePath) {
|
|
140
205
|
return;
|
|
141
206
|
}
|
|
142
|
-
|
|
143
|
-
fs_1.default.appendFileSync(this.logFilePath, logLine + '\n\n');
|
|
207
|
+
fs_1.default.writeFileSync(this.logFilePath, JSON.stringify(document, null, 2) + '\n');
|
|
144
208
|
}
|
|
145
209
|
catch (error) {
|
|
146
210
|
console.warn('[ApiLogger] Failed to write log file:', error);
|
|
147
211
|
}
|
|
148
212
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Generate ISO timestamp for filenames
|
|
151
|
-
*/
|
|
152
213
|
generateTimestamp() {
|
|
153
|
-
|
|
154
|
-
return now
|
|
214
|
+
return new Date()
|
|
155
215
|
.toISOString()
|
|
156
216
|
.replace(/[:.]/g, '-')
|
|
157
|
-
.replace('T', 'T')
|
|
158
217
|
.split('.')[0];
|
|
159
218
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Sanitize test name for use in filename
|
|
162
|
-
*/
|
|
163
219
|
sanitizeTestName(name) {
|
|
164
220
|
return name
|
|
165
221
|
.toLowerCase()
|
|
@@ -168,43 +224,12 @@ class ApiLogger {
|
|
|
168
224
|
.replace(/^-|-$/g, '')
|
|
169
225
|
.substring(0, 80);
|
|
170
226
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Get the current log file path
|
|
173
|
-
*/
|
|
174
227
|
getLogFilePath() {
|
|
175
228
|
return this.logFilePath;
|
|
176
229
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Check if logger is enabled
|
|
179
|
-
*/
|
|
180
230
|
isEnabled() {
|
|
181
231
|
return this.enabled;
|
|
182
232
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Finalize logging for test completion
|
|
185
|
-
*/
|
|
186
|
-
finalize(result, additionalInfo) {
|
|
187
|
-
if (!this.enabled) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
const finalizationEntry = {
|
|
192
|
-
timestamp: new Date().toISOString(),
|
|
193
|
-
step: 'TEST_FINALIZATION',
|
|
194
|
-
testName: this.testName,
|
|
195
|
-
context: this.context,
|
|
196
|
-
result,
|
|
197
|
-
logFilePath: this.logFilePath,
|
|
198
|
-
additionalInfo,
|
|
199
|
-
};
|
|
200
|
-
if (this.logFilePath) {
|
|
201
|
-
fs_1.default.appendFileSync(this.logFilePath, JSON.stringify(finalizationEntry, null, 2) + '\n\n');
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
console.warn('[ApiLogger] Error finalizing log:', error);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
233
|
}
|
|
209
234
|
exports.ApiLogger = ApiLogger;
|
|
210
235
|
/**
|
|
@@ -214,10 +239,10 @@ function createApiLogger(testName, context = 'test') {
|
|
|
214
239
|
return new ApiLogger({ testName, context });
|
|
215
240
|
}
|
|
216
241
|
/**
|
|
217
|
-
* Factory for setup context
|
|
242
|
+
* Factory for setup/preconditions context
|
|
218
243
|
*/
|
|
219
244
|
function createSetupLogger(testName) {
|
|
220
|
-
return new ApiLogger({ testName, context: '
|
|
245
|
+
return new ApiLogger({ testName, context: 'preconditions' });
|
|
221
246
|
}
|
|
222
247
|
/**
|
|
223
248
|
* Factory for teardown context
|
package/dist/ApiLogger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiLogger.js","sourceRoot":"","sources":["../src/ApiLogger.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"ApiLogger.js","sourceRoot":"","sources":["../src/ApiLogger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry for sharing ApiLogger instances across
|
|
3
|
+
* beforeAll / test / afterAll in a Playwright test.describe block.
|
|
4
|
+
*
|
|
5
|
+
* Solves the problem: Playwright creates separate fixture instances
|
|
6
|
+
* for beforeAll, each test(), and afterAll — without a shared store
|
|
7
|
+
* each gets its own logger and writes a separate file.
|
|
8
|
+
*
|
|
9
|
+
* With LoggerRegistry, all phases write to the SAME structured log.
|
|
10
|
+
*/
|
|
11
|
+
import { ApiLogger } from './ApiLogger';
|
|
12
|
+
import { LoggerConfig } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Get or create a shared logger by key.
|
|
15
|
+
* First call creates the logger, subsequent calls return the same instance.
|
|
16
|
+
*
|
|
17
|
+
* @param key - Unique key for the logger (e.g. describe block name)
|
|
18
|
+
* @param config - Logger config (only used on first call when creating)
|
|
19
|
+
* @returns Shared ApiLogger instance
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* test.describe('GET /api/v1/employees', () => {
|
|
23
|
+
* const LOG_KEY = 'get-employees';
|
|
24
|
+
*
|
|
25
|
+
* test.beforeAll(async ({ apiClient }) => {
|
|
26
|
+
* const logger = getSharedLogger(LOG_KEY, { testName: 'GET employees' });
|
|
27
|
+
* logger.startPreconditions();
|
|
28
|
+
* // ... apiClient calls logged to preconditions
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* test('should return 200', async ({ apiClient }) => {
|
|
32
|
+
* const logger = getSharedLogger(LOG_KEY);
|
|
33
|
+
* logger.startTest();
|
|
34
|
+
* // ... apiClient calls logged to steps
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* test.afterAll(() => {
|
|
38
|
+
* finalizeSharedLogger(LOG_KEY, 'PASSED');
|
|
39
|
+
* });
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
export declare function getSharedLogger(key: string, config?: LoggerConfig): ApiLogger;
|
|
43
|
+
/**
|
|
44
|
+
* Finalize a shared logger: write the structured document and remove from registry.
|
|
45
|
+
*
|
|
46
|
+
* @param key - The logger key used in getSharedLogger()
|
|
47
|
+
* @param result - Test result: 'PASSED' | 'FAILED' | 'SKIPPED'
|
|
48
|
+
* @param additionalInfo - Optional extra info to include
|
|
49
|
+
*/
|
|
50
|
+
export declare function finalizeSharedLogger(key: string, result: 'PASSED' | 'FAILED' | 'SKIPPED', additionalInfo?: Record<string, any>): void;
|
|
51
|
+
/**
|
|
52
|
+
* Check if a shared logger exists for the given key.
|
|
53
|
+
*/
|
|
54
|
+
export declare function hasSharedLogger(key: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Remove a shared logger without finalizing (cleanup).
|
|
57
|
+
*/
|
|
58
|
+
export declare function removeSharedLogger(key: string): void;
|
|
59
|
+
//# sourceMappingURL=LoggerRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoggerRegistry.d.ts","sourceRoot":"","sources":["../src/LoggerRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAK7E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,EACvC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACnC,IAAI,CAMN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Global registry for sharing ApiLogger instances across
|
|
4
|
+
* beforeAll / test / afterAll in a Playwright test.describe block.
|
|
5
|
+
*
|
|
6
|
+
* Solves the problem: Playwright creates separate fixture instances
|
|
7
|
+
* for beforeAll, each test(), and afterAll — without a shared store
|
|
8
|
+
* each gets its own logger and writes a separate file.
|
|
9
|
+
*
|
|
10
|
+
* With LoggerRegistry, all phases write to the SAME structured log.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.getSharedLogger = getSharedLogger;
|
|
14
|
+
exports.finalizeSharedLogger = finalizeSharedLogger;
|
|
15
|
+
exports.hasSharedLogger = hasSharedLogger;
|
|
16
|
+
exports.removeSharedLogger = removeSharedLogger;
|
|
17
|
+
const ApiLogger_1 = require("./ApiLogger");
|
|
18
|
+
const registry = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Get or create a shared logger by key.
|
|
21
|
+
* First call creates the logger, subsequent calls return the same instance.
|
|
22
|
+
*
|
|
23
|
+
* @param key - Unique key for the logger (e.g. describe block name)
|
|
24
|
+
* @param config - Logger config (only used on first call when creating)
|
|
25
|
+
* @returns Shared ApiLogger instance
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* test.describe('GET /api/v1/employees', () => {
|
|
29
|
+
* const LOG_KEY = 'get-employees';
|
|
30
|
+
*
|
|
31
|
+
* test.beforeAll(async ({ apiClient }) => {
|
|
32
|
+
* const logger = getSharedLogger(LOG_KEY, { testName: 'GET employees' });
|
|
33
|
+
* logger.startPreconditions();
|
|
34
|
+
* // ... apiClient calls logged to preconditions
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* test('should return 200', async ({ apiClient }) => {
|
|
38
|
+
* const logger = getSharedLogger(LOG_KEY);
|
|
39
|
+
* logger.startTest();
|
|
40
|
+
* // ... apiClient calls logged to steps
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* test.afterAll(() => {
|
|
44
|
+
* finalizeSharedLogger(LOG_KEY, 'PASSED');
|
|
45
|
+
* });
|
|
46
|
+
* });
|
|
47
|
+
*/
|
|
48
|
+
function getSharedLogger(key, config) {
|
|
49
|
+
if (!registry.has(key)) {
|
|
50
|
+
registry.set(key, new ApiLogger_1.ApiLogger(config));
|
|
51
|
+
}
|
|
52
|
+
return registry.get(key);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Finalize a shared logger: write the structured document and remove from registry.
|
|
56
|
+
*
|
|
57
|
+
* @param key - The logger key used in getSharedLogger()
|
|
58
|
+
* @param result - Test result: 'PASSED' | 'FAILED' | 'SKIPPED'
|
|
59
|
+
* @param additionalInfo - Optional extra info to include
|
|
60
|
+
*/
|
|
61
|
+
function finalizeSharedLogger(key, result, additionalInfo) {
|
|
62
|
+
const logger = registry.get(key);
|
|
63
|
+
if (logger) {
|
|
64
|
+
logger.finalize(result, additionalInfo);
|
|
65
|
+
registry.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a shared logger exists for the given key.
|
|
70
|
+
*/
|
|
71
|
+
function hasSharedLogger(key) {
|
|
72
|
+
return registry.has(key);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Remove a shared logger without finalizing (cleanup).
|
|
76
|
+
*/
|
|
77
|
+
function removeSharedLogger(key) {
|
|
78
|
+
registry.delete(key);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=LoggerRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoggerRegistry.js","sourceRoot":"","sources":["../src/LoggerRegistry.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAoCH,0CAKC;AASD,oDAUC;AAKD,0CAEC;AAKD,gDAEC;AAxED,2CAAwC;AAGxC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,SAAgB,eAAe,CAAC,GAAW,EAAE,MAAqB;IAChE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,qBAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;AAC5B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAClC,GAAW,EACX,MAAuC,EACvC,cAAoC;IAEpC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACxC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAAC,GAAW;IACzC,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { ApiLogger, createApiLogger, createSetupLogger, createTeardownLogger } from './ApiLogger';
|
|
2
2
|
export { CurlGenerator } from './CurlGenerator';
|
|
3
3
|
export { withApiLogging } from './withApiLogging';
|
|
4
|
+
export { getSharedLogger, finalizeSharedLogger, hasSharedLogger, removeSharedLogger } from './LoggerRegistry';
|
|
4
5
|
export type { ApiLoggingOptions } from './withApiLogging';
|
|
5
|
-
export type { LoggerConfig, RequestLogData, ResponseLogData, LogEntry, LogContext } from './types';
|
|
6
|
+
export type { LoggerConfig, RequestLogData, ResponseLogData, StepLogEntry, TestLogDocument, LogEntry, LogContext, } from './types';
|
|
6
7
|
export type { RequestData } from './CurlGenerator';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9G,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,YAAY,EACV,YAAY,EACZ,cAAc,EACd,eAAe,EACf,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.withApiLogging = exports.CurlGenerator = exports.createTeardownLogger = exports.createSetupLogger = exports.createApiLogger = exports.ApiLogger = void 0;
|
|
3
|
+
exports.removeSharedLogger = exports.hasSharedLogger = exports.finalizeSharedLogger = exports.getSharedLogger = exports.withApiLogging = exports.CurlGenerator = exports.createTeardownLogger = exports.createSetupLogger = exports.createApiLogger = exports.ApiLogger = void 0;
|
|
4
4
|
var ApiLogger_1 = require("./ApiLogger");
|
|
5
5
|
Object.defineProperty(exports, "ApiLogger", { enumerable: true, get: function () { return ApiLogger_1.ApiLogger; } });
|
|
6
6
|
Object.defineProperty(exports, "createApiLogger", { enumerable: true, get: function () { return ApiLogger_1.createApiLogger; } });
|
|
@@ -10,4 +10,9 @@ var CurlGenerator_1 = require("./CurlGenerator");
|
|
|
10
10
|
Object.defineProperty(exports, "CurlGenerator", { enumerable: true, get: function () { return CurlGenerator_1.CurlGenerator; } });
|
|
11
11
|
var withApiLogging_1 = require("./withApiLogging");
|
|
12
12
|
Object.defineProperty(exports, "withApiLogging", { enumerable: true, get: function () { return withApiLogging_1.withApiLogging; } });
|
|
13
|
+
var LoggerRegistry_1 = require("./LoggerRegistry");
|
|
14
|
+
Object.defineProperty(exports, "getSharedLogger", { enumerable: true, get: function () { return LoggerRegistry_1.getSharedLogger; } });
|
|
15
|
+
Object.defineProperty(exports, "finalizeSharedLogger", { enumerable: true, get: function () { return LoggerRegistry_1.finalizeSharedLogger; } });
|
|
16
|
+
Object.defineProperty(exports, "hasSharedLogger", { enumerable: true, get: function () { return LoggerRegistry_1.hasSharedLogger; } });
|
|
17
|
+
Object.defineProperty(exports, "removeSharedLogger", { enumerable: true, get: function () { return LoggerRegistry_1.removeSharedLogger; } });
|
|
13
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAkG;AAAzF,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAC5E,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAAkG;AAAzF,sGAAA,SAAS,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,iHAAA,oBAAoB,OAAA;AAC5E,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AACtB,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,mDAA8G;AAArG,iHAAA,eAAe,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAAE,iHAAA,eAAe,OAAA;AAAE,oHAAA,kBAAkB,OAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type LogContext = '
|
|
1
|
+
export type LogContext = 'preconditions' | 'test' | 'teardown';
|
|
2
2
|
export interface LoggerConfig {
|
|
3
3
|
testName?: string;
|
|
4
|
+
testFile?: string;
|
|
4
5
|
context?: LogContext;
|
|
5
6
|
logDirectory?: string;
|
|
6
7
|
maskAuthTokens?: boolean;
|
|
@@ -17,6 +18,36 @@ export interface ResponseLogData {
|
|
|
17
18
|
headers?: Record<string, string>;
|
|
18
19
|
body?: any;
|
|
19
20
|
}
|
|
21
|
+
export interface StepLogEntry {
|
|
22
|
+
step: number;
|
|
23
|
+
description?: string;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
request: RequestLogData;
|
|
26
|
+
response: ResponseLogData;
|
|
27
|
+
duration: number;
|
|
28
|
+
curl: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TestLogDocument {
|
|
31
|
+
test: {
|
|
32
|
+
name: string;
|
|
33
|
+
file?: string;
|
|
34
|
+
startedAt: string;
|
|
35
|
+
finishedAt?: string;
|
|
36
|
+
duration?: number;
|
|
37
|
+
result?: 'PASSED' | 'FAILED' | 'SKIPPED';
|
|
38
|
+
};
|
|
39
|
+
preconditions: StepLogEntry[];
|
|
40
|
+
steps: StepLogEntry[];
|
|
41
|
+
teardown: StepLogEntry[];
|
|
42
|
+
summary: {
|
|
43
|
+
totalRequests: number;
|
|
44
|
+
preconditions: number;
|
|
45
|
+
testSteps: number;
|
|
46
|
+
teardown: number;
|
|
47
|
+
totalDuration: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** @deprecated Use StepLogEntry instead */
|
|
20
51
|
export interface LogEntry {
|
|
21
52
|
timestamp: string;
|
|
22
53
|
testName: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;KAC1C,CAAC;IACF,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,2CAA2C;AAC3C,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,CAAC;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd"}
|
package/dist/withApiLogging.d.ts
CHANGED
|
@@ -8,12 +8,18 @@ import { LogContext } from './types';
|
|
|
8
8
|
export interface ApiLoggingOptions {
|
|
9
9
|
/** Test name for log filename (auto-detected from testInfo if provided) */
|
|
10
10
|
testName?: string;
|
|
11
|
-
/**
|
|
11
|
+
/** Test file path (auto-detected from testInfo if provided) */
|
|
12
|
+
testFile?: string;
|
|
13
|
+
/** Log context: 'preconditions' | 'test' | 'teardown' (default: 'test') */
|
|
12
14
|
context?: LogContext;
|
|
13
15
|
/** Custom log directory (default: 'logs/') */
|
|
14
16
|
logDirectory?: string;
|
|
15
17
|
/** Mask Authorization headers (default: true) */
|
|
16
18
|
maskAuthTokens?: boolean;
|
|
19
|
+
/** Existing logger instance to reuse */
|
|
20
|
+
logger?: ApiLogger;
|
|
21
|
+
/** Shared logger key — use same key in beforeAll/test/afterAll to write ONE log file */
|
|
22
|
+
sharedKey?: string;
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
25
|
* Wrap Playwright's APIRequestContext with automatic logging.
|
|
@@ -29,8 +35,12 @@ export interface ApiLoggingOptions {
|
|
|
29
35
|
* const apiClient = new ApiClient(loggedRequest);
|
|
30
36
|
*
|
|
31
37
|
* @example
|
|
32
|
-
* //
|
|
33
|
-
* const loggedRequest = withApiLogging(request, {
|
|
38
|
+
* // Shared logger across beforeAll/test/afterAll:
|
|
39
|
+
* const loggedRequest = withApiLogging(request, {
|
|
40
|
+
* sharedKey: 'my-describe',
|
|
41
|
+
* testName: 'My Test',
|
|
42
|
+
* context: 'preconditions'
|
|
43
|
+
* });
|
|
34
44
|
*/
|
|
35
45
|
export declare function withApiLogging(request: APIRequestContext, testInfoOrOptions?: TestInfo | ApiLoggingOptions): APIRequestContext & {
|
|
36
46
|
__logger: ApiLogger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withApiLogging.d.ts","sourceRoot":"","sources":["../src/withApiLogging.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAe,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"withApiLogging.d.ts","sourceRoot":"","sources":["../src/withApiLogging.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAe,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;AAGnD,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,iBAAiB,CAAC,EAAE,QAAQ,GAAG,iBAAiB,GAC/C,iBAAiB,GAAG;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAiH7C"}
|
package/dist/withApiLogging.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.withApiLogging = withApiLogging;
|
|
8
8
|
const ApiLogger_1 = require("./ApiLogger");
|
|
9
|
+
const LoggerRegistry_1 = require("./LoggerRegistry");
|
|
9
10
|
/**
|
|
10
11
|
* Wrap Playwright's APIRequestContext with automatic logging.
|
|
11
12
|
* All HTTP calls (get, post, put, patch, delete, head, fetch) are intercepted and logged.
|
|
@@ -20,30 +21,60 @@ const ApiLogger_1 = require("./ApiLogger");
|
|
|
20
21
|
* const apiClient = new ApiClient(loggedRequest);
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
|
-
* //
|
|
24
|
-
* const loggedRequest = withApiLogging(request, {
|
|
24
|
+
* // Shared logger across beforeAll/test/afterAll:
|
|
25
|
+
* const loggedRequest = withApiLogging(request, {
|
|
26
|
+
* sharedKey: 'my-describe',
|
|
27
|
+
* testName: 'My Test',
|
|
28
|
+
* context: 'preconditions'
|
|
29
|
+
* });
|
|
25
30
|
*/
|
|
26
31
|
function withApiLogging(request, testInfoOrOptions) {
|
|
27
|
-
// Resolve options
|
|
28
32
|
let options;
|
|
29
33
|
if (testInfoOrOptions && 'title' in testInfoOrOptions) {
|
|
30
|
-
|
|
34
|
+
const testInfo = testInfoOrOptions;
|
|
31
35
|
options = {
|
|
32
|
-
testName:
|
|
36
|
+
testName: testInfo.title,
|
|
37
|
+
testFile: testInfo.file,
|
|
33
38
|
context: 'test',
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
41
|
else {
|
|
37
42
|
options = testInfoOrOptions || {};
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Resolve logger: shared → existing → new
|
|
45
|
+
let logger;
|
|
46
|
+
if (options.sharedKey) {
|
|
47
|
+
// Shared logger by key — same instance across beforeAll/test/afterAll
|
|
48
|
+
const config = {
|
|
49
|
+
testName: options.testName,
|
|
50
|
+
testFile: options.testFile,
|
|
51
|
+
context: options.context || 'test',
|
|
52
|
+
logDirectory: options.logDirectory,
|
|
53
|
+
maskAuthTokens: options.maskAuthTokens,
|
|
54
|
+
};
|
|
55
|
+
logger = (0, LoggerRegistry_1.getSharedLogger)(options.sharedKey, config);
|
|
56
|
+
// Switch context for this phase
|
|
57
|
+
if (options.context) {
|
|
58
|
+
logger.setContext(options.context);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (options.logger) {
|
|
62
|
+
logger = options.logger;
|
|
63
|
+
if (options.context) {
|
|
64
|
+
logger.setContext(options.context);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const config = {
|
|
69
|
+
testName: options.testName,
|
|
70
|
+
testFile: options.testFile,
|
|
71
|
+
context: options.context || 'test',
|
|
72
|
+
logDirectory: options.logDirectory,
|
|
73
|
+
maskAuthTokens: options.maskAuthTokens,
|
|
74
|
+
};
|
|
75
|
+
logger = new ApiLogger_1.ApiLogger(config);
|
|
76
|
+
}
|
|
77
|
+
// If logging is disabled, return original request with logger ref
|
|
47
78
|
if (!logger.isEnabled()) {
|
|
48
79
|
return Object.assign(request, { __logger: logger });
|
|
49
80
|
}
|
|
@@ -51,22 +82,18 @@ function withApiLogging(request, testInfoOrOptions) {
|
|
|
51
82
|
const proxy = new Proxy(request, {
|
|
52
83
|
get(target, prop, receiver) {
|
|
53
84
|
const propName = typeof prop === 'string' ? prop : '';
|
|
54
|
-
// Expose logger reference
|
|
55
85
|
if (propName === '__logger') {
|
|
56
86
|
return logger;
|
|
57
87
|
}
|
|
58
88
|
if (HTTP_METHODS.includes(propName)) {
|
|
59
|
-
return async (url,
|
|
60
|
-
const method = propName === 'fetch' ? (
|
|
89
|
+
return async (url, reqOptions) => {
|
|
90
|
+
const method = propName === 'fetch' ? (reqOptions?.method || 'GET') : propName;
|
|
61
91
|
const startTime = Date.now();
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const requestBody = extractBody(options);
|
|
65
|
-
const contentType = extractContentType(options, requestHeaders);
|
|
92
|
+
const requestHeaders = reqOptions?.headers;
|
|
93
|
+
const requestBody = extractBody(reqOptions);
|
|
66
94
|
try {
|
|
67
|
-
const response = await target[propName](url,
|
|
95
|
+
const response = await target[propName](url, reqOptions);
|
|
68
96
|
const duration = Date.now() - startTime;
|
|
69
|
-
// Parse response body safely
|
|
70
97
|
const responseBody = await safeParseResponseBody(response);
|
|
71
98
|
const responseHeaders = extractResponseHeaders(response);
|
|
72
99
|
logger.logApiCall(method.toUpperCase(), response.url(), requestHeaders, requestBody, response.status(), responseHeaders, responseBody, duration);
|
|
@@ -84,9 +111,6 @@ function withApiLogging(request, testInfoOrOptions) {
|
|
|
84
111
|
});
|
|
85
112
|
return Object.assign(proxy, { __logger: logger });
|
|
86
113
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Extract body from Playwright request options
|
|
89
|
-
*/
|
|
90
114
|
function extractBody(options) {
|
|
91
115
|
if (!options)
|
|
92
116
|
return undefined;
|
|
@@ -98,26 +122,6 @@ function extractBody(options) {
|
|
|
98
122
|
return options.multipart;
|
|
99
123
|
return undefined;
|
|
100
124
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Detect content type from options
|
|
103
|
-
*/
|
|
104
|
-
function extractContentType(options, headers) {
|
|
105
|
-
if (options?.form)
|
|
106
|
-
return 'application/x-www-form-urlencoded';
|
|
107
|
-
if (options?.multipart)
|
|
108
|
-
return 'multipart/form-data';
|
|
109
|
-
if (headers) {
|
|
110
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
111
|
-
if (key.toLowerCase() === 'content-type') {
|
|
112
|
-
return Array.isArray(value) ? value[0] : String(value);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Safely parse response body (try JSON, fallback to text)
|
|
120
|
-
*/
|
|
121
125
|
async function safeParseResponseBody(response) {
|
|
122
126
|
try {
|
|
123
127
|
return await response.json();
|
|
@@ -131,9 +135,6 @@ async function safeParseResponseBody(response) {
|
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Extract headers from APIResponse
|
|
136
|
-
*/
|
|
137
138
|
function extractResponseHeaders(response) {
|
|
138
139
|
try {
|
|
139
140
|
return response.headers();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withApiLogging.js","sourceRoot":"","sources":["../src/withApiLogging.ts"],"names":[],"mappings":";AAAA;;;GAGG;;
|
|
1
|
+
{"version":3,"file":"withApiLogging.js","sourceRoot":"","sources":["../src/withApiLogging.ts"],"names":[],"mappings":"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"}
|