@testsmith/perfornium 0.6.4 → 0.6.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/cli/commands/distributed.js +2 -2
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/run.js +2 -0
- package/dist/config/parser.js +2 -2
- package/dist/config/types/global-config.d.ts +82 -2
- package/dist/config/types/scenario-config.d.ts +2 -2
- package/dist/core/data/data-manager.d.ts +70 -0
- package/dist/core/data/data-manager.js +186 -0
- package/dist/core/data/data-provider.d.ts +85 -0
- package/dist/core/data/data-provider.js +468 -0
- package/dist/core/data/index.d.ts +8 -0
- package/dist/core/data/index.js +13 -0
- package/dist/core/execution/check-evaluator.d.ts +10 -0
- package/dist/core/execution/check-evaluator.js +79 -0
- package/dist/core/execution/data-extractor.d.ts +6 -0
- package/dist/core/execution/data-extractor.js +70 -0
- package/dist/core/execution/index.d.ts +3 -0
- package/dist/core/execution/index.js +9 -0
- package/dist/core/execution/json-payload-processor.d.ts +7 -0
- package/dist/core/execution/json-payload-processor.js +140 -0
- package/dist/core/factories/index.d.ts +2 -0
- package/dist/core/factories/index.js +7 -0
- package/dist/core/factories/output-handler-factory.d.ts +10 -0
- package/dist/core/factories/output-handler-factory.js +91 -0
- package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
- package/dist/core/factories/protocol-handler-factory.js +96 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +8 -3
- package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
- package/dist/core/reporting/dashboard-reporter.js +127 -0
- package/dist/core/reporting/index.d.ts +1 -0
- package/dist/core/reporting/index.js +5 -0
- package/dist/core/step-executor.d.ts +6 -20
- package/dist/core/step-executor.js +72 -366
- package/dist/core/strategies/index.d.ts +2 -0
- package/dist/core/strategies/index.js +7 -0
- package/dist/core/strategies/scenario-selector.d.ts +13 -0
- package/dist/core/strategies/scenario-selector.js +37 -0
- package/dist/core/strategies/think-time-strategy.d.ts +15 -0
- package/dist/core/strategies/think-time-strategy.js +71 -0
- package/dist/core/test-runner.d.ts +4 -11
- package/dist/core/test-runner.js +105 -312
- package/dist/core/virtual-user.d.ts +7 -37
- package/dist/core/virtual-user.js +29 -269
- package/dist/dashboard/routes/api.d.ts +64 -0
- package/dist/dashboard/routes/api.js +569 -0
- package/dist/dashboard/routes/index.d.ts +2 -0
- package/dist/dashboard/routes/index.js +7 -0
- package/dist/dashboard/routes/static.d.ts +6 -0
- package/dist/dashboard/routes/static.js +76 -0
- package/dist/dashboard/server.d.ts +8 -84
- package/dist/dashboard/server.js +76 -2007
- package/dist/dashboard/services/file-scanner.d.ts +7 -0
- package/dist/dashboard/services/file-scanner.js +114 -0
- package/dist/dashboard/services/index.d.ts +5 -0
- package/dist/dashboard/services/index.js +13 -0
- package/dist/dashboard/services/influxdb-service.d.ts +41 -0
- package/dist/dashboard/services/influxdb-service.js +329 -0
- package/dist/dashboard/services/metrics-parser.d.ts +12 -0
- package/dist/dashboard/services/metrics-parser.js +209 -0
- package/dist/dashboard/services/results-manager.d.ts +17 -0
- package/dist/dashboard/services/results-manager.js +311 -0
- package/dist/dashboard/services/test-executor.d.ts +41 -0
- package/dist/dashboard/services/test-executor.js +250 -0
- package/dist/dashboard/services/workers-manager.d.ts +13 -0
- package/dist/dashboard/services/workers-manager.js +81 -0
- package/dist/dashboard/templates/index.html +122 -0
- package/dist/dashboard/templates/scripts/main.js +3280 -0
- package/dist/dashboard/templates/styles.css +402 -0
- package/dist/dashboard/types.d.ts +168 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/distributed/result-aggregator.js +1 -3
- package/dist/metrics/batch/batch-processor.d.ts +27 -0
- package/dist/metrics/batch/batch-processor.js +85 -0
- package/dist/metrics/batch/index.d.ts +1 -0
- package/dist/metrics/batch/index.js +5 -0
- package/dist/metrics/collector.d.ts +46 -45
- package/dist/metrics/collector.js +179 -640
- package/dist/metrics/core/error-tracker.d.ts +9 -0
- package/dist/metrics/core/error-tracker.js +52 -0
- package/dist/metrics/core/index.d.ts +3 -0
- package/dist/metrics/core/index.js +9 -0
- package/dist/metrics/core/result-storage.d.ts +19 -0
- package/dist/metrics/core/result-storage.js +56 -0
- package/dist/metrics/core/statistics-engine.d.ts +27 -0
- package/dist/metrics/core/statistics-engine.js +91 -0
- package/dist/metrics/output/file-writer.d.ts +19 -0
- package/dist/metrics/output/file-writer.js +129 -0
- package/dist/metrics/output/index.d.ts +2 -0
- package/dist/metrics/output/index.js +10 -0
- package/dist/metrics/output/influxdb-writer.d.ts +89 -0
- package/dist/metrics/output/influxdb-writer.js +404 -0
- package/dist/metrics/realtime/dispatcher.d.ts +18 -0
- package/dist/metrics/realtime/dispatcher.js +45 -0
- package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/graphite.js +61 -0
- package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
- package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/webhook.js +22 -0
- package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/websocket.js +25 -0
- package/dist/metrics/realtime/index.d.ts +5 -0
- package/dist/metrics/realtime/index.js +13 -0
- package/dist/metrics/reporting/index.d.ts +3 -0
- package/dist/metrics/reporting/index.js +9 -0
- package/dist/metrics/reporting/step-statistics.d.ts +6 -0
- package/dist/metrics/reporting/step-statistics.js +59 -0
- package/dist/metrics/reporting/summary-generator.d.ts +16 -0
- package/dist/metrics/reporting/summary-generator.js +46 -0
- package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
- package/dist/metrics/reporting/timeline-calculator.js +86 -0
- package/dist/metrics/types.d.ts +58 -0
- package/dist/outputs/csv.d.ts +2 -0
- package/dist/outputs/csv.js +21 -2
- package/dist/outputs/json.js +6 -2
- package/dist/protocols/rest/handler.d.ts +4 -53
- package/dist/protocols/rest/handler.js +73 -454
- package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
- package/dist/protocols/rest/request/auth-handler.js +30 -0
- package/dist/protocols/rest/request/body-processor.d.ts +11 -0
- package/dist/protocols/rest/request/body-processor.js +62 -0
- package/dist/protocols/rest/request/index.d.ts +2 -0
- package/dist/protocols/rest/request/index.js +7 -0
- package/dist/protocols/rest/response/checks.d.ts +6 -0
- package/dist/protocols/rest/response/checks.js +71 -0
- package/dist/protocols/rest/response/index.d.ts +2 -0
- package/dist/protocols/rest/response/index.js +7 -0
- package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
- package/dist/protocols/rest/response/size-calculator.js +64 -0
- package/dist/protocols/web/browser/highlight.d.ts +7 -0
- package/dist/protocols/web/browser/highlight.js +47 -0
- package/dist/protocols/web/browser/index.d.ts +4 -0
- package/dist/protocols/web/browser/index.js +11 -0
- package/dist/protocols/web/browser/manager.d.ts +20 -0
- package/dist/protocols/web/browser/manager.js +189 -0
- package/dist/protocols/web/browser/screenshot.d.ts +8 -0
- package/dist/protocols/web/browser/screenshot.js +69 -0
- package/dist/protocols/web/browser/storage.d.ts +5 -0
- package/dist/protocols/web/browser/storage.js +45 -0
- package/dist/protocols/web/commands/index.d.ts +5 -0
- package/dist/protocols/web/commands/index.js +11 -0
- package/dist/protocols/web/commands/interaction.d.ts +13 -0
- package/dist/protocols/web/commands/interaction.js +68 -0
- package/dist/protocols/web/commands/measurement.d.ts +16 -0
- package/dist/protocols/web/commands/measurement.js +33 -0
- package/dist/protocols/web/commands/navigation.d.ts +11 -0
- package/dist/protocols/web/commands/navigation.js +43 -0
- package/dist/protocols/web/commands/types.d.ts +12 -0
- package/dist/protocols/web/commands/types.js +2 -0
- package/dist/protocols/web/commands/verification.d.ts +11 -0
- package/dist/protocols/web/commands/verification.js +98 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +160 -650
- package/dist/protocols/web/network/capture.d.ts +19 -0
- package/dist/protocols/web/network/capture.js +225 -0
- package/dist/protocols/web/network/filters.d.ts +5 -0
- package/dist/protocols/web/network/filters.js +49 -0
- package/dist/protocols/web/network/index.d.ts +4 -0
- package/dist/protocols/web/network/index.js +9 -0
- package/dist/protocols/web/network/types.d.ts +13 -0
- package/dist/protocols/web/network/types.js +2 -0
- package/dist/protocols/web/network/utils.d.ts +8 -0
- package/dist/protocols/web/network/utils.js +29 -0
- package/dist/reporting/chart-data/index.d.ts +5 -0
- package/dist/reporting/chart-data/index.js +13 -0
- package/dist/reporting/chart-data/network.d.ts +25 -0
- package/dist/reporting/chart-data/network.js +78 -0
- package/dist/reporting/chart-data/scenario.d.ts +37 -0
- package/dist/reporting/chart-data/scenario.js +76 -0
- package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
- package/dist/reporting/chart-data/step-statistics.js +94 -0
- package/dist/reporting/chart-data/throughput.d.ts +16 -0
- package/dist/reporting/chart-data/throughput.js +24 -0
- package/dist/reporting/chart-data/timeline.d.ts +17 -0
- package/dist/reporting/chart-data/timeline.js +46 -0
- package/dist/reporting/handlebars-helpers.d.ts +1 -0
- package/dist/reporting/handlebars-helpers.js +63 -0
- package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
- package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
- package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
- package/dist/utils/data-utils.d.ts +17 -0
- package/dist/utils/data-utils.js +129 -0
- package/dist/utils/template.js +2 -2
- package/package.json +5 -2
- package/dist/core/csv-data-provider.d.ts +0 -47
- package/dist/core/csv-data-provider.js +0 -265
- package/dist/reporting/generator.d.ts +0 -42
- package/dist/reporting/generator.js +0 -1217
- package/dist/reporting/templates/html.hbs +0 -2453
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthHandler = void 0;
|
|
4
|
+
class AuthHandler {
|
|
5
|
+
static handleAuthentication(config, headers, auth) {
|
|
6
|
+
switch (auth.type) {
|
|
7
|
+
case 'basic':
|
|
8
|
+
if (auth.username && auth.password) {
|
|
9
|
+
config.auth = { username: auth.username, password: auth.password };
|
|
10
|
+
}
|
|
11
|
+
break;
|
|
12
|
+
case 'bearer':
|
|
13
|
+
if (auth.token) {
|
|
14
|
+
headers['Authorization'] = `Bearer ${auth.token}`;
|
|
15
|
+
}
|
|
16
|
+
break;
|
|
17
|
+
case 'digest':
|
|
18
|
+
if (auth.username && auth.password) {
|
|
19
|
+
config.auth = { username: auth.username, password: auth.password };
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
case 'oauth':
|
|
23
|
+
if (auth.token) {
|
|
24
|
+
headers['Authorization'] = `OAuth ${auth.token}`;
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.AuthHandler = AuthHandler;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class BodyProcessor {
|
|
2
|
+
static processBodyPayload(body: any): {
|
|
3
|
+
data: any;
|
|
4
|
+
contentType?: string;
|
|
5
|
+
};
|
|
6
|
+
static isJsonString(str: string): boolean;
|
|
7
|
+
static isXmlString(str: string): boolean;
|
|
8
|
+
static isTemplateString(body: string): boolean;
|
|
9
|
+
static detectTemplateContentType(templateBody: string): string | undefined;
|
|
10
|
+
static hasContentTypeHeader(headers: Record<string, string>): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BodyProcessor = void 0;
|
|
4
|
+
class BodyProcessor {
|
|
5
|
+
static processBodyPayload(body) {
|
|
6
|
+
if (typeof body === 'string') {
|
|
7
|
+
const trimmedBody = body.trim();
|
|
8
|
+
if (BodyProcessor.isJsonString(trimmedBody)) {
|
|
9
|
+
return { data: body, contentType: 'application/json' };
|
|
10
|
+
}
|
|
11
|
+
if (BodyProcessor.isXmlString(trimmedBody)) {
|
|
12
|
+
return { data: body, contentType: 'application/xml' };
|
|
13
|
+
}
|
|
14
|
+
if (BodyProcessor.isTemplateString(body)) {
|
|
15
|
+
const detectedType = BodyProcessor.detectTemplateContentType(body);
|
|
16
|
+
return { data: body, contentType: detectedType };
|
|
17
|
+
}
|
|
18
|
+
return { data: body, contentType: 'text/plain' };
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return { data: JSON.stringify(body), contentType: 'application/json' };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
static isJsonString(str) {
|
|
25
|
+
if (!str.startsWith('{') && !str.startsWith('[')) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
JSON.parse(str);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
static isXmlString(str) {
|
|
37
|
+
return str.startsWith('<?xml') ||
|
|
38
|
+
(str.startsWith('<') && str.includes('>') && str.endsWith('>'));
|
|
39
|
+
}
|
|
40
|
+
static isTemplateString(body) {
|
|
41
|
+
return body.includes('{{template:') ||
|
|
42
|
+
(body.includes('{{') && body.includes('}}'));
|
|
43
|
+
}
|
|
44
|
+
static detectTemplateContentType(templateBody) {
|
|
45
|
+
const templateMatch = templateBody.match(/\{\{template:([^}]+)\}\}/);
|
|
46
|
+
if (templateMatch) {
|
|
47
|
+
const templatePath = templateMatch[1];
|
|
48
|
+
if (templatePath.endsWith('.json'))
|
|
49
|
+
return 'application/json';
|
|
50
|
+
if (templatePath.endsWith('.xml'))
|
|
51
|
+
return 'application/xml';
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
static hasContentTypeHeader(headers) {
|
|
56
|
+
if (!headers)
|
|
57
|
+
return false;
|
|
58
|
+
const headerKeys = Object.keys(headers).map(key => key.toLowerCase());
|
|
59
|
+
return headerKeys.includes('content-type');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.BodyProcessor = BodyProcessor;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthHandler = exports.BodyProcessor = void 0;
|
|
4
|
+
var body_processor_1 = require("./body-processor");
|
|
5
|
+
Object.defineProperty(exports, "BodyProcessor", { enumerable: true, get: function () { return body_processor_1.BodyProcessor; } });
|
|
6
|
+
var auth_handler_1 = require("./auth-handler");
|
|
7
|
+
Object.defineProperty(exports, "AuthHandler", { enumerable: true, get: function () { return auth_handler_1.AuthHandler; } });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AxiosResponse } from 'axios';
|
|
2
|
+
import { ProtocolResult } from '../../base';
|
|
3
|
+
export declare class ResponseChecks {
|
|
4
|
+
static runChecks(checks: any[], response: AxiosResponse, duration: number, result: ProtocolResult): void;
|
|
5
|
+
static getJsonPath(obj: any, path: string): any;
|
|
6
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResponseChecks = void 0;
|
|
4
|
+
class ResponseChecks {
|
|
5
|
+
static runChecks(checks, response, duration, result) {
|
|
6
|
+
let hasFailure = false;
|
|
7
|
+
const errors = [];
|
|
8
|
+
for (const check of checks) {
|
|
9
|
+
try {
|
|
10
|
+
let passed = true;
|
|
11
|
+
switch (check.type) {
|
|
12
|
+
case 'status':
|
|
13
|
+
passed = response.status === check.value;
|
|
14
|
+
if (!passed)
|
|
15
|
+
errors.push(`Expected status ${check.value}, got ${response.status}`);
|
|
16
|
+
break;
|
|
17
|
+
case 'response_time':
|
|
18
|
+
const expectedTime = typeof check.value === 'string'
|
|
19
|
+
? parseInt(check.value.replace(/[<>]/g, ''))
|
|
20
|
+
: check.value;
|
|
21
|
+
if (check.operator === 'lt' || check.value.toString().startsWith('<')) {
|
|
22
|
+
passed = duration < expectedTime;
|
|
23
|
+
if (!passed)
|
|
24
|
+
errors.push(`Response time ${duration}ms exceeded ${expectedTime}ms`);
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'json_path':
|
|
28
|
+
const value = ResponseChecks.getJsonPath(response.data, check.value);
|
|
29
|
+
passed = value !== undefined && value !== null;
|
|
30
|
+
if (!passed)
|
|
31
|
+
errors.push(`JSON path ${check.value} not found`);
|
|
32
|
+
break;
|
|
33
|
+
case 'text_contains':
|
|
34
|
+
const text = typeof response.data === 'string'
|
|
35
|
+
? response.data
|
|
36
|
+
: JSON.stringify(response.data);
|
|
37
|
+
passed = text.includes(check.value);
|
|
38
|
+
if (!passed)
|
|
39
|
+
errors.push(`Response does not contain "${check.value}"`);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (!passed)
|
|
43
|
+
hasFailure = true;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
errors.push(`Check error: ${error}`);
|
|
47
|
+
hasFailure = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (hasFailure) {
|
|
51
|
+
result.success = false;
|
|
52
|
+
result.error = `Checks failed: ${errors.join(', ')}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
static getJsonPath(obj, path) {
|
|
56
|
+
if (!obj)
|
|
57
|
+
return undefined;
|
|
58
|
+
const keys = path.replace(/^\$\./, '').split('.');
|
|
59
|
+
let current = obj;
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
if (current && typeof current === 'object' && key in current) {
|
|
62
|
+
current = current[key];
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return current;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.ResponseChecks = ResponseChecks;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResponseChecks = exports.SizeCalculator = void 0;
|
|
4
|
+
var size_calculator_1 = require("./size-calculator");
|
|
5
|
+
Object.defineProperty(exports, "SizeCalculator", { enumerable: true, get: function () { return size_calculator_1.SizeCalculator; } });
|
|
6
|
+
var checks_1 = require("./checks");
|
|
7
|
+
Object.defineProperty(exports, "ResponseChecks", { enumerable: true, get: function () { return checks_1.ResponseChecks; } });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
export declare class SizeCalculator {
|
|
3
|
+
static calculateRequestSizes(config: AxiosRequestConfig): {
|
|
4
|
+
requestHeadersSize: number;
|
|
5
|
+
requestBodySize: number;
|
|
6
|
+
};
|
|
7
|
+
static calculateResponseSizes(response: AxiosResponse, getResponseText: (data: any) => string): {
|
|
8
|
+
headersSize: number;
|
|
9
|
+
bodySize: number;
|
|
10
|
+
};
|
|
11
|
+
static detectDataType(response: AxiosResponse): 'text' | 'bin' | '';
|
|
12
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SizeCalculator = void 0;
|
|
4
|
+
class SizeCalculator {
|
|
5
|
+
static calculateRequestSizes(config) {
|
|
6
|
+
let requestHeadersSize = 0;
|
|
7
|
+
let requestBodySize = 0;
|
|
8
|
+
if (config.headers) {
|
|
9
|
+
const headersString = Object.entries(config.headers)
|
|
10
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
11
|
+
.join('\r\n');
|
|
12
|
+
requestHeadersSize = Buffer.byteLength(headersString + '\r\n\r\n', 'utf8');
|
|
13
|
+
}
|
|
14
|
+
if (config.data) {
|
|
15
|
+
if (typeof config.data === 'string') {
|
|
16
|
+
requestBodySize = Buffer.byteLength(config.data, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
else if (Buffer.isBuffer(config.data)) {
|
|
19
|
+
requestBodySize = config.data.length;
|
|
20
|
+
}
|
|
21
|
+
else if (typeof config.data === 'object') {
|
|
22
|
+
try {
|
|
23
|
+
const dataString = JSON.stringify(config.data);
|
|
24
|
+
requestBodySize = Buffer.byteLength(dataString, 'utf8');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
requestBodySize = 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { requestHeadersSize, requestBodySize };
|
|
32
|
+
}
|
|
33
|
+
static calculateResponseSizes(response, getResponseText) {
|
|
34
|
+
let headersSize = 0;
|
|
35
|
+
let bodySize = 0;
|
|
36
|
+
if (response.headers) {
|
|
37
|
+
const headersString = Object.entries(response.headers)
|
|
38
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
39
|
+
.join('\r\n');
|
|
40
|
+
headersSize = Buffer.byteLength(headersString + '\r\n\r\n', 'utf8');
|
|
41
|
+
}
|
|
42
|
+
if (response.data) {
|
|
43
|
+
const responseText = getResponseText(response.data);
|
|
44
|
+
bodySize = Buffer.byteLength(responseText, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
return { headersSize, bodySize };
|
|
47
|
+
}
|
|
48
|
+
static detectDataType(response) {
|
|
49
|
+
const contentType = response.headers['content-type'] || '';
|
|
50
|
+
if (contentType.includes('text/') ||
|
|
51
|
+
contentType.includes('application/json') ||
|
|
52
|
+
contentType.includes('application/xml') ||
|
|
53
|
+
contentType.includes('application/javascript')) {
|
|
54
|
+
return 'text';
|
|
55
|
+
}
|
|
56
|
+
else if (contentType.includes('image/') ||
|
|
57
|
+
contentType.includes('application/octet-stream') ||
|
|
58
|
+
contentType.includes('application/pdf')) {
|
|
59
|
+
return 'bin';
|
|
60
|
+
}
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.SizeCalculator = SizeCalculator;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { HighlightConfig } from '../../../config';
|
|
3
|
+
export declare class ElementHighlighter {
|
|
4
|
+
private highlightConfig;
|
|
5
|
+
constructor(highlightConfig: boolean | HighlightConfig | undefined);
|
|
6
|
+
highlightElement(page: Page, selector: string): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElementHighlighter = void 0;
|
|
4
|
+
const logger_1 = require("../../../utils/logger");
|
|
5
|
+
class ElementHighlighter {
|
|
6
|
+
constructor(highlightConfig) {
|
|
7
|
+
this.highlightConfig = highlightConfig;
|
|
8
|
+
}
|
|
9
|
+
async highlightElement(page, selector) {
|
|
10
|
+
if (!this.highlightConfig)
|
|
11
|
+
return;
|
|
12
|
+
const config = typeof this.highlightConfig === 'boolean'
|
|
13
|
+
? { enabled: this.highlightConfig }
|
|
14
|
+
: this.highlightConfig;
|
|
15
|
+
if (!config.enabled)
|
|
16
|
+
return;
|
|
17
|
+
const duration = config.duration || 500;
|
|
18
|
+
const color = config.color || '#ff0000';
|
|
19
|
+
const style = config.style || 'border';
|
|
20
|
+
try {
|
|
21
|
+
const locator = page.locator(selector).first();
|
|
22
|
+
const count = await locator.count();
|
|
23
|
+
if (count === 0)
|
|
24
|
+
return;
|
|
25
|
+
await locator.evaluate((el, opts) => {
|
|
26
|
+
const { color, style, duration } = opts;
|
|
27
|
+
const originalStyle = el.getAttribute('style') || '';
|
|
28
|
+
let highlightStyle = '';
|
|
29
|
+
if (style === 'border' || style === 'both') {
|
|
30
|
+
highlightStyle += `outline: 3px solid ${color} !important; outline-offset: 2px !important;`;
|
|
31
|
+
}
|
|
32
|
+
if (style === 'background' || style === 'both') {
|
|
33
|
+
highlightStyle += `background-color: ${color}33 !important;`;
|
|
34
|
+
}
|
|
35
|
+
el.setAttribute('style', originalStyle + highlightStyle);
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
el.setAttribute('style', originalStyle);
|
|
38
|
+
}, duration);
|
|
39
|
+
}, { color, style, duration });
|
|
40
|
+
await page.waitForTimeout(duration);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
logger_1.logger.debug(`Failed to highlight element ${selector}:`, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.ElementHighlighter = ElementHighlighter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScreenshotCapture = exports.ElementHighlighter = exports.StorageManager = exports.BrowserManager = void 0;
|
|
4
|
+
var manager_1 = require("./manager");
|
|
5
|
+
Object.defineProperty(exports, "BrowserManager", { enumerable: true, get: function () { return manager_1.BrowserManager; } });
|
|
6
|
+
var storage_1 = require("./storage");
|
|
7
|
+
Object.defineProperty(exports, "StorageManager", { enumerable: true, get: function () { return storage_1.StorageManager; } });
|
|
8
|
+
var highlight_1 = require("./highlight");
|
|
9
|
+
Object.defineProperty(exports, "ElementHighlighter", { enumerable: true, get: function () { return highlight_1.ElementHighlighter; } });
|
|
10
|
+
var screenshot_1 = require("./screenshot");
|
|
11
|
+
Object.defineProperty(exports, "ScreenshotCapture", { enumerable: true, get: function () { return screenshot_1.ScreenshotCapture; } });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Browser, BrowserContext, Page } from 'playwright';
|
|
2
|
+
import { BrowserConfig } from '../../../config';
|
|
3
|
+
export declare class BrowserManager {
|
|
4
|
+
private config;
|
|
5
|
+
private browsers;
|
|
6
|
+
private contexts;
|
|
7
|
+
private pages;
|
|
8
|
+
constructor(config: BrowserConfig);
|
|
9
|
+
getPage(vuId: number, onPageCreated?: (page: Page, context: BrowserContext) => Promise<void>): Promise<Page>;
|
|
10
|
+
getExistingPage(vuId: number): Page | undefined;
|
|
11
|
+
getContext(vuId: number): BrowserContext | undefined;
|
|
12
|
+
getBrowser(vuId: number): Browser | undefined;
|
|
13
|
+
private createBrowserForVU;
|
|
14
|
+
getBrowserInfo(vuId: number): {
|
|
15
|
+
connected: boolean;
|
|
16
|
+
} | null;
|
|
17
|
+
getActiveVUCount(): number;
|
|
18
|
+
cleanupVU(vuId: number): Promise<void>;
|
|
19
|
+
cleanupAll(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserManager = void 0;
|
|
4
|
+
const playwright_1 = require("playwright");
|
|
5
|
+
const logger_1 = require("../../../utils/logger");
|
|
6
|
+
class BrowserManager {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.browsers = new Map();
|
|
10
|
+
this.contexts = new Map();
|
|
11
|
+
this.pages = new Map();
|
|
12
|
+
}
|
|
13
|
+
async getPage(vuId, onPageCreated) {
|
|
14
|
+
let page = this.pages.get(vuId);
|
|
15
|
+
if (!page) {
|
|
16
|
+
const browser = await this.createBrowserForVU(vuId);
|
|
17
|
+
this.browsers.set(vuId, browser);
|
|
18
|
+
const context = await browser.newContext({
|
|
19
|
+
viewport: this.config.viewport || { width: 1280, height: 720 },
|
|
20
|
+
ignoreHTTPSErrors: true,
|
|
21
|
+
storageState: undefined
|
|
22
|
+
});
|
|
23
|
+
page = await context.newPage();
|
|
24
|
+
page.setDefaultTimeout(30000);
|
|
25
|
+
page.setDefaultNavigationTimeout(30000);
|
|
26
|
+
if (onPageCreated) {
|
|
27
|
+
await onPageCreated(page, context);
|
|
28
|
+
}
|
|
29
|
+
this.contexts.set(vuId, context);
|
|
30
|
+
this.pages.set(vuId, page);
|
|
31
|
+
logger_1.logger.debug(`VU ${vuId}: Created enhanced browser with Web Vitals support`);
|
|
32
|
+
}
|
|
33
|
+
return page;
|
|
34
|
+
}
|
|
35
|
+
getExistingPage(vuId) {
|
|
36
|
+
return this.pages.get(vuId);
|
|
37
|
+
}
|
|
38
|
+
getContext(vuId) {
|
|
39
|
+
return this.contexts.get(vuId);
|
|
40
|
+
}
|
|
41
|
+
getBrowser(vuId) {
|
|
42
|
+
return this.browsers.get(vuId);
|
|
43
|
+
}
|
|
44
|
+
async createBrowserForVU(vuId) {
|
|
45
|
+
const browserType = this.config.type || 'chromium';
|
|
46
|
+
const launchOptions = {
|
|
47
|
+
headless: this.config.headless !== false,
|
|
48
|
+
slowMo: this.config.slow_mo || 0,
|
|
49
|
+
args: [
|
|
50
|
+
'--no-sandbox',
|
|
51
|
+
'--disable-dev-shm-usage',
|
|
52
|
+
'--disable-web-security',
|
|
53
|
+
'--disable-features=VizDisplayCompositor',
|
|
54
|
+
'--disable-http-cache',
|
|
55
|
+
'--disable-cache',
|
|
56
|
+
'--disable-application-cache',
|
|
57
|
+
'--disable-offline-load-stale-cache',
|
|
58
|
+
'--disk-cache-size=0',
|
|
59
|
+
'--media-cache-size=0',
|
|
60
|
+
'--no-first-run',
|
|
61
|
+
'--no-default-browser-check',
|
|
62
|
+
'--disable-background-networking',
|
|
63
|
+
'--disable-sync',
|
|
64
|
+
'--metrics-recording-only',
|
|
65
|
+
'--mute-audio',
|
|
66
|
+
'--disable-renderer-backgrounding',
|
|
67
|
+
'--enable-precise-memory-info',
|
|
68
|
+
'--enable-performance-manager-web-contents-observer',
|
|
69
|
+
'--enable-experimental-web-platform-features'
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
let browser;
|
|
73
|
+
try {
|
|
74
|
+
switch (browserType) {
|
|
75
|
+
case 'chromium':
|
|
76
|
+
browser = await playwright_1.chromium.launch(launchOptions);
|
|
77
|
+
break;
|
|
78
|
+
case 'chrome':
|
|
79
|
+
browser = await playwright_1.chromium.launch({ ...launchOptions, channel: 'chrome' });
|
|
80
|
+
break;
|
|
81
|
+
case 'msedge':
|
|
82
|
+
browser = await playwright_1.chromium.launch({ ...launchOptions, channel: 'msedge' });
|
|
83
|
+
break;
|
|
84
|
+
case 'firefox':
|
|
85
|
+
browser = await playwright_1.firefox.launch(launchOptions);
|
|
86
|
+
break;
|
|
87
|
+
case 'webkit':
|
|
88
|
+
browser = await playwright_1.webkit.launch(launchOptions);
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
throw new Error(`Unsupported browser type: ${browserType}. Supported: chromium, chrome, msedge, firefox, webkit`);
|
|
92
|
+
}
|
|
93
|
+
logger_1.logger.debug(`VU ${vuId}: Launched enhanced ${browserType} browser with Web Vitals support`);
|
|
94
|
+
return browser;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger_1.logger.error(`VU ${vuId}: Failed to launch ${browserType} browser:`, error);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
getBrowserInfo(vuId) {
|
|
102
|
+
const browser = this.browsers.get(vuId);
|
|
103
|
+
if (!browser)
|
|
104
|
+
return null;
|
|
105
|
+
return { connected: browser.isConnected() };
|
|
106
|
+
}
|
|
107
|
+
getActiveVUCount() {
|
|
108
|
+
return this.browsers.size;
|
|
109
|
+
}
|
|
110
|
+
async cleanupVU(vuId) {
|
|
111
|
+
logger_1.logger.debug(`Cleaning up enhanced browser resources for VU ${vuId}...`);
|
|
112
|
+
const page = this.pages.get(vuId);
|
|
113
|
+
if (page) {
|
|
114
|
+
try {
|
|
115
|
+
if (!page.isClosed()) {
|
|
116
|
+
await page.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing page:`, error);
|
|
121
|
+
}
|
|
122
|
+
this.pages.delete(vuId);
|
|
123
|
+
}
|
|
124
|
+
const context = this.contexts.get(vuId);
|
|
125
|
+
if (context) {
|
|
126
|
+
try {
|
|
127
|
+
await context.close();
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (!error?.message?.includes('Failed to find context') &&
|
|
131
|
+
!error?.message?.includes('Target closed') &&
|
|
132
|
+
!error?.message?.includes('has been closed')) {
|
|
133
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing context:`, error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.contexts.delete(vuId);
|
|
137
|
+
}
|
|
138
|
+
const browser = this.browsers.get(vuId);
|
|
139
|
+
if (browser) {
|
|
140
|
+
try {
|
|
141
|
+
if (browser.isConnected()) {
|
|
142
|
+
await browser.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing browser:`, error);
|
|
147
|
+
}
|
|
148
|
+
this.browsers.delete(vuId);
|
|
149
|
+
}
|
|
150
|
+
logger_1.logger.debug(`VU ${vuId}: Enhanced browser cleanup completed`);
|
|
151
|
+
}
|
|
152
|
+
async cleanupAll() {
|
|
153
|
+
const browserCount = this.browsers.size;
|
|
154
|
+
logger_1.logger.debug(`Cleaning up ${browserCount} enhanced browsers...`);
|
|
155
|
+
for (const [vuId, page] of this.pages) {
|
|
156
|
+
try {
|
|
157
|
+
if (!page.isClosed()) {
|
|
158
|
+
await page.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing page:`, error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const [vuId, context] of this.contexts) {
|
|
166
|
+
try {
|
|
167
|
+
await context.close();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing context:`, error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const [vuId, browser] of this.browsers) {
|
|
174
|
+
try {
|
|
175
|
+
if (browser.isConnected()) {
|
|
176
|
+
await browser.close();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
logger_1.logger.warn(`VU ${vuId}: Error closing browser:`, error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
this.pages.clear();
|
|
184
|
+
this.contexts.clear();
|
|
185
|
+
this.browsers.clear();
|
|
186
|
+
logger_1.logger.debug(`Enhanced cleanup completed - ${browserCount} browsers closed`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.BrowserManager = BrowserManager;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Page } from 'playwright';
|
|
2
|
+
import { ScreenshotConfig } from '../../../config';
|
|
3
|
+
export declare class ScreenshotCapture {
|
|
4
|
+
private screenshotConfig;
|
|
5
|
+
constructor(screenshotConfig: boolean | ScreenshotConfig | undefined);
|
|
6
|
+
captureFailureScreenshot(page: Page, vuId: number, command: string): Promise<string>;
|
|
7
|
+
isEnabled(): boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ScreenshotCapture = void 0;
|
|
37
|
+
const logger_1 = require("../../../utils/logger");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
class ScreenshotCapture {
|
|
41
|
+
constructor(screenshotConfig) {
|
|
42
|
+
this.screenshotConfig = screenshotConfig;
|
|
43
|
+
}
|
|
44
|
+
async captureFailureScreenshot(page, vuId, command) {
|
|
45
|
+
let outputDir = 'screenshots';
|
|
46
|
+
let fullPage = true;
|
|
47
|
+
if (typeof this.screenshotConfig === 'object') {
|
|
48
|
+
outputDir = this.screenshotConfig.output_dir || 'screenshots';
|
|
49
|
+
fullPage = this.screenshotConfig.full_page !== false;
|
|
50
|
+
}
|
|
51
|
+
if (!fs.existsSync(outputDir)) {
|
|
52
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
55
|
+
const sanitizedCommand = command.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
56
|
+
const filename = `failure_vu${vuId}_${sanitizedCommand}_${timestamp}.png`;
|
|
57
|
+
const screenshotPath = path.join(outputDir, filename);
|
|
58
|
+
await page.screenshot({
|
|
59
|
+
path: screenshotPath,
|
|
60
|
+
fullPage
|
|
61
|
+
});
|
|
62
|
+
logger_1.logger.info(`📸 Screenshot captured: ${screenshotPath}`);
|
|
63
|
+
return screenshotPath;
|
|
64
|
+
}
|
|
65
|
+
isEnabled() {
|
|
66
|
+
return !!this.screenshotConfig;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.ScreenshotCapture = ScreenshotCapture;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Page, BrowserContext } from 'playwright';
|
|
2
|
+
import { ClearStorageConfig } from '../../../config';
|
|
3
|
+
export declare class StorageManager {
|
|
4
|
+
clearStorageIfConfigured(page: Page, context: BrowserContext, clearConfig: boolean | ClearStorageConfig | undefined): Promise<void>;
|
|
5
|
+
}
|