fast-api-tester 1.0.3 ā 1.0.5
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/apiEngine.d.ts +31 -20
- package/dist/apiEngine.d.ts.map +1 -1
- package/dist/apiEngine.js +257 -36
- package/dist/apiEngine.js.map +1 -1
- package/package.json +1 -1
package/dist/apiEngine.d.ts
CHANGED
|
@@ -11,47 +11,58 @@ export interface ApiResponseData<T = unknown> {
|
|
|
11
11
|
body: T;
|
|
12
12
|
latencyMs: number;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export interface TestExecutionRecord {
|
|
15
|
+
method: HttpMethod;
|
|
16
|
+
endpoint: string;
|
|
17
|
+
statusCode: number;
|
|
18
|
+
latencyMs: number;
|
|
19
|
+
slaLimitMs?: number;
|
|
20
|
+
passed: boolean;
|
|
21
|
+
errorMsg?: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RunSummaryHistory {
|
|
25
|
+
runId: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
total: number;
|
|
28
|
+
passed: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
avgLatencyMs: number;
|
|
31
|
+
}
|
|
17
32
|
export declare class ApiResponse<T = unknown> implements ApiResponseData<T> {
|
|
18
33
|
statusCode: number;
|
|
19
34
|
headers: Record<string, string | string[] | undefined>;
|
|
20
35
|
body: T;
|
|
21
36
|
latencyMs: number;
|
|
37
|
+
lastExecutionRecord?: TestExecutionRecord;
|
|
22
38
|
constructor(data: ApiResponseData<T>);
|
|
23
|
-
/**
|
|
24
|
-
* Asserts that the response latency was strictly under the specified SLA limit (in ms).
|
|
25
|
-
* @throws Error if latency meets or exceeds the target SLA.
|
|
26
|
-
*/
|
|
27
39
|
expectLatencyUnder(maxLatencyMs: number): this;
|
|
28
40
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Fluent API Test Client
|
|
31
|
-
*/
|
|
32
41
|
export declare class ApiEngine {
|
|
33
42
|
private baseUrl;
|
|
34
43
|
private defaultHeaders;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
private records;
|
|
45
|
+
private reportOutputPath;
|
|
46
|
+
private historyFilePath;
|
|
47
|
+
private autoReportEnabled;
|
|
38
48
|
setBaseUrl(url: string): this;
|
|
39
|
-
/**
|
|
40
|
-
* Add default headers (e.g., Bearer tokens) across all requests
|
|
41
|
-
*/
|
|
42
49
|
setHeader(key: string, value: string): this;
|
|
50
|
+
configureReport(options: {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
outputPath?: string;
|
|
53
|
+
historyPath?: string;
|
|
54
|
+
}): this;
|
|
43
55
|
get<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
44
56
|
post<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
45
57
|
put<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
46
58
|
patch<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
47
59
|
delete<T = unknown>(path: string, options?: Omit<RequestOptions, 'body'>): Promise<ApiResponse<T>>;
|
|
48
|
-
/**
|
|
49
|
-
* Core request runner using low-level undici.Pool
|
|
50
|
-
*/
|
|
51
60
|
request<T = unknown>(method: HttpMethod, path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
52
61
|
/**
|
|
53
|
-
*
|
|
62
|
+
* Persists current run to history file and returns all past execution history
|
|
54
63
|
*/
|
|
64
|
+
private updateAndGetHistory;
|
|
65
|
+
private generateHtmlReport;
|
|
55
66
|
destroy(): Promise<void>;
|
|
56
67
|
}
|
|
57
68
|
//# sourceMappingURL=apiEngine.d.ts.map
|
package/dist/apiEngine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiEngine.d.ts","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apiEngine.d.ts","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"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"}
|
package/dist/apiEngine.js
CHANGED
|
@@ -1,69 +1,73 @@
|
|
|
1
1
|
import { Pool } from 'undici';
|
|
2
2
|
import { URL } from 'node:url';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
6
5
|
export class ApiResponse {
|
|
7
6
|
statusCode;
|
|
8
7
|
headers;
|
|
9
8
|
body;
|
|
10
9
|
latencyMs;
|
|
10
|
+
lastExecutionRecord;
|
|
11
11
|
constructor(data) {
|
|
12
12
|
this.statusCode = data.statusCode;
|
|
13
13
|
this.headers = data.headers;
|
|
14
14
|
this.body = data.body;
|
|
15
15
|
this.latencyMs = data.latencyMs;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Asserts that the response latency was strictly under the specified SLA limit (in ms).
|
|
19
|
-
* @throws Error if latency meets or exceeds the target SLA.
|
|
20
|
-
*/
|
|
21
17
|
expectLatencyUnder(maxLatencyMs) {
|
|
18
|
+
if (this.lastExecutionRecord) {
|
|
19
|
+
this.lastExecutionRecord.slaLimitMs = maxLatencyMs;
|
|
20
|
+
}
|
|
22
21
|
if (this.latencyMs >= maxLatencyMs) {
|
|
23
|
-
|
|
22
|
+
const errorMsg = `[SLA Violation] Expected response latency to be under ${maxLatencyMs}ms, but took ${this.latencyMs.toFixed(2)}ms.`;
|
|
23
|
+
if (this.lastExecutionRecord) {
|
|
24
|
+
this.lastExecutionRecord.passed = false;
|
|
25
|
+
this.lastExecutionRecord.errorMsg = errorMsg;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(errorMsg);
|
|
24
28
|
}
|
|
25
29
|
return this;
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Global connection pool map to keep sockets alive across tests
|
|
30
|
-
*/
|
|
31
32
|
const poolRegistry = new Map();
|
|
32
33
|
function getOrCreatePool(origin) {
|
|
33
34
|
let pool = poolRegistry.get(origin);
|
|
34
35
|
if (!pool) {
|
|
35
36
|
pool = new Pool(origin, {
|
|
36
|
-
connections: 50,
|
|
37
|
-
keepAliveTimeout: 60_000,
|
|
37
|
+
connections: 50,
|
|
38
|
+
keepAliveTimeout: 60_000,
|
|
38
39
|
pipelining: 1,
|
|
39
40
|
});
|
|
40
41
|
poolRegistry.set(origin, pool);
|
|
41
42
|
}
|
|
42
43
|
return pool;
|
|
43
44
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Fluent API Test Client
|
|
46
|
-
*/
|
|
47
45
|
export class ApiEngine {
|
|
48
46
|
baseUrl = '';
|
|
49
47
|
defaultHeaders = {
|
|
50
48
|
'accept': 'application/json',
|
|
51
49
|
};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
records = [];
|
|
51
|
+
reportOutputPath = 'test-report.html';
|
|
52
|
+
historyFilePath = '.test-history.json';
|
|
53
|
+
autoReportEnabled = true;
|
|
55
54
|
setBaseUrl(url) {
|
|
56
55
|
this.baseUrl = url.endsWith('/') ? url.slice(0, -1) : url;
|
|
57
56
|
return this;
|
|
58
57
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Add default headers (e.g., Bearer tokens) across all requests
|
|
61
|
-
*/
|
|
62
58
|
setHeader(key, value) {
|
|
63
59
|
this.defaultHeaders[key.toLowerCase()] = value;
|
|
64
60
|
return this;
|
|
65
61
|
}
|
|
66
|
-
|
|
62
|
+
configureReport(options) {
|
|
63
|
+
if (options.enabled !== undefined)
|
|
64
|
+
this.autoReportEnabled = options.enabled;
|
|
65
|
+
if (options.outputPath)
|
|
66
|
+
this.reportOutputPath = options.outputPath;
|
|
67
|
+
if (options.historyPath)
|
|
68
|
+
this.historyFilePath = options.historyPath;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
67
71
|
get(path, options) {
|
|
68
72
|
return this.request('GET', path, options);
|
|
69
73
|
}
|
|
@@ -79,24 +83,18 @@ export class ApiEngine {
|
|
|
79
83
|
delete(path, options) {
|
|
80
84
|
return this.request('DELETE', path, options);
|
|
81
85
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Core request runner using low-level undici.Pool
|
|
84
|
-
*/
|
|
85
86
|
async request(method, path, options = {}) {
|
|
86
87
|
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
87
88
|
const targetUrl = new URL(`${this.baseUrl}${fullPath}`);
|
|
88
|
-
// Append query parameters
|
|
89
89
|
if (options.query) {
|
|
90
90
|
Object.entries(options.query).forEach(([key, val]) => {
|
|
91
91
|
targetUrl.searchParams.append(key, String(val));
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
// Merge headers
|
|
95
94
|
const headers = {
|
|
96
95
|
...this.defaultHeaders,
|
|
97
96
|
...(options.headers || {}),
|
|
98
97
|
};
|
|
99
|
-
// Serialize body if present
|
|
100
98
|
let requestBody = undefined;
|
|
101
99
|
if (options.body !== undefined) {
|
|
102
100
|
if (typeof options.body === 'object') {
|
|
@@ -111,7 +109,6 @@ export class ApiEngine {
|
|
|
111
109
|
}
|
|
112
110
|
const pool = getOrCreatePool(targetUrl.origin);
|
|
113
111
|
const startTime = process.hrtime.bigint();
|
|
114
|
-
// High-performance direct pool dispatch
|
|
115
112
|
const response = await pool.request({
|
|
116
113
|
path: `${targetUrl.pathname}${targetUrl.search}`,
|
|
117
114
|
method: method,
|
|
@@ -120,10 +117,8 @@ export class ApiEngine {
|
|
|
120
117
|
headersTimeout: options.timeoutMs ?? 10_000,
|
|
121
118
|
bodyTimeout: options.timeoutMs ?? 10_000,
|
|
122
119
|
});
|
|
123
|
-
// Calculate latency in milliseconds
|
|
124
120
|
const endTime = process.hrtime.bigint();
|
|
125
121
|
const latencyMs = Number(endTime - startTime) / 1_000_000;
|
|
126
|
-
// Parse response body
|
|
127
122
|
const rawText = await response.body.text();
|
|
128
123
|
let parsedBody;
|
|
129
124
|
try {
|
|
@@ -132,18 +127,244 @@ export class ApiEngine {
|
|
|
132
127
|
catch {
|
|
133
128
|
parsedBody = rawText;
|
|
134
129
|
}
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
const formattedLatency = Number(latencyMs.toFixed(2));
|
|
131
|
+
const passed = response.statusCode >= 200 && response.statusCode < 400;
|
|
132
|
+
const record = {
|
|
133
|
+
method,
|
|
134
|
+
endpoint: `${targetUrl.pathname}${targetUrl.search}`,
|
|
135
|
+
statusCode: response.statusCode,
|
|
136
|
+
latencyMs: formattedLatency,
|
|
137
|
+
passed,
|
|
138
|
+
timestamp: new Date().toLocaleTimeString(),
|
|
139
|
+
};
|
|
140
|
+
this.records.push(record);
|
|
141
|
+
const apiResponse = new ApiResponse({
|
|
137
142
|
statusCode: response.statusCode,
|
|
138
143
|
headers: response.headers,
|
|
139
144
|
body: parsedBody,
|
|
140
|
-
latencyMs:
|
|
145
|
+
latencyMs: formattedLatency,
|
|
141
146
|
});
|
|
147
|
+
apiResponse.lastExecutionRecord = record;
|
|
148
|
+
return apiResponse;
|
|
142
149
|
}
|
|
143
150
|
/**
|
|
144
|
-
*
|
|
151
|
+
* Persists current run to history file and returns all past execution history
|
|
145
152
|
*/
|
|
153
|
+
updateAndGetHistory(currentSummary) {
|
|
154
|
+
const historyFile = path.resolve(this.historyFilePath);
|
|
155
|
+
let history = [];
|
|
156
|
+
if (fs.existsSync(historyFile)) {
|
|
157
|
+
try {
|
|
158
|
+
const content = fs.readFileSync(historyFile, 'utf-8');
|
|
159
|
+
history = JSON.parse(content);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
history = [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Add current run and keep last 10 executions
|
|
166
|
+
history.push(currentSummary);
|
|
167
|
+
if (history.length > 10) {
|
|
168
|
+
history = history.slice(-10);
|
|
169
|
+
}
|
|
170
|
+
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2), 'utf-8');
|
|
171
|
+
return history;
|
|
172
|
+
}
|
|
173
|
+
generateHtmlReport() {
|
|
174
|
+
if (this.records.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
const total = this.records.length;
|
|
177
|
+
const passed = this.records.filter((r) => r.passed).length;
|
|
178
|
+
const failed = total - passed;
|
|
179
|
+
const avgLatency = Number((this.records.reduce((acc, r) => acc + r.latencyMs, 0) / total).toFixed(1));
|
|
180
|
+
const runTimestamp = new Date().toLocaleString();
|
|
181
|
+
const currentRunSummary = {
|
|
182
|
+
runId: `#${Date.now().toString().slice(-4)}`,
|
|
183
|
+
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
|
184
|
+
total,
|
|
185
|
+
passed,
|
|
186
|
+
failed,
|
|
187
|
+
avgLatencyMs: avgLatency,
|
|
188
|
+
};
|
|
189
|
+
const historyData = this.updateAndGetHistory(currentRunSummary);
|
|
190
|
+
const tableRows = this.records
|
|
191
|
+
.map((r) => `
|
|
192
|
+
<tr>
|
|
193
|
+
<td><span class="badge badge-${r.method.toLowerCase()}">${r.method}</span></td>
|
|
194
|
+
<td><code>${r.endpoint}</code></td>
|
|
195
|
+
<td><strong>${r.statusCode}</strong></td>
|
|
196
|
+
<td>${r.latencyMs} ms</td>
|
|
197
|
+
<td>${r.slaLimitMs ? `< ${r.slaLimitMs} ms` : 'N/A'}</td>
|
|
198
|
+
<td>
|
|
199
|
+
<span class="status-tag ${r.passed ? 'status-pass' : 'status-fail'}">
|
|
200
|
+
${r.passed ? 'PASSED' : 'FAILED'}
|
|
201
|
+
</span>
|
|
202
|
+
${r.errorMsg ? `<div class="error-msg">${r.errorMsg}</div>` : ''}
|
|
203
|
+
</td>
|
|
204
|
+
</tr>`)
|
|
205
|
+
.join('');
|
|
206
|
+
const historyRows = historyData
|
|
207
|
+
.map((h) => `
|
|
208
|
+
<tr>
|
|
209
|
+
<td><strong>${h.runId}</strong></td>
|
|
210
|
+
<td>${h.timestamp}</td>
|
|
211
|
+
<td>${h.total}</td>
|
|
212
|
+
<td style="color: #16a34a; font-weight: bold;">${h.passed}</td>
|
|
213
|
+
<td style="color: #dc2626; font-weight: bold;">${h.failed}</td>
|
|
214
|
+
<td>${h.avgLatencyMs} ms</td>
|
|
215
|
+
</tr>`)
|
|
216
|
+
.join('');
|
|
217
|
+
const html = `<!DOCTYPE html>
|
|
218
|
+
<html lang="en">
|
|
219
|
+
<head>
|
|
220
|
+
<meta charset="UTF-8">
|
|
221
|
+
<title>fast-api-tester Analytics & Execution Summary</title>
|
|
222
|
+
<!-- CDN load Chart.js -->
|
|
223
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
224
|
+
<style>
|
|
225
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
226
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f8fafc; color: #1e293b; padding: 30px; margin: 0; }
|
|
227
|
+
.header { background: #0f172a; color: #fff; padding: 24px 28px; border-radius: 8px; margin-bottom: 24px; }
|
|
228
|
+
.header h1 { margin: 0 0 6px 0; font-size: 20pt; color: #38bdf8; }
|
|
229
|
+
.header p { margin: 0; color: #94a3b8; font-size: 10pt; }
|
|
230
|
+
|
|
231
|
+
.summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
|
|
232
|
+
.card { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 18px; }
|
|
233
|
+
.card-label { font-size: 8pt; text-transform: uppercase; color: #64748b; font-weight: 600; margin-bottom: 4px; }
|
|
234
|
+
.card-value { font-size: 20pt; font-weight: 700; color: #0f172a; }
|
|
235
|
+
.card-value.pass { color: #16a34a; } .card-value.fail { color: #dc2626; } .card-value.sla { color: #2563eb; }
|
|
236
|
+
|
|
237
|
+
.charts-container { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 24px; }
|
|
238
|
+
.chart-box { background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; height: 320px; }
|
|
239
|
+
.chart-box h3 { margin-top: 0; font-size: 11pt; color: #475569; margin-bottom: 15px; }
|
|
240
|
+
|
|
241
|
+
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; border: 1px solid #e2e8f0; overflow: hidden; margin-bottom: 24px; }
|
|
242
|
+
th { background: #f1f5f9; color: #475569; text-align: left; padding: 12px 14px; font-size: 9pt; text-transform: uppercase; border-bottom: 1px solid #e2e8f0; }
|
|
243
|
+
td { padding: 12px 14px; border-bottom: 1px solid #f1f5f9; font-size: 9.5pt; vertical-align: top; }
|
|
244
|
+
|
|
245
|
+
.badge { padding: 3px 8px; border-radius: 4px; font-size: 8pt; font-weight: 700; text-transform: uppercase; }
|
|
246
|
+
.badge-get { background: #e0f2fe; color: #0369a1; }
|
|
247
|
+
.badge-post { background: #dcfce7; color: #15803d; }
|
|
248
|
+
.badge-put { background: #fef3c7; color: #b45309; }
|
|
249
|
+
.badge-patch { background: #f3e8ff; color: #6b21a8; }
|
|
250
|
+
.badge-delete { background: #fee2e2; color: #b91c1c; }
|
|
251
|
+
|
|
252
|
+
.status-tag { font-weight: 700; padding: 3px 8px; border-radius: 4px; font-size: 8pt; display: inline-block; }
|
|
253
|
+
.status-pass { background: #dcfce7; color: #166534; }
|
|
254
|
+
.status-fail { background: #fee2e2; color: #991b1b; }
|
|
255
|
+
.error-msg { margin-top: 6px; font-family: monospace; font-size: 8.5pt; color: #dc2626; background: #fef2f2; padding: 6px 8px; border-radius: 4px; }
|
|
256
|
+
|
|
257
|
+
.section-title { font-size: 13pt; color: #0f172a; margin-bottom: 12px; }
|
|
258
|
+
</style>
|
|
259
|
+
</head>
|
|
260
|
+
<body>
|
|
261
|
+
<div class="header">
|
|
262
|
+
<h1>ā” fast-api-tester Analytics & Execution Summary</h1>
|
|
263
|
+
<p>Generated on ${runTimestamp}</p>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div class="summary-grid">
|
|
267
|
+
<div class="card"><div class="card-label">Total Requests</div><div class="card-value">${total}</div></div>
|
|
268
|
+
<div class="card"><div class="card-label">Passed</div><div class="card-value pass">${passed}</div></div>
|
|
269
|
+
<div class="card"><div class="card-label">Failed</div><div class="card-value fail">${failed}</div></div>
|
|
270
|
+
<div class="card"><div class="card-label">Avg Latency</div><div class="card-value sla">${avgLatency} ms</div></div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<!-- Visual Analytics Section -->
|
|
274
|
+
<div class="charts-container">
|
|
275
|
+
<div class="chart-box">
|
|
276
|
+
<h3>Current Run Status (Pie Chart)</h3>
|
|
277
|
+
<div style="height: 230px; position: relative;">
|
|
278
|
+
<canvas id="pieChart"></canvas>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="chart-box">
|
|
282
|
+
<h3>Historical Execution Trend (Pass vs Fail)</h3>
|
|
283
|
+
<div style="height: 230px; position: relative;">
|
|
284
|
+
<canvas id="barChart"></canvas>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<h2 class="section-title">Current Test Suite Results</h2>
|
|
290
|
+
<table>
|
|
291
|
+
<thead>
|
|
292
|
+
<tr><th>Method</th><th>Endpoint</th><th>Status</th><th>Latency</th><th>SLA Target</th><th>Result</th></tr>
|
|
293
|
+
</thead>
|
|
294
|
+
<tbody>${tableRows}</tbody>
|
|
295
|
+
</table>
|
|
296
|
+
|
|
297
|
+
<h2 class="section-title">Historical Executions (Last 10 Runs)</h2>
|
|
298
|
+
<table>
|
|
299
|
+
<thead>
|
|
300
|
+
<tr><th>Run ID</th><th>Time</th><th>Total Tests</th><th>Passed</th><th>Failed</th><th>Avg Latency</th></tr>
|
|
301
|
+
</thead>
|
|
302
|
+
<tbody>${historyRows}</tbody>
|
|
303
|
+
</table>
|
|
304
|
+
|
|
305
|
+
<script>
|
|
306
|
+
// 1. Chart.js Pie Chart for Current Run
|
|
307
|
+
new Chart(document.getElementById('pieChart'), {
|
|
308
|
+
type: 'pie',
|
|
309
|
+
data: {
|
|
310
|
+
labels: ['Passed', 'Failed'],
|
|
311
|
+
datasets: [{
|
|
312
|
+
data: [${passed}, ${failed}],
|
|
313
|
+
backgroundColor: ['#22c55e', '#ef4444'],
|
|
314
|
+
borderWidth: 1
|
|
315
|
+
}]
|
|
316
|
+
},
|
|
317
|
+
options: {
|
|
318
|
+
responsive: true,
|
|
319
|
+
maintainAspectRatio: false,
|
|
320
|
+
plugins: {
|
|
321
|
+
legend: { position: 'bottom' }
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// 2. Chart.js Bar Chart for Historical Trends
|
|
327
|
+
const historyData = ${JSON.stringify(historyData)};
|
|
328
|
+
new Chart(document.getElementById('barChart'), {
|
|
329
|
+
type: 'bar',
|
|
330
|
+
data: {
|
|
331
|
+
labels: historyData.map(h => h.runId + ' (' + h.timestamp + ')'),
|
|
332
|
+
datasets: [
|
|
333
|
+
{
|
|
334
|
+
label: 'Passed',
|
|
335
|
+
data: historyData.map(h => h.passed),
|
|
336
|
+
backgroundColor: '#22c55e',
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
label: 'Failed',
|
|
340
|
+
data: historyData.map(h => h.failed),
|
|
341
|
+
backgroundColor: '#ef4444',
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
options: {
|
|
346
|
+
responsive: true,
|
|
347
|
+
maintainAspectRatio: false,
|
|
348
|
+
scales: {
|
|
349
|
+
x: { stacked: true },
|
|
350
|
+
y: { stacked: true, beginAtZero: true }
|
|
351
|
+
},
|
|
352
|
+
plugins: {
|
|
353
|
+
legend: { position: 'bottom' }
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
</script>
|
|
358
|
+
</body>
|
|
359
|
+
</html>`;
|
|
360
|
+
const fullPath = path.resolve(this.reportOutputPath);
|
|
361
|
+
fs.writeFileSync(fullPath, html, 'utf-8');
|
|
362
|
+
console.log(`\nš Visual report generated: ${fullPath}`);
|
|
363
|
+
}
|
|
146
364
|
async destroy() {
|
|
365
|
+
if (this.autoReportEnabled) {
|
|
366
|
+
this.generateHtmlReport();
|
|
367
|
+
}
|
|
147
368
|
const closes = Array.from(poolRegistry.values()).map((p) => p.close());
|
|
148
369
|
await Promise.all(closes);
|
|
149
370
|
poolRegistry.clear();
|
package/dist/apiEngine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiEngine.js","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"apiEngine.js","sourceRoot":"","sources":["../src/apiEngine.ts"],"names":[],"mappings":"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"}
|