newman-reporter-dashboard-wrapper 1.0.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/dist/.newman-history.json +456 -0
- package/dist/dashboard.html +384 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
- package/readme.md +20 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"runId": "RUN-683232",
|
|
4
|
+
"collectionName": "Public API Test Suite",
|
|
5
|
+
"timestamp": "2026-06-25 05:18:24",
|
|
6
|
+
"totalRequests": 2,
|
|
7
|
+
"passedAssertions": 4,
|
|
8
|
+
"failedAssertions": 0,
|
|
9
|
+
"avgResponseTime": 327,
|
|
10
|
+
"status": "Success",
|
|
11
|
+
"requests": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Get All Posts",
|
|
14
|
+
"method": "GET",
|
|
15
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
16
|
+
"statusCode": 0,
|
|
17
|
+
"responseTime": 0,
|
|
18
|
+
"assertions": []
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "Create New Post",
|
|
22
|
+
"method": "POST",
|
|
23
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
24
|
+
"statusCode": 0,
|
|
25
|
+
"responseTime": 0,
|
|
26
|
+
"assertions": []
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"runId": "RUN-518049",
|
|
32
|
+
"collectionName": "Public API Test Suite",
|
|
33
|
+
"timestamp": "2026-06-25 05:19:13",
|
|
34
|
+
"totalRequests": 2,
|
|
35
|
+
"passedAssertions": 4,
|
|
36
|
+
"failedAssertions": 0,
|
|
37
|
+
"avgResponseTime": 469,
|
|
38
|
+
"status": "Success",
|
|
39
|
+
"requests": [
|
|
40
|
+
{
|
|
41
|
+
"name": "Get All Posts",
|
|
42
|
+
"method": "GET",
|
|
43
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
44
|
+
"statusCode": 0,
|
|
45
|
+
"responseTime": 0,
|
|
46
|
+
"assertions": []
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "Create New Post",
|
|
50
|
+
"method": "POST",
|
|
51
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
52
|
+
"statusCode": 0,
|
|
53
|
+
"responseTime": 0,
|
|
54
|
+
"assertions": []
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"runId": "RUN-854823",
|
|
60
|
+
"collectionName": "Public API Test Suite",
|
|
61
|
+
"timestamp": "2026-06-25 05:21:41",
|
|
62
|
+
"totalRequests": 2,
|
|
63
|
+
"passedAssertions": 4,
|
|
64
|
+
"failedAssertions": 0,
|
|
65
|
+
"avgResponseTime": 260,
|
|
66
|
+
"status": "Success",
|
|
67
|
+
"requests": [
|
|
68
|
+
{
|
|
69
|
+
"name": "Get All Posts",
|
|
70
|
+
"method": "GET",
|
|
71
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
72
|
+
"statusCode": 0,
|
|
73
|
+
"responseTime": 0,
|
|
74
|
+
"assertions": []
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "Create New Post",
|
|
78
|
+
"method": "POST",
|
|
79
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
80
|
+
"statusCode": 0,
|
|
81
|
+
"responseTime": 0,
|
|
82
|
+
"assertions": []
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"runId": "RUN-948995",
|
|
88
|
+
"collectionName": "Public API Test Suite",
|
|
89
|
+
"timestamp": "2026-06-25 06:12:26",
|
|
90
|
+
"totalRequests": 2,
|
|
91
|
+
"passedAssertions": 4,
|
|
92
|
+
"failedAssertions": 0,
|
|
93
|
+
"avgResponseTime": 302,
|
|
94
|
+
"status": "Success",
|
|
95
|
+
"requests": [
|
|
96
|
+
{
|
|
97
|
+
"name": "Get All Posts",
|
|
98
|
+
"method": "GET",
|
|
99
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
100
|
+
"statusCode": 0,
|
|
101
|
+
"responseTime": 0,
|
|
102
|
+
"assertions": []
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "Create New Post",
|
|
106
|
+
"method": "POST",
|
|
107
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
108
|
+
"statusCode": 0,
|
|
109
|
+
"responseTime": 0,
|
|
110
|
+
"assertions": []
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"runId": "RUN-759529",
|
|
116
|
+
"collectionName": "Public API Test Suite",
|
|
117
|
+
"timestamp": "2026-06-25 06:12:59",
|
|
118
|
+
"totalRequests": 2,
|
|
119
|
+
"passedAssertions": 4,
|
|
120
|
+
"failedAssertions": 0,
|
|
121
|
+
"avgResponseTime": 255,
|
|
122
|
+
"status": "Success",
|
|
123
|
+
"requests": [
|
|
124
|
+
{
|
|
125
|
+
"name": "Get All Posts",
|
|
126
|
+
"method": "GET",
|
|
127
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
128
|
+
"statusCode": 0,
|
|
129
|
+
"responseTime": 0,
|
|
130
|
+
"assertions": []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "Create New Post",
|
|
134
|
+
"method": "POST",
|
|
135
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
136
|
+
"statusCode": 0,
|
|
137
|
+
"responseTime": 0,
|
|
138
|
+
"assertions": []
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"runId": "RUN-708198",
|
|
144
|
+
"collectionName": "Public API Test Suite",
|
|
145
|
+
"timestamp": "2026-06-25 06:14:17",
|
|
146
|
+
"totalRequests": 2,
|
|
147
|
+
"passedAssertions": 3,
|
|
148
|
+
"failedAssertions": 1,
|
|
149
|
+
"avgResponseTime": 487,
|
|
150
|
+
"status": "Failure",
|
|
151
|
+
"requests": [
|
|
152
|
+
{
|
|
153
|
+
"name": "Get All Posts",
|
|
154
|
+
"method": "GET",
|
|
155
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
156
|
+
"statusCode": 0,
|
|
157
|
+
"responseTime": 0,
|
|
158
|
+
"assertions": []
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"name": "Create New Post",
|
|
162
|
+
"method": "POST",
|
|
163
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
164
|
+
"statusCode": 0,
|
|
165
|
+
"responseTime": 0,
|
|
166
|
+
"assertions": []
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"runId": "RUN-760015",
|
|
172
|
+
"collectionName": "Public API Test Suite",
|
|
173
|
+
"timestamp": "2026-06-25 06:18:20",
|
|
174
|
+
"totalRequests": 2,
|
|
175
|
+
"passedAssertions": 8,
|
|
176
|
+
"failedAssertions": 0,
|
|
177
|
+
"avgResponseTime": 545,
|
|
178
|
+
"status": "Success",
|
|
179
|
+
"requests": [
|
|
180
|
+
{
|
|
181
|
+
"name": "Get All Posts",
|
|
182
|
+
"method": "GET",
|
|
183
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
184
|
+
"statusCode": 0,
|
|
185
|
+
"responseTime": 0,
|
|
186
|
+
"assertions": []
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "Create New Post",
|
|
190
|
+
"method": "POST",
|
|
191
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
192
|
+
"statusCode": 0,
|
|
193
|
+
"responseTime": 0,
|
|
194
|
+
"assertions": []
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"runId": "RUN-766800",
|
|
200
|
+
"collectionName": "Public API Test Suite",
|
|
201
|
+
"timestamp": "2026-06-25 06:19:57",
|
|
202
|
+
"totalRequests": 2,
|
|
203
|
+
"passedAssertions": 8,
|
|
204
|
+
"failedAssertions": 0,
|
|
205
|
+
"avgResponseTime": 548,
|
|
206
|
+
"status": "Success",
|
|
207
|
+
"requests": [
|
|
208
|
+
{
|
|
209
|
+
"id": "623bbfa7-945e-4177-8908-d4095e3b523e",
|
|
210
|
+
"name": "Get All Posts",
|
|
211
|
+
"method": "GET",
|
|
212
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
213
|
+
"statusCode": 200,
|
|
214
|
+
"responseTime": 315,
|
|
215
|
+
"assertions": [
|
|
216
|
+
{
|
|
217
|
+
"name": "Status code is 200",
|
|
218
|
+
"passed": true
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"name": "Response time is within acceptable SLA limits",
|
|
222
|
+
"passed": true
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "Response is an array containing posts",
|
|
226
|
+
"passed": true
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "First post object complies with data schema contract",
|
|
230
|
+
"passed": true
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"id": "e99530c3-e9b2-48c5-a23e-3b4c6d946f46",
|
|
236
|
+
"name": "Create New Post",
|
|
237
|
+
"method": "POST",
|
|
238
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
239
|
+
"statusCode": 201,
|
|
240
|
+
"responseTime": 780,
|
|
241
|
+
"assertions": [
|
|
242
|
+
{
|
|
243
|
+
"name": "Status code is 201 Created",
|
|
244
|
+
"passed": true
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"name": "Response headers confirm JSON payload structure",
|
|
248
|
+
"passed": true
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"name": "Response returns generated resource identity",
|
|
252
|
+
"passed": true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "Response contains the complete mirrored request body data",
|
|
256
|
+
"passed": true
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"runId": "RUN-152281",
|
|
264
|
+
"collectionName": "Public API Test Suite",
|
|
265
|
+
"timestamp": "2026-06-25 06:24:56",
|
|
266
|
+
"totalRequests": 2,
|
|
267
|
+
"passedAssertions": 8,
|
|
268
|
+
"failedAssertions": 0,
|
|
269
|
+
"avgResponseTime": 315,
|
|
270
|
+
"status": "Success",
|
|
271
|
+
"requests": [
|
|
272
|
+
{
|
|
273
|
+
"id": "32d48e3e-2463-418e-81c6-9559fd9e3dcb",
|
|
274
|
+
"name": "Get All Posts",
|
|
275
|
+
"method": "GET",
|
|
276
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
277
|
+
"statusCode": 200,
|
|
278
|
+
"responseTime": 323,
|
|
279
|
+
"assertions": [
|
|
280
|
+
{
|
|
281
|
+
"name": "Status code is 200",
|
|
282
|
+
"passed": true
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"name": "Response time is within acceptable SLA limits",
|
|
286
|
+
"passed": true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"name": "Response is an array containing posts",
|
|
290
|
+
"passed": true
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "First post object complies with data schema contract",
|
|
294
|
+
"passed": true
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"id": "a148ca0b-10a2-41fb-bf76-65e34813babb",
|
|
300
|
+
"name": "Create New Post",
|
|
301
|
+
"method": "POST",
|
|
302
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
303
|
+
"statusCode": 201,
|
|
304
|
+
"responseTime": 306,
|
|
305
|
+
"assertions": [
|
|
306
|
+
{
|
|
307
|
+
"name": "Status code is 201 Created",
|
|
308
|
+
"passed": true
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "Response headers confirm JSON payload structure",
|
|
312
|
+
"passed": true
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "Response returns generated resource identity",
|
|
316
|
+
"passed": true
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"name": "Response contains the complete mirrored request body data",
|
|
320
|
+
"passed": true
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"runId": "RUN-132160",
|
|
328
|
+
"collectionName": "Public API Test Suite",
|
|
329
|
+
"timestamp": "2026-06-25 06:25:33",
|
|
330
|
+
"totalRequests": 2,
|
|
331
|
+
"passedAssertions": 7,
|
|
332
|
+
"failedAssertions": 1,
|
|
333
|
+
"avgResponseTime": 401,
|
|
334
|
+
"status": "Failure",
|
|
335
|
+
"requests": [
|
|
336
|
+
{
|
|
337
|
+
"id": "e2c848fc-90eb-447f-a59d-76711e6d082e",
|
|
338
|
+
"name": "Get All Posts",
|
|
339
|
+
"method": "GET",
|
|
340
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
341
|
+
"statusCode": 200,
|
|
342
|
+
"responseTime": 411,
|
|
343
|
+
"assertions": [
|
|
344
|
+
{
|
|
345
|
+
"name": "Status code is 200",
|
|
346
|
+
"passed": false,
|
|
347
|
+
"errorMessage": "expected response to have status code 201 but got 200"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"name": "Response time is within acceptable SLA limits",
|
|
351
|
+
"passed": true
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"name": "Response is an array containing posts",
|
|
355
|
+
"passed": true
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "First post object complies with data schema contract",
|
|
359
|
+
"passed": true
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"id": "74b3409f-de1f-47a7-9994-7ebf08da94fe",
|
|
365
|
+
"name": "Create New Post",
|
|
366
|
+
"method": "POST",
|
|
367
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
368
|
+
"statusCode": 201,
|
|
369
|
+
"responseTime": 390,
|
|
370
|
+
"assertions": [
|
|
371
|
+
{
|
|
372
|
+
"name": "Status code is 201 Created",
|
|
373
|
+
"passed": true
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"name": "Response headers confirm JSON payload structure",
|
|
377
|
+
"passed": true
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"name": "Response returns generated resource identity",
|
|
381
|
+
"passed": true
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"name": "Response contains the complete mirrored request body data",
|
|
385
|
+
"passed": true
|
|
386
|
+
}
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"runId": "RUN-975070",
|
|
393
|
+
"collectionName": "Public API Test Suite",
|
|
394
|
+
"timestamp": "2026-06-25 06:33:12",
|
|
395
|
+
"totalRequests": 2,
|
|
396
|
+
"passedAssertions": 7,
|
|
397
|
+
"failedAssertions": 1,
|
|
398
|
+
"avgResponseTime": 286,
|
|
399
|
+
"status": "Failure",
|
|
400
|
+
"requests": [
|
|
401
|
+
{
|
|
402
|
+
"id": "6e21365e-5752-4231-a7d9-bae2cef99003",
|
|
403
|
+
"name": "Get All Posts",
|
|
404
|
+
"method": "GET",
|
|
405
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
406
|
+
"statusCode": 200,
|
|
407
|
+
"responseTime": 282,
|
|
408
|
+
"assertions": [
|
|
409
|
+
{
|
|
410
|
+
"name": "Status code is 200",
|
|
411
|
+
"passed": false,
|
|
412
|
+
"errorMessage": "expected response to have status code 201 but got 200"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"name": "Response time is within acceptable SLA limits",
|
|
416
|
+
"passed": true
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"name": "Response is an array containing posts",
|
|
420
|
+
"passed": true
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"name": "First post object complies with data schema contract",
|
|
424
|
+
"passed": true
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "9e3c5316-f15f-47c1-9065-0704235bc6d5",
|
|
430
|
+
"name": "Create New Post",
|
|
431
|
+
"method": "POST",
|
|
432
|
+
"url": "https://jsonplaceholder.typicode.com/posts",
|
|
433
|
+
"statusCode": 201,
|
|
434
|
+
"responseTime": 289,
|
|
435
|
+
"assertions": [
|
|
436
|
+
{
|
|
437
|
+
"name": "Status code is 201 Created",
|
|
438
|
+
"passed": true
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"name": "Response headers confirm JSON payload structure",
|
|
442
|
+
"passed": true
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"name": "Response returns generated resource identity",
|
|
446
|
+
"passed": true
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "Response contains the complete mirrored request body data",
|
|
450
|
+
"passed": true
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Newman Orchestrator: Interactive Analytics</title>
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
body { background-color: #f4f6f9; font-family: system-ui, -apple-system, sans-serif; }
|
|
11
|
+
.card { border: none; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.03); }
|
|
12
|
+
.clickable-row { cursor: pointer; transition: background-color 0.2s; }
|
|
13
|
+
.clickable-row:hover { background-color: #f0f4f8 !important; }
|
|
14
|
+
.status-Success { background-color: #d1e7dd; color: #0f5132; padding: 4px 10px; border-radius: 20px; font-size: 0.82rem; font-weight: 600; }
|
|
15
|
+
.status-Warning { background-color: #fff3cd; color: #664d03; padding: 4px 10px; border-radius: 20px; font-size: 0.82rem; font-weight: 600; }
|
|
16
|
+
.status-Failure { background-color: #f8d7da; color: #842029; padding: 4px 10px; border-radius: 20px; font-size: 0.82rem; font-weight: 600; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div class="container-fluid py-4 px-5">
|
|
21
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
22
|
+
<h2 class="fw-bold m-0">🚀 Newman Orchestrator Analytics</h2>
|
|
23
|
+
<span class="badge bg-dark px-3 py-2 fs-6">V1.0.0</span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="row g-3 mb-4">
|
|
27
|
+
<div class="col-md-4">
|
|
28
|
+
<div class="card p-3">
|
|
29
|
+
<small class="text-muted text-uppercase fw-bold">Run History</small>
|
|
30
|
+
<h3 id="stat-total-runs" class="fw-bold m-0">0 runs</h3>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="col-md-4">
|
|
34
|
+
<div class="card p-3">
|
|
35
|
+
<small class="text-muted text-uppercase fw-bold">Success Pass Rate</small>
|
|
36
|
+
<h3 id="stat-success-rate" class="fw-bold m-0 text-success">0%</h3>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="col-md-4">
|
|
40
|
+
<div class="card p-3">
|
|
41
|
+
<small class="text-muted text-uppercase fw-bold">Details Engine</small>
|
|
42
|
+
<h3 class="fw-bold m-0 text-primary">● Active</h3>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="row g-3 mb-4">
|
|
48
|
+
<div class="col-md-8">
|
|
49
|
+
<div class="card p-3 h-100">
|
|
50
|
+
<h6 class="fw-bold mb-3 text-secondary">Historical Pass / Fail Trend & Response Times</h6>
|
|
51
|
+
<canvas id="trendChart" height="120"></canvas>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="col-md-4">
|
|
55
|
+
<div class="card p-3 h-100">
|
|
56
|
+
<canvas id="latencyChart"></canvas>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="card p-4">
|
|
62
|
+
<h5 class="fw-bold mb-3">Execution Ledger <span class="fs-6 fw-normal text-muted">(Click any row or graph coordinate to reveal request details)</span></h5>
|
|
63
|
+
<div class="table-responsive">
|
|
64
|
+
<table class="table table-hover align-middle m-0">
|
|
65
|
+
<thead class="table-light">
|
|
66
|
+
<tr>
|
|
67
|
+
<th>Run ID</th>
|
|
68
|
+
<th>Collection Target</th>
|
|
69
|
+
<th>Timestamp</th>
|
|
70
|
+
<th>Assertions (P/F)</th>
|
|
71
|
+
<th>Avg Response Time</th>
|
|
72
|
+
<th>Status</th>
|
|
73
|
+
</tr>
|
|
74
|
+
</thead>
|
|
75
|
+
<tbody id="execution-table-body"></tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="offcanvas offcanvas-end style-drawer" style="width: 45%;" tabIndex="-1" id="detailsDrawer" aria-labelledby="detailsDrawerLabel">
|
|
82
|
+
<div class="offcanvas-header bg-dark text-white">
|
|
83
|
+
<h5 class="offcanvas-title fw-bold" id="detailsDrawerLabel">Run Details</h5>
|
|
84
|
+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="offcanvas-body" id="drawer-content-body">
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
91
|
+
<script>
|
|
92
|
+
let cachedData = [];
|
|
93
|
+
const bsDrawer = new bootstrap.Offcanvas(document.getElementById('detailsDrawer'));
|
|
94
|
+
|
|
95
|
+
async function initMetricsEngine() {
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch('/api/history');
|
|
98
|
+
cachedData = await res.json();
|
|
99
|
+
|
|
100
|
+
if (!cachedData || cachedData.length === 0) {
|
|
101
|
+
console.warn("Database storage is currently empty.");
|
|
102
|
+
document.getElementById('execution-table-body').innerHTML =
|
|
103
|
+
`<tr><td colspan="6" class="text-center text-muted py-4">No data streams captured yet. Please run your collection suite to feed data.</td></tr>`;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
document.getElementById('stat-total-runs').innerText = `${cachedData.length} executions`;
|
|
108
|
+
const passCount = cachedData.filter(r => r.status !== 'Failure').length;
|
|
109
|
+
document.getElementById('stat-success-rate').innerText = `${Math.round((passCount / cachedData.length) * 100)}%`;
|
|
110
|
+
|
|
111
|
+
const tbody = document.getElementById('execution-table-body');
|
|
112
|
+
tbody.innerHTML = "";
|
|
113
|
+
|
|
114
|
+
cachedData.slice().reverse().forEach(run => {
|
|
115
|
+
const tr = document.createElement('tr');
|
|
116
|
+
tr.className = 'clickable-row';
|
|
117
|
+
tr.onclick = () => showDetailedRunBreakdown(run.runId);
|
|
118
|
+
tr.innerHTML = `
|
|
119
|
+
<td class="fw-bold text-primary">${run.runId}</td>
|
|
120
|
+
<td>${run.collectionName}</td>
|
|
121
|
+
<td class="text-muted">${run.timestamp}</td>
|
|
122
|
+
<td><span class="text-success">${run.passedAssertions} ✓</span> / <span class="text-danger">${run.failedAssertions} ✗</span></td>
|
|
123
|
+
<td><strong>${run.avgResponseTime}ms</strong></td>
|
|
124
|
+
<td><span class="status-${run.status}">${run.status}</span></td>
|
|
125
|
+
`;
|
|
126
|
+
tbody.appendChild(tr);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
renderCharts(cachedData);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("Dashboard engine rendering failure:", err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Heuristic AI Engine to generate free, context-aware QA advice for failing runs
|
|
136
|
+
function generateAIAdvice(run) {
|
|
137
|
+
let insights = [];
|
|
138
|
+
const highLatencyRequests = run.requests.filter(r => r.responseTime > 800);
|
|
139
|
+
|
|
140
|
+
// Captures requests that either have broken HTTP status codes OR have explicitly failed assertions
|
|
141
|
+
const failedRequests = run.requests.filter(r =>
|
|
142
|
+
r.statusCode >= 400 ||
|
|
143
|
+
(r.assertions && r.assertions.some(a => !a.passed))
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Scenario 1: Total Success Matrix
|
|
147
|
+
if (run.failedAssertions === 0 && run.status === 'Success' && highLatencyRequests.length === 0) {
|
|
148
|
+
return `
|
|
149
|
+
<div class="alert alert-success border-0 shadow-sm mb-3">
|
|
150
|
+
<h6 class="fw-bold mb-1">🤖 AI Advisor: System Healthy</h6>
|
|
151
|
+
<small class="d-block text-secondary">All endpoints responded within benchmark thresholds with 100% assertion parity. No regression risks detected. Ready for deployment pipeline integration.</small>
|
|
152
|
+
</div>`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Scenario 2: Global performance degradation
|
|
156
|
+
if (run.avgResponseTime > 1000) {
|
|
157
|
+
insights.push(`⚠️ <strong>Global Latency Warning:</strong> The collection's average response time is running slow at <strong>${run.avgResponseTime}ms</strong>. This suggests network congestion, slow environment proxies, or resource-heavy database lookups.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Scenario 3: Individual latency outliers
|
|
161
|
+
if (highLatencyRequests.length > 0) {
|
|
162
|
+
highLatencyRequests.forEach(r => {
|
|
163
|
+
insights.push(`🐢 <strong>Endpoint Degraded:</strong> <code>[${r.method}] ${r.name}</code> took <strong>${r.responseTime}ms</strong>. This exceeds safe interactive API performance budgets (800ms). Consider applying data caching or optimizing server queries.`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Scenario 4: Broken tests / assertions
|
|
168
|
+
if (failedRequests.length > 0) {
|
|
169
|
+
failedRequests.forEach(r => {
|
|
170
|
+
const code = r.statusCode;
|
|
171
|
+
const brokenAssertions = r.assertions ? r.assertions.filter(a => !a.passed) : [];
|
|
172
|
+
|
|
173
|
+
if (code === 401 || code === 403) {
|
|
174
|
+
insights.push(`🔒 <strong>Authentication Fault on "${r.name}":</strong> Request dropped an access error (<strong>Status ${code}</strong>). Check if your environmental Authorization header tokens, API keys, or OAuth sessions expired.`);
|
|
175
|
+
} else if (code >= 500) {
|
|
176
|
+
insights.push(`💥 <strong>Server-Side Crash on "${r.name}":</strong> Backend returned a <strong>Status ${code}</strong> internal runtime error. Check your server logs or application container traces for active unhandled exceptions.`);
|
|
177
|
+
} else if (brokenAssertions.length > 0) {
|
|
178
|
+
// Pinpoint exactly which script validation assertion failed
|
|
179
|
+
brokenAssertions.forEach(a => {
|
|
180
|
+
let adviceString = `❌ <strong>Assertion Failed on "${r.name}":</strong> The check <code>"${a.name}"</code> collapsed.`;
|
|
181
|
+
if (a.errorMessage) {
|
|
182
|
+
adviceString += ` Backend unexpected value details: <em class="text-danger">${a.errorMessage}</em>`;
|
|
183
|
+
} else {
|
|
184
|
+
adviceString += ` Review your endpoint's response payload structure to ensure it matches the Postman test schema expectation.`;
|
|
185
|
+
}
|
|
186
|
+
insights.push(adviceString);
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
insights.push(`⚠️ <strong>Unexpected Code on "${r.name}":</strong> Returned a client-side warning error (<strong>Status ${code}</strong>). Verify parameter schema spelling parameters.`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Render the advice box matrix inside the view canvas layout
|
|
195
|
+
return `
|
|
196
|
+
<div class="card bg-light border-0 shadow-sm mb-4">
|
|
197
|
+
<div class="card-body">
|
|
198
|
+
<h6 class="fw-bold text-dark mb-3 d-flex align-items-center gap-2">
|
|
199
|
+
<span>🤖 AI QA Advisor Insights</span>
|
|
200
|
+
<span class="badge bg-danger font-monospace text-white" style="font-size:10px;">${run.failedAssertions} Failure(s) Detected</span>
|
|
201
|
+
</h6>
|
|
202
|
+
<ul class="small text-dark ps-3 m-0 d-flex flex-column gap-2">
|
|
203
|
+
${insights.map(insight => `<li>${insight}</li>`).join('')}
|
|
204
|
+
</ul>
|
|
205
|
+
</div>
|
|
206
|
+
</div>`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function showDetailedRunBreakdown(runId) {
|
|
210
|
+
const run = cachedData.find(r => r.runId === runId);
|
|
211
|
+
if (!run) return;
|
|
212
|
+
|
|
213
|
+
document.getElementById('detailsDrawerLabel').innerText = `Execution Report: ${run.runId}`;
|
|
214
|
+
const body = document.getElementById('drawer-content-body');
|
|
215
|
+
|
|
216
|
+
const aiAdviceHTML = generateAIAdvice(run);
|
|
217
|
+
|
|
218
|
+
let htmlHTML = `<div class="mb-4">
|
|
219
|
+
<h6><strong>Collection Suite:</strong> ${run.collectionName}</h6>
|
|
220
|
+
<p class="text-muted mb-1">Executed at: ${run.timestamp}</p>
|
|
221
|
+
<div class="badge bg-secondary p-2">Average Response Latency: ${run.avgResponseTime}ms</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
${aiAdviceHTML}
|
|
225
|
+
<hr/>`;
|
|
226
|
+
|
|
227
|
+
run.requests.forEach((req, idx) => {
|
|
228
|
+
const statusBadge = req.statusCode >= 200 && req.statusCode < 300 ? 'bg-success' : 'bg-danger';
|
|
229
|
+
|
|
230
|
+
htmlHTML += `
|
|
231
|
+
<div class="card p-3 mb-3 border">
|
|
232
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
233
|
+
<h6 class="fw-bold text-dark m-0">${idx + 1}. ${req.name}</h6>
|
|
234
|
+
<span class="badge ${statusBadge}">${req.method} ${req.statusCode}</span>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="text-muted font-monospace small mb-2 text-truncate">${req.url}</div>
|
|
237
|
+
<div class="small text-secondary mb-2">Response Time: <strong>${req.responseTime}ms</strong></div>
|
|
238
|
+
|
|
239
|
+
<div class="mt-2">
|
|
240
|
+
<h6>Assertions:</h6>
|
|
241
|
+
${!req.assertions || req.assertions.length === 0 ? '<p class="text-muted small italic m-0">No assertions defined for this step.</p>' : ''}
|
|
242
|
+
${req.assertions ? req.assertions.map(a => `
|
|
243
|
+
<div class="d-flex align-items-start gap-2 my-1">
|
|
244
|
+
<span>${a.passed ? '✅' : '❌'}</span>
|
|
245
|
+
<div>
|
|
246
|
+
<span class="${a.passed ? 'text-success' : 'text-danger fw-bold'}">${a.name}</span>
|
|
247
|
+
${a.errorMessage ? `<div class="text-muted font-monospace text-xs" style="font-size:11px">${a.errorMessage}</div>` : ''}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
`).join('') : ''}
|
|
251
|
+
</div>
|
|
252
|
+
</div>`;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
body.innerHTML = htmlHTML;
|
|
256
|
+
bsDrawer.show();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function renderCharts(data) {
|
|
260
|
+
const existingTrendChart = Chart.getChart("trendChart");
|
|
261
|
+
if (existingTrendChart) existingTrendChart.destroy();
|
|
262
|
+
|
|
263
|
+
const existingLatencyChart = Chart.getChart("latencyChart");
|
|
264
|
+
if (existingLatencyChart) existingLatencyChart.destroy();
|
|
265
|
+
|
|
266
|
+
const ctx = document.getElementById('trendChart');
|
|
267
|
+
if (!ctx) return;
|
|
268
|
+
|
|
269
|
+
new Chart(ctx, {
|
|
270
|
+
type: 'bar',
|
|
271
|
+
data: {
|
|
272
|
+
labels: data.map(r => r.runId),
|
|
273
|
+
datasets: [
|
|
274
|
+
{
|
|
275
|
+
label: 'Passed Assertions',
|
|
276
|
+
data: data.map(r => r.passedAssertions),
|
|
277
|
+
backgroundColor: 'rgba(25, 135, 84, 0.6)',
|
|
278
|
+
borderColor: '#198754',
|
|
279
|
+
borderWidth: 1,
|
|
280
|
+
stack: 'assertions',
|
|
281
|
+
yAxisID: 'y'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
label: 'Failed Assertions',
|
|
285
|
+
data: data.map(r => r.failedAssertions),
|
|
286
|
+
backgroundColor: 'rgba(220, 53, 69, 0.6)',
|
|
287
|
+
borderColor: '#dc3545',
|
|
288
|
+
borderWidth: 1,
|
|
289
|
+
stack: 'assertions',
|
|
290
|
+
yAxisID: 'y'
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
label: 'Avg Response Time (ms)',
|
|
294
|
+
data: data.map(r => r.avgResponseTime),
|
|
295
|
+
type: 'line',
|
|
296
|
+
borderColor: '#0d6efd',
|
|
297
|
+
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
|
298
|
+
tension: 0.3,
|
|
299
|
+
fill: true,
|
|
300
|
+
pointRadius: 5,
|
|
301
|
+
pointHoverRadius: 8,
|
|
302
|
+
yAxisID: 'y1'
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
options: {
|
|
307
|
+
responsive: true,
|
|
308
|
+
plugins: {
|
|
309
|
+
legend: { position: 'top' },
|
|
310
|
+
tooltip: {
|
|
311
|
+
mode: 'index',
|
|
312
|
+
intersect: false,
|
|
313
|
+
callbacks: {
|
|
314
|
+
title: (context) => `Execution Details: ${context[0].label}`,
|
|
315
|
+
label: function(context) {
|
|
316
|
+
let label = context.dataset.label || '';
|
|
317
|
+
if (label) label += ': ';
|
|
318
|
+
if (context.datasetIndex === 2) {
|
|
319
|
+
label += `${context.raw} ms`;
|
|
320
|
+
} else {
|
|
321
|
+
label += `${context.raw} checks`;
|
|
322
|
+
}
|
|
323
|
+
return label;
|
|
324
|
+
},
|
|
325
|
+
footer: (context) => '💡 Click point to open full API request trace panel.'
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
scales: {
|
|
330
|
+
y: {
|
|
331
|
+
type: 'linear',
|
|
332
|
+
position: 'left',
|
|
333
|
+
title: { display: true, text: 'Assertion Check Quantities' },
|
|
334
|
+
grid: { drawOnChartArea: true }
|
|
335
|
+
},
|
|
336
|
+
y1: {
|
|
337
|
+
type: 'linear',
|
|
338
|
+
position: 'right',
|
|
339
|
+
title: { display: true, text: 'Latency Speed (ms)' },
|
|
340
|
+
grid: { drawOnChartArea: false },
|
|
341
|
+
ticks: { callback: (value) => `${value}ms` }
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
onClick: function(event, activeElements, chart) {
|
|
345
|
+
const points = chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true);
|
|
346
|
+
if (points.length > 0) {
|
|
347
|
+
const dataIndex = points[0].index;
|
|
348
|
+
const selectedRunId = data.map(r => r.runId)[dataIndex];
|
|
349
|
+
if (selectedRunId) {
|
|
350
|
+
showDetailedRunBreakdown(selectedRunId);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const latencyCanvas = document.getElementById('latencyChart');
|
|
358
|
+
if (!latencyCanvas) return;
|
|
359
|
+
|
|
360
|
+
const latest = data[data.length - 1];
|
|
361
|
+
new Chart(latencyCanvas, {
|
|
362
|
+
type: 'doughnut',
|
|
363
|
+
data: {
|
|
364
|
+
labels: ['Passed Assertions', 'Failed Assertions'],
|
|
365
|
+
datasets: [{
|
|
366
|
+
data: [latest.passedAssertions, latest.failedAssertions],
|
|
367
|
+
backgroundColor: ['#198754', '#dc3545'],
|
|
368
|
+
borderWidth: 2,
|
|
369
|
+
hoverOffset: 4
|
|
370
|
+
}]
|
|
371
|
+
},
|
|
372
|
+
options: {
|
|
373
|
+
plugins: {
|
|
374
|
+
legend: { position: 'bottom' },
|
|
375
|
+
title: { display: true, text: `Latest Run State (${latest.runId})` }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
window.onload = initMetricsEngine;
|
|
382
|
+
</script>
|
|
383
|
+
</body>
|
|
384
|
+
</html>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import newman from 'newman';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
const HISTORY_FILE = path.join(__dirname, '.newman-history.json');
|
|
11
|
+
const SERVER_PORT = 8086;
|
|
12
|
+
function fetchLocalLedger() {
|
|
13
|
+
if (!fs.existsSync(HISTORY_FILE)) {
|
|
14
|
+
fs.writeFileSync(HISTORY_FILE, JSON.stringify([]));
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function commitRunToHistory(metric) {
|
|
24
|
+
const history = fetchLocalLedger();
|
|
25
|
+
history.push(metric);
|
|
26
|
+
if (history.length > 50)
|
|
27
|
+
history.shift();
|
|
28
|
+
fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2));
|
|
29
|
+
console.log(`\x1b[32m✓ [LEDGER SUCCESS]\x1b[0m Appended run ${metric.runId} successfully.`);
|
|
30
|
+
}
|
|
31
|
+
function executePipeline() {
|
|
32
|
+
const collectionArg = process.argv[2];
|
|
33
|
+
if (!collectionArg) {
|
|
34
|
+
console.error("\x1b[31m❌ Usage Error:\x1b[0m Please specify your Postman collection file.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const resolutionPath = path.resolve(collectionArg);
|
|
38
|
+
console.log(`\x1b[36m[ORCHESTRATOR]\x1b[0m Booting runtime stream for: [${path.basename(resolutionPath)}]`);
|
|
39
|
+
let totalLatency = 0;
|
|
40
|
+
let totalRequests = 0;
|
|
41
|
+
let passedAssertions = 0;
|
|
42
|
+
let failedAssertions = 0;
|
|
43
|
+
const detailedRequests = [];
|
|
44
|
+
// Tracks the active request execution block reference context
|
|
45
|
+
let currentRequestRef = null;
|
|
46
|
+
let collectionData;
|
|
47
|
+
try {
|
|
48
|
+
collectionData = JSON.parse(fs.readFileSync(resolutionPath, 'utf-8'));
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
console.error(`\x1b[31m[ERROR]\x1b[0m Failed to parse collection file: ${e.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
newman.run({
|
|
55
|
+
collection: collectionData,
|
|
56
|
+
reporters: 'cli'
|
|
57
|
+
})
|
|
58
|
+
.on('request', (err, args) => {
|
|
59
|
+
if (err || !args.response)
|
|
60
|
+
return;
|
|
61
|
+
totalRequests++;
|
|
62
|
+
totalLatency += args.response.responseTime;
|
|
63
|
+
// Create the active request record block during execution runtime
|
|
64
|
+
currentRequestRef = {
|
|
65
|
+
id: args.cursor.ref, // Unique internal step identifier
|
|
66
|
+
name: args.item?.name || 'Unnamed Request',
|
|
67
|
+
method: args.item?.request?.method || 'GET',
|
|
68
|
+
url: args.item?.request?.url?.toString() || '',
|
|
69
|
+
statusCode: args.response.code || 0,
|
|
70
|
+
responseTime: args.response.responseTime || 0,
|
|
71
|
+
assertions: []
|
|
72
|
+
};
|
|
73
|
+
detailedRequests.push(currentRequestRef);
|
|
74
|
+
})
|
|
75
|
+
.on('assertion', (err, args) => {
|
|
76
|
+
// Safe global status monitoring tracking counters
|
|
77
|
+
if (err)
|
|
78
|
+
failedAssertions++;
|
|
79
|
+
else
|
|
80
|
+
passedAssertions++;
|
|
81
|
+
// Map assertions actively straight to the current request reference payload array
|
|
82
|
+
if (currentRequestRef && currentRequestRef.id === args.cursor.ref) {
|
|
83
|
+
currentRequestRef.assertions.push({
|
|
84
|
+
name: args.assertion || 'Unnamed Checkpoint',
|
|
85
|
+
passed: !err,
|
|
86
|
+
errorMessage: err ? err.message : undefined
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.on('done', (err, summary) => {
|
|
91
|
+
if (err || !summary) {
|
|
92
|
+
console.error("\x1b[31m[CRITICAL]\x1b[0m Collection run crashed.", err);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const calculatedAvg = totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0;
|
|
96
|
+
let healthRating = 'Success';
|
|
97
|
+
if (failedAssertions > 0)
|
|
98
|
+
healthRating = 'Failure';
|
|
99
|
+
else if (calculatedAvg > 1000)
|
|
100
|
+
healthRating = 'Warning';
|
|
101
|
+
const metricsPayload = {
|
|
102
|
+
runId: `RUN-${Math.floor(100000 + Math.random() * 900000)}`,
|
|
103
|
+
collectionName: summary.collection.name || 'Postman Collection',
|
|
104
|
+
timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19),
|
|
105
|
+
totalRequests,
|
|
106
|
+
passedAssertions,
|
|
107
|
+
failedAssertions,
|
|
108
|
+
avgResponseTime: calculatedAvg,
|
|
109
|
+
status: healthRating,
|
|
110
|
+
requests: detailedRequests
|
|
111
|
+
};
|
|
112
|
+
commitRunToHistory(metricsPayload);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
spinUpDashboardPortal();
|
|
115
|
+
}, 300);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function spinUpDashboardPortal() {
|
|
119
|
+
const app = express();
|
|
120
|
+
app.get('/api/history', (req, res) => {
|
|
121
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
|
122
|
+
res.json(fetchLocalLedger());
|
|
123
|
+
});
|
|
124
|
+
app.get('/', (req, res) => {
|
|
125
|
+
res.sendFile(path.join(__dirname, 'dashboard.html'));
|
|
126
|
+
});
|
|
127
|
+
app.listen(SERVER_PORT, async () => {
|
|
128
|
+
console.log(`\x1b[32m🚀 [PORTAL] Dashboard metrics serving at http://localhost:${SERVER_PORT}\x1b[0m`);
|
|
129
|
+
await open(`http://localhost:${SERVER_PORT}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
executePipeline();
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "newman-reporter-dashboard-wrapper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Historical trend reporting dashboard for Postman/Newman executions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"newman-dashboard": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && copyfiles -u 1 src/dashboard.html dist/",
|
|
15
|
+
"prepare": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"express": "^4.19.2",
|
|
19
|
+
"newman": "^6.1.3",
|
|
20
|
+
"open": "^10.1.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/express": "^4.17.21",
|
|
24
|
+
"@types/newman": "*",
|
|
25
|
+
"@types/node": "^20.11.0",
|
|
26
|
+
"copyfiles": "^2.4.1",
|
|
27
|
+
"typescript": "^5.3.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Newman Dashboard Orchestrator 🚀
|
|
2
|
+
|
|
3
|
+
An interactive, real-time HTML5 analytics reporter dashboard for Postman collections executed via Newman. Includes a built-in, completely free, client-side **AI QA Advisor Insights** engine to instantly diagnose assertion and performance bottlenecks locally with zero external API dependencies.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚡ Quick Start Guide
|
|
8
|
+
|
|
9
|
+
Get up and running with your interactive dashboard wrapper in under 2 minutes.
|
|
10
|
+
|
|
11
|
+
### 1. Prerequisites
|
|
12
|
+
Ensure you have [Node.js](https://nodejs.org/) (v18 or higher) installed on your machine. You will also need a Postman collection exported as a `.json` file.
|
|
13
|
+
|
|
14
|
+
### 2. Global Installation
|
|
15
|
+
Open your terminal and install the orchestrator utility globally:
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g newman-reporter-dashboard-orchestrator
|
|
18
|
+
|
|
19
|
+
How to run the newman and invoke dashboard
|
|
20
|
+
newman-dashboard my_postman_collection.json
|