playwright-api-logger 1.0.0 → 2.1.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 +132 -92
- 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/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +49 -0
- package/dist/withApiLogging.d.ts.map +1 -0
- package/dist/withApiLogging.js +133 -0
- package/dist/withApiLogging.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,45 +21,45 @@
|
|
|
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
|
-
C[API Call
|
|
27
|
+
C[API Call GET/POST/PUT/DELETE]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
subgraph
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
subgraph P[playwright-api-logger]
|
|
31
|
+
W["withApiLogging(request, testInfo)"]
|
|
32
|
+
PR[Proxy over APIRequestContext]
|
|
33
|
+
AL[ApiLogger]
|
|
34
|
+
CG[CurlGenerator]
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
subgraph
|
|
37
|
-
LOG[logs/TEST_*.log
|
|
37
|
+
subgraph O[Output]
|
|
38
|
+
LOG["logs/TEST_*.log<br/>request, response, curl, duration"]
|
|
38
39
|
RC[Ready-to-use curl for Postman / terminal]
|
|
39
40
|
end
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
C -->
|
|
44
|
-
|
|
45
|
-
%% logging interaction
|
|
46
|
-
B --> AL
|
|
42
|
+
F -->|"1 line change"| W
|
|
43
|
+
W --> PR
|
|
44
|
+
C --> PR
|
|
45
|
+
PR --> AL
|
|
47
46
|
AL --> LOG
|
|
48
|
-
|
|
49
|
-
%% curl generation
|
|
50
|
-
B --> CG
|
|
47
|
+
AL --> CG
|
|
51
48
|
CG --> RC
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
```
|
|
52
|
+
API_LOGS=true → Logging ON (files created in logs/)
|
|
53
|
+
API_LOGS=false → Logging OFF (zero overhead, default)
|
|
54
|
+
```
|
|
56
55
|
|
|
57
56
|
## Features
|
|
58
57
|
|
|
59
|
-
- **
|
|
58
|
+
- **One-line integration** — just wrap `request` with `withApiLogging()`, zero changes to controllers/clients
|
|
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
|
|
@@ -72,58 +72,46 @@ npm install playwright-api-logger
|
|
|
72
72
|
|
|
73
73
|
## Quick Start
|
|
74
74
|
|
|
75
|
-
###
|
|
75
|
+
### One line in your fixture — that's it!
|
|
76
76
|
|
|
77
77
|
```typescript
|
|
78
|
-
import {
|
|
78
|
+
import { withApiLogging } from 'playwright-api-logger';
|
|
79
79
|
|
|
80
80
|
export const test = base.extend({
|
|
81
81
|
apiClient: async ({ request }, use, testInfo) => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
logger.finalize(testInfo.status === 'passed' ? 'PASSED' : 'FAILED');
|
|
89
|
-
} else {
|
|
90
|
-
await use(apiClient);
|
|
91
|
-
}
|
|
82
|
+
const loggedRequest = withApiLogging(request, testInfo);
|
|
83
|
+
const apiClient = new ApiClient(loggedRequest);
|
|
84
|
+
await use(apiClient);
|
|
85
|
+
loggedRequest.__logger.finalize(
|
|
86
|
+
testInfo.status === 'passed' ? 'PASSED' : 'FAILED'
|
|
87
|
+
);
|
|
92
88
|
},
|
|
93
89
|
});
|
|
94
90
|
```
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
import { ApiLogger } from 'playwright-api-logger';
|
|
100
|
-
|
|
101
|
-
class BaseApiController {
|
|
102
|
-
protected apiLogger: ApiLogger | null = null;
|
|
103
|
-
|
|
104
|
-
setApiLogger(logger: ApiLogger): void {
|
|
105
|
-
this.apiLogger = logger;
|
|
106
|
-
}
|
|
92
|
+
No changes to your controllers, clients, or test files.
|
|
107
93
|
|
|
108
|
-
|
|
109
|
-
const startTime = Date.now();
|
|
110
|
-
const response = await this.request.get(url, { headers });
|
|
111
|
-
const duration = Date.now() - startTime;
|
|
112
|
-
|
|
113
|
-
if (this.apiLogger?.isEnabled()) {
|
|
114
|
-
const body = await response.json().catch(() => response.text());
|
|
115
|
-
this.apiLogger.logApiCall(
|
|
116
|
-
'GET', url, headers, undefined,
|
|
117
|
-
response.status(), undefined, body, duration
|
|
118
|
-
);
|
|
119
|
-
}
|
|
94
|
+
### With preconditions and step descriptions
|
|
120
95
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
});
|
|
124
112
|
```
|
|
125
113
|
|
|
126
|
-
###
|
|
114
|
+
### Enable via environment variable
|
|
127
115
|
|
|
128
116
|
```bash
|
|
129
117
|
# .env
|
|
@@ -137,56 +125,93 @@ API_LOGS=true npx playwright test
|
|
|
137
125
|
|
|
138
126
|
## Log Output
|
|
139
127
|
|
|
140
|
-
|
|
128
|
+
One structured JSON document per test:
|
|
141
129
|
|
|
142
130
|
```
|
|
143
131
|
logs/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
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
|
|
147
134
|
```
|
|
148
135
|
|
|
149
|
-
|
|
136
|
+
### Example log:
|
|
150
137
|
|
|
151
138
|
```json
|
|
152
139
|
{
|
|
153
|
-
"
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"body": { "name": "John" }
|
|
161
|
-
},
|
|
162
|
-
"response": {
|
|
163
|
-
"status": 201,
|
|
164
|
-
"body": { "id": 1, "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"
|
|
165
147
|
},
|
|
166
|
-
"
|
|
167
|
-
|
|
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
|
+
}
|
|
168
190
|
}
|
|
169
191
|
```
|
|
170
192
|
|
|
171
193
|
## API Reference
|
|
172
194
|
|
|
173
|
-
###
|
|
195
|
+
### `withApiLogging(request, testInfoOrOptions?)` ⭐
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
|----------|-------------|
|
|
177
|
-
| `createApiLogger(testName, context?)` | Create logger (context default: `'test'`) |
|
|
178
|
-
| `createSetupLogger(testName)` | Create logger with `'setup'` context |
|
|
179
|
-
| `createTeardownLogger(testName)` | Create logger with `'teardown'` context |
|
|
197
|
+
Main integration point. Wraps `APIRequestContext` with a Proxy that logs all HTTP calls.
|
|
180
198
|
|
|
181
|
-
|
|
199
|
+
```typescript
|
|
200
|
+
const loggedRequest = withApiLogging(request, testInfo);
|
|
201
|
+
loggedRequest.__logger // access the ApiLogger instance
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### `ApiLogger` — context & description
|
|
182
205
|
|
|
183
206
|
| Method | Description |
|
|
184
207
|
|--------|-------------|
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
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 |
|
|
188
214
|
| `isEnabled()` | Check if logging is active |
|
|
189
|
-
| `finalize(result, additionalInfo?)` | Write test result (`PASSED` / `FAILED` / `SKIPPED`) |
|
|
190
215
|
| `getLogFilePath()` | Get current log file path |
|
|
191
216
|
|
|
192
217
|
### `CurlGenerator`
|
|
@@ -201,16 +226,31 @@ Each log entry is a JSON object:
|
|
|
201
226
|
|-------------|---------|-------------|
|
|
202
227
|
| `API_LOGS` | `false` | Set to `'true'` to enable logging |
|
|
203
228
|
|
|
229
|
+
### `ApiLoggingOptions`
|
|
230
|
+
|
|
204
231
|
```typescript
|
|
205
|
-
// LoggerConfig
|
|
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
|
|
|
242
|
+
## Migration from v1 → v2
|
|
243
|
+
|
|
244
|
+
```diff
|
|
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);
|
|
252
|
+
```
|
|
253
|
+
|
|
214
254
|
## License
|
|
215
255
|
|
|
216
256
|
MIT
|
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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ApiLogger, createApiLogger, createSetupLogger, createTeardownLogger } from './ApiLogger';
|
|
2
2
|
export { CurlGenerator } from './CurlGenerator';
|
|
3
|
-
export
|
|
3
|
+
export { withApiLogging } from './withApiLogging';
|
|
4
|
+
export type { ApiLoggingOptions } from './withApiLogging';
|
|
5
|
+
export type { LoggerConfig, RequestLogData, ResponseLogData, StepLogEntry, TestLogDocument, LogEntry, LogContext, } from './types';
|
|
4
6
|
export type { RequestData } from './CurlGenerator';
|
|
5
7
|
//# 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,
|
|
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,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.CurlGenerator = exports.createTeardownLogger = exports.createSetupLogger = exports.createApiLogger = exports.ApiLogger = void 0;
|
|
3
|
+
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; } });
|
|
@@ -8,4 +8,6 @@ Object.defineProperty(exports, "createSetupLogger", { enumerable: true, get: fun
|
|
|
8
8
|
Object.defineProperty(exports, "createTeardownLogger", { enumerable: true, get: function () { return ApiLogger_1.createTeardownLogger; } });
|
|
9
9
|
var CurlGenerator_1 = require("./CurlGenerator");
|
|
10
10
|
Object.defineProperty(exports, "CurlGenerator", { enumerable: true, get: function () { return CurlGenerator_1.CurlGenerator; } });
|
|
11
|
+
var withApiLogging_1 = require("./withApiLogging");
|
|
12
|
+
Object.defineProperty(exports, "withApiLogging", { enumerable: true, get: function () { return withApiLogging_1.withApiLogging; } });
|
|
11
13
|
//# 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"}
|
|
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"}
|
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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy-based API logging wrapper for Playwright's APIRequestContext
|
|
3
|
+
* Zero changes to your controllers/clients — just wrap `request` in the fixture
|
|
4
|
+
*/
|
|
5
|
+
import type { APIRequestContext, TestInfo } from '@playwright/test';
|
|
6
|
+
import { ApiLogger } from './ApiLogger';
|
|
7
|
+
import { LogContext } from './types';
|
|
8
|
+
export interface ApiLoggingOptions {
|
|
9
|
+
/** Test name for log filename (auto-detected from testInfo if provided) */
|
|
10
|
+
testName?: string;
|
|
11
|
+
/** Test file path (auto-detected from testInfo if provided) */
|
|
12
|
+
testFile?: string;
|
|
13
|
+
/** Log context: 'preconditions' | 'test' | 'teardown' (default: 'test') */
|
|
14
|
+
context?: LogContext;
|
|
15
|
+
/** Custom log directory (default: 'logs/') */
|
|
16
|
+
logDirectory?: string;
|
|
17
|
+
/** Mask Authorization headers (default: true) */
|
|
18
|
+
maskAuthTokens?: boolean;
|
|
19
|
+
/** Existing logger to share across setup/test/teardown phases */
|
|
20
|
+
logger?: ApiLogger;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wrap Playwright's APIRequestContext with automatic logging.
|
|
24
|
+
* All HTTP calls (get, post, put, patch, delete, head, fetch) are intercepted and logged.
|
|
25
|
+
*
|
|
26
|
+
* @param request - Playwright APIRequestContext
|
|
27
|
+
* @param testInfoOrOptions - TestInfo object or ApiLoggingOptions
|
|
28
|
+
* @returns Proxied APIRequestContext with logging + logger reference via `__logger`
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Minimal — just pass testInfo:
|
|
32
|
+
* const loggedRequest = withApiLogging(request, testInfo);
|
|
33
|
+
* const apiClient = new ApiClient(loggedRequest);
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // With preconditions and test steps:
|
|
37
|
+
* const loggedRequest = withApiLogging(request, testInfo);
|
|
38
|
+
* loggedRequest.__logger.startPreconditions();
|
|
39
|
+
* loggedRequest.__logger.describe('Get employee for test');
|
|
40
|
+
* await apiClient.getEmployees();
|
|
41
|
+
* loggedRequest.__logger.startTest();
|
|
42
|
+
* loggedRequest.__logger.describe('Try to access without token');
|
|
43
|
+
* await apiClient.getWithoutAuth();
|
|
44
|
+
* loggedRequest.__logger.finalize('PASSED');
|
|
45
|
+
*/
|
|
46
|
+
export declare function withApiLogging(request: APIRequestContext, testInfoOrOptions?: TestInfo | ApiLoggingOptions): APIRequestContext & {
|
|
47
|
+
__logger: ApiLogger;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=withApiLogging.d.ts.map
|
|
@@ -0,0 +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;AAEnD,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,iEAAiE;IACjE,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,iBAAiB,CAAC,EAAE,QAAQ,GAAG,iBAAiB,GAC/C,iBAAiB,GAAG;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAmG7C"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proxy-based API logging wrapper for Playwright's APIRequestContext
|
|
4
|
+
* Zero changes to your controllers/clients — just wrap `request` in the fixture
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.withApiLogging = withApiLogging;
|
|
8
|
+
const ApiLogger_1 = require("./ApiLogger");
|
|
9
|
+
/**
|
|
10
|
+
* Wrap Playwright's APIRequestContext with automatic logging.
|
|
11
|
+
* All HTTP calls (get, post, put, patch, delete, head, fetch) are intercepted and logged.
|
|
12
|
+
*
|
|
13
|
+
* @param request - Playwright APIRequestContext
|
|
14
|
+
* @param testInfoOrOptions - TestInfo object or ApiLoggingOptions
|
|
15
|
+
* @returns Proxied APIRequestContext with logging + logger reference via `__logger`
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Minimal — just pass testInfo:
|
|
19
|
+
* const loggedRequest = withApiLogging(request, testInfo);
|
|
20
|
+
* const apiClient = new ApiClient(loggedRequest);
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // With preconditions and test steps:
|
|
24
|
+
* const loggedRequest = withApiLogging(request, testInfo);
|
|
25
|
+
* loggedRequest.__logger.startPreconditions();
|
|
26
|
+
* loggedRequest.__logger.describe('Get employee for test');
|
|
27
|
+
* await apiClient.getEmployees();
|
|
28
|
+
* loggedRequest.__logger.startTest();
|
|
29
|
+
* loggedRequest.__logger.describe('Try to access without token');
|
|
30
|
+
* await apiClient.getWithoutAuth();
|
|
31
|
+
* loggedRequest.__logger.finalize('PASSED');
|
|
32
|
+
*/
|
|
33
|
+
function withApiLogging(request, testInfoOrOptions) {
|
|
34
|
+
let options;
|
|
35
|
+
if (testInfoOrOptions && 'title' in testInfoOrOptions) {
|
|
36
|
+
const testInfo = testInfoOrOptions;
|
|
37
|
+
options = {
|
|
38
|
+
testName: testInfo.title,
|
|
39
|
+
testFile: testInfo.file,
|
|
40
|
+
context: 'test',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
options = testInfoOrOptions || {};
|
|
45
|
+
}
|
|
46
|
+
// Use shared logger or create new
|
|
47
|
+
let logger;
|
|
48
|
+
if (options.logger) {
|
|
49
|
+
logger = options.logger;
|
|
50
|
+
if (options.context) {
|
|
51
|
+
logger.setContext(options.context);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const config = {
|
|
56
|
+
testName: options.testName,
|
|
57
|
+
testFile: options.testFile,
|
|
58
|
+
context: options.context || 'test',
|
|
59
|
+
logDirectory: options.logDirectory,
|
|
60
|
+
maskAuthTokens: options.maskAuthTokens,
|
|
61
|
+
};
|
|
62
|
+
logger = new ApiLogger_1.ApiLogger(config);
|
|
63
|
+
}
|
|
64
|
+
// If logging is disabled, return original request with logger ref
|
|
65
|
+
if (!logger.isEnabled()) {
|
|
66
|
+
return Object.assign(request, { __logger: logger });
|
|
67
|
+
}
|
|
68
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'fetch'];
|
|
69
|
+
const proxy = new Proxy(request, {
|
|
70
|
+
get(target, prop, receiver) {
|
|
71
|
+
const propName = typeof prop === 'string' ? prop : '';
|
|
72
|
+
if (propName === '__logger') {
|
|
73
|
+
return logger;
|
|
74
|
+
}
|
|
75
|
+
if (HTTP_METHODS.includes(propName)) {
|
|
76
|
+
return async (url, reqOptions) => {
|
|
77
|
+
const method = propName === 'fetch' ? (reqOptions?.method || 'GET') : propName;
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
const requestHeaders = reqOptions?.headers;
|
|
80
|
+
const requestBody = extractBody(reqOptions);
|
|
81
|
+
try {
|
|
82
|
+
const response = await target[propName](url, reqOptions);
|
|
83
|
+
const duration = Date.now() - startTime;
|
|
84
|
+
const responseBody = await safeParseResponseBody(response);
|
|
85
|
+
const responseHeaders = extractResponseHeaders(response);
|
|
86
|
+
logger.logApiCall(method.toUpperCase(), response.url(), requestHeaders, requestBody, response.status(), responseHeaders, responseBody, duration);
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const duration = Date.now() - startTime;
|
|
91
|
+
logger.logApiCall(method.toUpperCase(), url, requestHeaders, requestBody, 0, undefined, { error: String(error) }, duration);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return Reflect.get(target, prop, receiver);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return Object.assign(proxy, { __logger: logger });
|
|
100
|
+
}
|
|
101
|
+
function extractBody(options) {
|
|
102
|
+
if (!options)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (options.data)
|
|
105
|
+
return options.data;
|
|
106
|
+
if (options.form)
|
|
107
|
+
return options.form;
|
|
108
|
+
if (options.multipart)
|
|
109
|
+
return options.multipart;
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
async function safeParseResponseBody(response) {
|
|
113
|
+
try {
|
|
114
|
+
return await response.json();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
try {
|
|
118
|
+
return await response.text();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function extractResponseHeaders(response) {
|
|
126
|
+
try {
|
|
127
|
+
return response.headers();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=withApiLogging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withApiLogging.js","sourceRoot":"","sources":["../src/withApiLogging.ts"],"names":[],"mappings":"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"}
|