@loadstrike/loadstrike-sdk 1.0.26901 → 1.0.27301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cjs/index.js +31 -2
- package/dist/cjs/local.js +74 -2
- package/dist/cjs/runtime.js +422 -2
- package/dist/cjs/sinks.js +262 -1
- package/dist/cjs/transports.js +659 -12
- package/dist/esm/index.js +3 -3
- package/dist/esm/local.js +74 -2
- package/dist/esm/runtime.js +414 -1
- package/dist/esm/sinks.js +249 -0
- package/dist/esm/transports.js +652 -11
- package/dist/types/contracts.d.ts +30 -0
- package/dist/types/index.d.ts +7 -7
- package/dist/types/runtime.d.ts +120 -0
- package/dist/types/sinks.d.ts +136 -0
- package/dist/types/transports.d.ts +194 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ LoadStrike is a developer-first load testing SDK for Node.js applications, servi
|
|
|
9
9
|
- Exercise HTTP and event-driven workflows across multiple steps.
|
|
10
10
|
- Apply load simulations, thresholds, and custom metrics to real transactions.
|
|
11
11
|
- Split a single load profile across weighted scenario mixes.
|
|
12
|
-
- Generate local reports and, on
|
|
12
|
+
- Generate local reports and, on Enterprise, publish observability data to supported sinks.
|
|
13
13
|
|
|
14
|
-
Built-in transport coverage includes HTTP, Kafka, RabbitMQ, NATS, Redis Streams, Azure Event Hubs, Push Diffusion, and delegate-based custom streams. Local report output supports HTML, Markdown, TXT, and CSV, and
|
|
14
|
+
Built-in transport coverage includes HTTP, Kafka, RabbitMQ, NATS, Redis Streams, Azure Event Hubs, Push Diffusion, and delegate-based custom streams. Local report output supports HTML, Markdown, TXT, and CSV, and Enterprise can publish to InfluxDB, TimescaleDB, Grafana Loki, Datadog, Splunk HEC, OpenTelemetry Collector, and the expanded built-in sink family.
|
|
15
15
|
|
|
16
16
|
## Requirements
|
|
17
17
|
|
|
@@ -56,7 +56,7 @@ const result = await LoadStrikeRunner
|
|
|
56
56
|
|
|
57
57
|
## Traffic Mixes
|
|
58
58
|
|
|
59
|
-
Use `LoadStrikeTrafficMix` on
|
|
59
|
+
Use `LoadStrikeTrafficMix` on Pro and Enterprise plans when one total load profile should be distributed across multiple scenario lanes. For example, a 1000 requests-per-second profile with scenario weights of 60, 30, and 10 sends roughly 600 requests per second to the first scenario, 300 to the second, and 100 to the third.
|
|
60
60
|
|
|
61
61
|
Each lane is still a normal scenario with its own named steps, thresholds, reports, and portal results. Register the mix with `LoadStrikeRunner.registerTrafficMix(...)` or add it to a runner with `.addTrafficMix(...)`.
|
|
62
62
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.PortalReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.InfluxDbReportingSinkOptions = exports.InfluxDbReportingSink = exports.GrafanaLokiReportingSinkOptions = void 0;
|
|
3
|
+
exports.SqsEndpointDefinition = exports.AzureEventHubsEndpointDefinition = exports.RedisStreamsEndpointDefinition = exports.NatsEndpointDefinition = exports.RabbitMqEndpointDefinition = exports.KafkaSaslOptions = exports.KafkaEndpointDefinition = exports.HttpEndpointDefinition = exports.TrafficEndpointDefinition = exports.LOADSTRIKE_TRACE_ID_TRACKING_FIELD = exports.LOADSTRIKE_TRACE_ID_HEADER = exports.TrackingFieldSelector = exports.TrackingPayloadBuilder = exports.RedisCorrelationStore = exports.RedisCorrelationStoreOptions = exports.InMemoryCorrelationStore = exports.CrossPlatformTrackingRuntime = exports.CorrelationStoreConfiguration = exports.LoadStrikeThreshold = exports.LoadStrikeStep = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeMetric = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeTrafficMix = exports.LoadStrikeSimulation = exports.LoadStrikeScenarioShare = exports.LoadStrikeScenario = exports.LoadStrikeRunner = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeResponse = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = exports.LoadStrikePluginDataTable = exports.LoadStrikePluginData = exports.LoadStrikeBrowserWebVitalsCollector = exports.LoadStrikeBrowserWebVitals = exports.LoadStrikeAccessibilityScanner = exports.LoadStrikeAccessibilityCiGate = exports.LoadStrikeAccessibilityBaseline = exports.LoadStrikeAccessibilityAdapters = exports.LoadStrikeAccessibility = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeAutopilotReadiness = exports.LoadStrikeAutopilotResult = exports.LoadStrikeAutopilot = void 0;
|
|
4
|
+
exports.TimescaleDbReportingSinkOptions = exports.TimescaleDbReportingSink = exports.StatsDReportingSink = exports.SplunkReportingSinkOptions = exports.SplunkReportingSink = exports.PrometheusRemoteWriteReportingSink = exports.PortalReportingSink = exports.OpenSearchReportingSink = exports.OtelCollectorReportingSinkOptions = exports.OtelCollectorReportingSink = exports.NewRelicReportingSink = exports.NetdataStatsDReportingSink = exports.KafkaReportingSink = exports.JsonlFileReportingSink = exports.InfluxDbReportingSinkOptions = exports.InfluxDbReportingSink = exports.GenericWebhookReportingSink = exports.GrafanaLokiReportingSinkOptions = exports.GrafanaLokiReportingSink = exports.ElasticsearchReportingSink = exports.DynatraceReportingSink = exports.CloudWatchReportingSink = exports.DogStatsDReportingSink = exports.DatadogReportingSinkOptions = exports.DatadogReportingSink = exports.HttpAuthOptions = exports.HttpOAuth2ClientCredentialsOptions = exports.WebSocketReconnectPolicy = exports.WebSocketNativeClientOptions = exports.WebSocketMessageSpec = exports.WebSocketExpectedMessage = exports.WebSocketEndpointDefinition = exports.ProtocolMetricSnapshot = exports.GrpcStatusMapper = exports.GrpcNativeClientOptions = exports.GrpcMethodTypes = exports.GrpcEndpointDefinition = exports.PushDiffusionEndpointDefinition = exports.DelegateStreamEndpointDefinition = void 0;
|
|
5
5
|
var autopilot_js_1 = require("./autopilot.js");
|
|
6
6
|
Object.defineProperty(exports, "LoadStrikeAutopilot", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilot; } });
|
|
7
7
|
Object.defineProperty(exports, "LoadStrikeAutopilotResult", { enumerable: true, get: function () { return autopilot_js_1.LoadStrikeAutopilotResult; } });
|
|
@@ -11,6 +11,13 @@ var runtime_js_1 = require("./runtime.js");
|
|
|
11
11
|
Object.defineProperty(exports, "CrossPlatformScenarioConfigurator", { enumerable: true, get: function () { return runtime_js_1.CrossPlatformScenarioConfigurator; } });
|
|
12
12
|
Object.defineProperty(exports, "ScenarioTrackingExtensions", { enumerable: true, get: function () { return runtime_js_1.ScenarioTrackingExtensions; } });
|
|
13
13
|
Object.defineProperty(exports, "LoadStrikeContext", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeContext; } });
|
|
14
|
+
Object.defineProperty(exports, "LoadStrikeAccessibility", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibility; } });
|
|
15
|
+
Object.defineProperty(exports, "LoadStrikeAccessibilityAdapters", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibilityAdapters; } });
|
|
16
|
+
Object.defineProperty(exports, "LoadStrikeAccessibilityBaseline", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibilityBaseline; } });
|
|
17
|
+
Object.defineProperty(exports, "LoadStrikeAccessibilityCiGate", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibilityCiGate; } });
|
|
18
|
+
Object.defineProperty(exports, "LoadStrikeAccessibilityScanner", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeAccessibilityScanner; } });
|
|
19
|
+
Object.defineProperty(exports, "LoadStrikeBrowserWebVitals", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeBrowserWebVitals; } });
|
|
20
|
+
Object.defineProperty(exports, "LoadStrikeBrowserWebVitalsCollector", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeBrowserWebVitalsCollector; } });
|
|
14
21
|
Object.defineProperty(exports, "LoadStrikePluginData", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginData; } });
|
|
15
22
|
Object.defineProperty(exports, "LoadStrikePluginDataTable", { enumerable: true, get: function () { return runtime_js_1.LoadStrikePluginDataTable; } });
|
|
16
23
|
Object.defineProperty(exports, "LoadStrikeNodeType", { enumerable: true, get: function () { return runtime_js_1.LoadStrikeNodeType; } });
|
|
@@ -52,19 +59,41 @@ Object.defineProperty(exports, "AzureEventHubsEndpointDefinition", { enumerable:
|
|
|
52
59
|
Object.defineProperty(exports, "SqsEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.SqsEndpointDefinition; } });
|
|
53
60
|
Object.defineProperty(exports, "DelegateStreamEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.DelegateStreamEndpointDefinition; } });
|
|
54
61
|
Object.defineProperty(exports, "PushDiffusionEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.PushDiffusionEndpointDefinition; } });
|
|
62
|
+
Object.defineProperty(exports, "GrpcEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.GrpcEndpointDefinition; } });
|
|
63
|
+
Object.defineProperty(exports, "GrpcMethodTypes", { enumerable: true, get: function () { return transports_js_1.GrpcMethodTypes; } });
|
|
64
|
+
Object.defineProperty(exports, "GrpcNativeClientOptions", { enumerable: true, get: function () { return transports_js_1.GrpcNativeClientOptions; } });
|
|
65
|
+
Object.defineProperty(exports, "GrpcStatusMapper", { enumerable: true, get: function () { return transports_js_1.GrpcStatusMapper; } });
|
|
66
|
+
Object.defineProperty(exports, "ProtocolMetricSnapshot", { enumerable: true, get: function () { return transports_js_1.ProtocolMetricSnapshot; } });
|
|
67
|
+
Object.defineProperty(exports, "WebSocketEndpointDefinition", { enumerable: true, get: function () { return transports_js_1.WebSocketEndpointDefinition; } });
|
|
68
|
+
Object.defineProperty(exports, "WebSocketExpectedMessage", { enumerable: true, get: function () { return transports_js_1.WebSocketExpectedMessage; } });
|
|
69
|
+
Object.defineProperty(exports, "WebSocketMessageSpec", { enumerable: true, get: function () { return transports_js_1.WebSocketMessageSpec; } });
|
|
70
|
+
Object.defineProperty(exports, "WebSocketNativeClientOptions", { enumerable: true, get: function () { return transports_js_1.WebSocketNativeClientOptions; } });
|
|
71
|
+
Object.defineProperty(exports, "WebSocketReconnectPolicy", { enumerable: true, get: function () { return transports_js_1.WebSocketReconnectPolicy; } });
|
|
55
72
|
Object.defineProperty(exports, "HttpOAuth2ClientCredentialsOptions", { enumerable: true, get: function () { return transports_js_1.HttpOAuth2ClientCredentialsOptions; } });
|
|
56
73
|
Object.defineProperty(exports, "HttpAuthOptions", { enumerable: true, get: function () { return transports_js_1.HttpAuthOptions; } });
|
|
57
74
|
var sinks_js_1 = require("./sinks.js");
|
|
58
75
|
Object.defineProperty(exports, "DatadogReportingSink", { enumerable: true, get: function () { return sinks_js_1.DatadogReportingSink; } });
|
|
59
76
|
Object.defineProperty(exports, "DatadogReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.DatadogReportingSinkOptions; } });
|
|
77
|
+
Object.defineProperty(exports, "DogStatsDReportingSink", { enumerable: true, get: function () { return sinks_js_1.DogStatsDReportingSink; } });
|
|
78
|
+
Object.defineProperty(exports, "CloudWatchReportingSink", { enumerable: true, get: function () { return sinks_js_1.CloudWatchReportingSink; } });
|
|
79
|
+
Object.defineProperty(exports, "DynatraceReportingSink", { enumerable: true, get: function () { return sinks_js_1.DynatraceReportingSink; } });
|
|
80
|
+
Object.defineProperty(exports, "ElasticsearchReportingSink", { enumerable: true, get: function () { return sinks_js_1.ElasticsearchReportingSink; } });
|
|
60
81
|
Object.defineProperty(exports, "GrafanaLokiReportingSink", { enumerable: true, get: function () { return sinks_js_1.GrafanaLokiReportingSink; } });
|
|
61
82
|
Object.defineProperty(exports, "GrafanaLokiReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.GrafanaLokiReportingSinkOptions; } });
|
|
83
|
+
Object.defineProperty(exports, "GenericWebhookReportingSink", { enumerable: true, get: function () { return sinks_js_1.GenericWebhookReportingSink; } });
|
|
62
84
|
Object.defineProperty(exports, "InfluxDbReportingSink", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSink; } });
|
|
63
85
|
Object.defineProperty(exports, "InfluxDbReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.InfluxDbReportingSinkOptions; } });
|
|
86
|
+
Object.defineProperty(exports, "JsonlFileReportingSink", { enumerable: true, get: function () { return sinks_js_1.JsonlFileReportingSink; } });
|
|
87
|
+
Object.defineProperty(exports, "KafkaReportingSink", { enumerable: true, get: function () { return sinks_js_1.KafkaReportingSink; } });
|
|
88
|
+
Object.defineProperty(exports, "NetdataStatsDReportingSink", { enumerable: true, get: function () { return sinks_js_1.NetdataStatsDReportingSink; } });
|
|
89
|
+
Object.defineProperty(exports, "NewRelicReportingSink", { enumerable: true, get: function () { return sinks_js_1.NewRelicReportingSink; } });
|
|
64
90
|
Object.defineProperty(exports, "OtelCollectorReportingSink", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSink; } });
|
|
65
91
|
Object.defineProperty(exports, "OtelCollectorReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.OtelCollectorReportingSinkOptions; } });
|
|
92
|
+
Object.defineProperty(exports, "OpenSearchReportingSink", { enumerable: true, get: function () { return sinks_js_1.OpenSearchReportingSink; } });
|
|
66
93
|
Object.defineProperty(exports, "PortalReportingSink", { enumerable: true, get: function () { return sinks_js_1.PortalReportingSink; } });
|
|
94
|
+
Object.defineProperty(exports, "PrometheusRemoteWriteReportingSink", { enumerable: true, get: function () { return sinks_js_1.PrometheusRemoteWriteReportingSink; } });
|
|
67
95
|
Object.defineProperty(exports, "SplunkReportingSink", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSink; } });
|
|
68
96
|
Object.defineProperty(exports, "SplunkReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.SplunkReportingSinkOptions; } });
|
|
97
|
+
Object.defineProperty(exports, "StatsDReportingSink", { enumerable: true, get: function () { return sinks_js_1.StatsDReportingSink; } });
|
|
69
98
|
Object.defineProperty(exports, "TimescaleDbReportingSink", { enumerable: true, get: function () { return sinks_js_1.TimescaleDbReportingSink; } });
|
|
70
99
|
Object.defineProperty(exports, "TimescaleDbReportingSinkOptions", { enumerable: true, get: function () { return sinks_js_1.TimescaleDbReportingSinkOptions; } });
|
package/dist/cjs/local.js
CHANGED
|
@@ -79,7 +79,9 @@ const TRACKING_FEATURE_BY_KIND = {
|
|
|
79
79
|
delegatestream: "endpoint.delegate_stream",
|
|
80
80
|
nats: "endpoint.nats",
|
|
81
81
|
redisstreams: "endpoint.redis_streams",
|
|
82
|
-
sqs: "endpoint.sqs"
|
|
82
|
+
sqs: "endpoint.sqs",
|
|
83
|
+
grpc: "endpoint.grpc",
|
|
84
|
+
websocket: "endpoint.websocket"
|
|
83
85
|
};
|
|
84
86
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
85
87
|
"GITHUB_ACTIONS",
|
|
@@ -205,7 +207,9 @@ class LoadStrikeLocalClient {
|
|
|
205
207
|
NodeType: stringOrDefault(context.NodeType, nodeType),
|
|
206
208
|
MachineName: machineName,
|
|
207
209
|
EnvironmentClassification: environmentClassification,
|
|
208
|
-
DeviceHash: computedDeviceHash
|
|
210
|
+
DeviceHash: computedDeviceHash,
|
|
211
|
+
Scenarios: buildLicenseScenarioMetadata(request),
|
|
212
|
+
ScenarioSourceAnalysis: buildScenarioSourceAnalysisMetadata(request)
|
|
209
213
|
};
|
|
210
214
|
const controller = new AbortController();
|
|
211
215
|
const timer = setTimeout(() => controller.abort(), this.licenseValidationTimeoutMs);
|
|
@@ -1576,6 +1580,74 @@ function computeScenarioRequestCount(scenario) {
|
|
|
1576
1580
|
}
|
|
1577
1581
|
return Math.max(total, 0);
|
|
1578
1582
|
}
|
|
1583
|
+
function buildLicenseScenarioMetadata(request) {
|
|
1584
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
1585
|
+
return scenarios.map((item) => {
|
|
1586
|
+
const scenario = asRecord(item);
|
|
1587
|
+
const scenarioName = stringOrDefault(scenario.ScenarioName ?? scenario.scenarioName ?? scenario.Name ?? scenario.name, "");
|
|
1588
|
+
return {
|
|
1589
|
+
Name: scenarioName,
|
|
1590
|
+
ScenarioName: scenarioName,
|
|
1591
|
+
Simulations: normalizeLicenseSimulations(scenario.LoadSimulations)
|
|
1592
|
+
};
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
function normalizeLicenseSimulations(value) {
|
|
1596
|
+
const simulations = Array.isArray(value) ? value : [];
|
|
1597
|
+
return simulations.map((item) => {
|
|
1598
|
+
const simulation = asRecord(item);
|
|
1599
|
+
return {
|
|
1600
|
+
Kind: stringOrDefault(simulation.Kind ?? simulation.kind, ""),
|
|
1601
|
+
Rate: positiveOrUndefined(simulation.Rate ?? simulation.rate),
|
|
1602
|
+
MinRate: positiveOrUndefined(simulation.MinRate ?? simulation.minRate),
|
|
1603
|
+
MaxRate: positiveOrUndefined(simulation.MaxRate ?? simulation.maxRate),
|
|
1604
|
+
Copies: positiveOrUndefined(simulation.Copies ?? simulation.copies),
|
|
1605
|
+
Iterations: positiveOrUndefined(simulation.Iterations ?? simulation.iterations),
|
|
1606
|
+
IntervalSeconds: positiveNumberOrUndefined(simulation.IntervalSeconds ?? simulation.intervalSeconds),
|
|
1607
|
+
DurationSeconds: positiveNumberOrUndefined(simulation.DurationSeconds ?? simulation.durationSeconds ?? simulation.DuringSeconds ?? simulation.duringSeconds),
|
|
1608
|
+
Children: normalizeLicenseSimulations(simulation.Children ?? simulation.children)
|
|
1609
|
+
};
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
function buildScenarioSourceAnalysisMetadata(request) {
|
|
1613
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
1614
|
+
return scenarios.map((item) => {
|
|
1615
|
+
const scenario = asRecord(item);
|
|
1616
|
+
const source = asRecord(scenario.ScenarioSourceAnalysis ?? scenario.scenarioSourceAnalysis ?? scenario.SourceAnalysis);
|
|
1617
|
+
const scenarioName = stringOrDefault(source.ScenarioName ?? source.scenarioName ?? scenario.Name ?? scenario.name, "");
|
|
1618
|
+
const scenarioBlockLineCount = Math.max(asInt(source.ScenarioBlockLineCount ?? source.scenarioBlockLineCount), 1);
|
|
1619
|
+
const localMethodLineCount = Math.max(asInt(source.LocalMethodLineCount
|
|
1620
|
+
?? source.localMethodLineCount
|
|
1621
|
+
?? source.TransitiveLocalMethodLineCount
|
|
1622
|
+
?? source.transitiveLocalMethodLineCount), 0);
|
|
1623
|
+
const totalCountedLineCount = Math.max(asInt(source.TotalCountedLineCount ?? source.totalCountedLineCount), scenarioBlockLineCount + localMethodLineCount);
|
|
1624
|
+
const warningsValue = source.Warnings ?? source.warnings;
|
|
1625
|
+
const warnings = Array.isArray(warningsValue)
|
|
1626
|
+
? warningsValue.map((warning) => stringOrDefault(warning, "")).filter((warning) => warning.length > 0)
|
|
1627
|
+
: ["Scenario source analysis was synthesized from scenario metadata."];
|
|
1628
|
+
return {
|
|
1629
|
+
ScenarioName: scenarioName,
|
|
1630
|
+
Language: stringOrDefault(source.Language ?? source.language, "TypeScript/JavaScript"),
|
|
1631
|
+
AnalyzerVersion: stringOrDefault(source.AnalyzerVersion ?? source.analyzerVersion, "function-source-v1"),
|
|
1632
|
+
ScenarioBlockLineCount: scenarioBlockLineCount,
|
|
1633
|
+
LocalMethodLineCount: localMethodLineCount,
|
|
1634
|
+
TotalCountedLineCount: totalCountedLineCount,
|
|
1635
|
+
IgnoredExternalCallCount: Math.max(asInt(source.IgnoredExternalCallCount
|
|
1636
|
+
?? source.ignoredExternalCallCount
|
|
1637
|
+
?? source.IgnoredLibraryCallCount
|
|
1638
|
+
?? source.ignoredLibraryCallCount), 0),
|
|
1639
|
+
Warnings: warnings
|
|
1640
|
+
};
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
function positiveOrUndefined(value) {
|
|
1644
|
+
const normalized = asInt(value);
|
|
1645
|
+
return normalized > 0 ? normalized : undefined;
|
|
1646
|
+
}
|
|
1647
|
+
function positiveNumberOrUndefined(value) {
|
|
1648
|
+
const normalized = asNumber(value);
|
|
1649
|
+
return normalized > 0 ? normalized : undefined;
|
|
1650
|
+
}
|
|
1579
1651
|
function buildNodeStatsContract(context, createdUtc, completedUtc, values) {
|
|
1580
1652
|
const durationMs = Math.max(completedUtc.getTime() - createdUtc.getTime(), 0);
|
|
1581
1653
|
return {
|
package/dist/cjs/runtime.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.__loadstrikeTestExports = exports.LoadStrikeRunner = exports.LoadStrikeTrafficMix = exports.LoadStrikeScenarioShare = exports.LoadStrikeScenario = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
|
|
6
|
+
exports.__loadstrikeTestExports = exports.LoadStrikeRunner = exports.LoadStrikeTrafficMix = exports.LoadStrikeScenarioShare = exports.LoadStrikeScenario = exports.LoadStrikeBrowserWebVitals = exports.LoadStrikeAccessibility = exports.LoadStrikeBrowserWebVitalsCollector = exports.LoadStrikeAccessibilityAdapters = exports.LoadStrikeAccessibilityScanner = exports.LoadStrikeAccessibilityCiGate = exports.LoadStrikeAccessibilityBaseline = exports.LoadStrikeContext = exports.ScenarioTrackingExtensions = exports.CrossPlatformScenarioConfigurator = exports.LoadStrikeThreshold = exports.LoadStrikeMetric = exports.LoadStrikeGauge = exports.LoadStrikeCounter = exports.LoadStrikeSimulation = exports.LoadStrikeStep = exports.LoadStrikeResponse = exports.LoadStrikePluginData = exports.LoadStrikePluginDataTable = exports.CrossPlatformTrackingConfiguration = exports.LoadStrikeOperationType = exports.LoadStrikeScenarioOperation = exports.LoadStrikeLogLevel = exports.LoadStrikeReportFormat = exports.LoadStrikeNodeType = void 0;
|
|
7
7
|
const node_fs_1 = require("node:fs");
|
|
8
8
|
const node_crypto_1 = require("node:crypto");
|
|
9
9
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -1622,6 +1622,405 @@ class LoadStrikeContext {
|
|
|
1622
1622
|
}
|
|
1623
1623
|
}
|
|
1624
1624
|
exports.LoadStrikeContext = LoadStrikeContext;
|
|
1625
|
+
const ACCESSIBILITY_TESTING_FEATURE = "testing.accessibility";
|
|
1626
|
+
const BROWSER_WEB_VITALS_FEATURE = "testing.browser_web_vitals";
|
|
1627
|
+
class LoadStrikeAccessibilityBaseline {
|
|
1628
|
+
constructor(initial = {}) {
|
|
1629
|
+
const source = qualityRecord(initial);
|
|
1630
|
+
this.allowedRuleIds = qualityStringArray(qualityValue(source, "allowedRuleIds", "AllowedRuleIds"));
|
|
1631
|
+
this.AllowedRuleIds = this.allowedRuleIds;
|
|
1632
|
+
this.maxNewViolations = Math.max(0, qualityNumber(source, 0, "maxNewViolations", "MaxNewViolations"));
|
|
1633
|
+
this.MaxNewViolations = this.maxNewViolations;
|
|
1634
|
+
}
|
|
1635
|
+
compare(result) {
|
|
1636
|
+
const violations = Array.isArray(result?.violations) ? result.violations : [];
|
|
1637
|
+
const allowed = new Set(this.allowedRuleIds.map((value) => value.toLowerCase()));
|
|
1638
|
+
const newViolations = violations.filter((violation) => !allowed.has(String(violation.ruleId ?? "").toLowerCase()));
|
|
1639
|
+
const newRuleIds = Array.from(new Set(newViolations
|
|
1640
|
+
.map((violation) => String(violation.ruleId ?? "").trim())
|
|
1641
|
+
.filter(Boolean)));
|
|
1642
|
+
return {
|
|
1643
|
+
passed: newViolations.length <= this.maxNewViolations,
|
|
1644
|
+
Passed: newViolations.length <= this.maxNewViolations,
|
|
1645
|
+
newViolationCount: newViolations.length,
|
|
1646
|
+
NewViolationCount: newViolations.length,
|
|
1647
|
+
newRuleIds,
|
|
1648
|
+
NewRuleIds: newRuleIds
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
Compare(result) {
|
|
1652
|
+
return this.compare(result);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
exports.LoadStrikeAccessibilityBaseline = LoadStrikeAccessibilityBaseline;
|
|
1656
|
+
class LoadStrikeAccessibilityCiGate {
|
|
1657
|
+
constructor(initial = {}) {
|
|
1658
|
+
const source = qualityRecord(initial);
|
|
1659
|
+
this.maxViolations = qualityOptionalNumber(source, "maxViolations", "MaxViolations");
|
|
1660
|
+
this.MaxViolations = this.maxViolations;
|
|
1661
|
+
this.maxCriticalViolations = qualityOptionalNumber(source, "maxCriticalViolations", "MaxCriticalViolations");
|
|
1662
|
+
this.MaxCriticalViolations = this.maxCriticalViolations;
|
|
1663
|
+
this.maxSeriousViolations = qualityOptionalNumber(source, "maxSeriousViolations", "MaxSeriousViolations");
|
|
1664
|
+
this.MaxSeriousViolations = this.maxSeriousViolations;
|
|
1665
|
+
const baseline = qualityValue(source, "baseline", "Baseline");
|
|
1666
|
+
this.baseline = baseline instanceof LoadStrikeAccessibilityBaseline
|
|
1667
|
+
? baseline
|
|
1668
|
+
: (baseline && typeof baseline === "object" ? new LoadStrikeAccessibilityBaseline(baseline) : undefined);
|
|
1669
|
+
this.Baseline = this.baseline;
|
|
1670
|
+
}
|
|
1671
|
+
evaluate(result) {
|
|
1672
|
+
const normalized = attachAccessibilityCounts(result);
|
|
1673
|
+
const reasons = [];
|
|
1674
|
+
if (this.maxViolations != null && (normalized.totalViolations ?? 0) > this.maxViolations) {
|
|
1675
|
+
reasons.push(`total accessibility violations ${normalized.totalViolations} exceeded ${this.maxViolations}.`);
|
|
1676
|
+
}
|
|
1677
|
+
if (this.maxCriticalViolations != null && (normalized.criticalViolations ?? 0) > this.maxCriticalViolations) {
|
|
1678
|
+
reasons.push(`critical accessibility violations ${normalized.criticalViolations} exceeded ${this.maxCriticalViolations}.`);
|
|
1679
|
+
}
|
|
1680
|
+
if (this.maxSeriousViolations != null && (normalized.seriousViolations ?? 0) > this.maxSeriousViolations) {
|
|
1681
|
+
reasons.push(`serious accessibility violations ${normalized.seriousViolations} exceeded ${this.maxSeriousViolations}.`);
|
|
1682
|
+
}
|
|
1683
|
+
if (this.baseline) {
|
|
1684
|
+
const baseline = this.baseline.compare(normalized);
|
|
1685
|
+
if (!baseline.passed) {
|
|
1686
|
+
reasons.push(`baseline found ${baseline.newViolationCount} new accessibility violation(s).`);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return {
|
|
1690
|
+
passed: reasons.length === 0,
|
|
1691
|
+
Passed: reasons.length === 0,
|
|
1692
|
+
reasons,
|
|
1693
|
+
Reasons: reasons
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
Evaluate(result) {
|
|
1697
|
+
return this.evaluate(result);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
exports.LoadStrikeAccessibilityCiGate = LoadStrikeAccessibilityCiGate;
|
|
1701
|
+
class LoadStrikeAccessibilityScanner {
|
|
1702
|
+
static scanHtml(options, html) {
|
|
1703
|
+
validateAbsoluteUrl(options?.url, "Accessibility check URL");
|
|
1704
|
+
const source = String(html ?? "");
|
|
1705
|
+
const violations = [];
|
|
1706
|
+
if (!/<html\b[^>]*\blang\s*=\s*["'][^"']+["']/i.test(source)) {
|
|
1707
|
+
violations.push(createAccessibilityViolation("html-has-lang", "serious", "The html element should declare a non-empty language."));
|
|
1708
|
+
}
|
|
1709
|
+
if (/<title\b[^>]*>\s*<\/title>/i.test(source) || !/<title\b/i.test(source)) {
|
|
1710
|
+
violations.push(createAccessibilityViolation("document-title", "serious", "The page should provide a useful title."));
|
|
1711
|
+
}
|
|
1712
|
+
for (const match of source.matchAll(/<img\b(?![^>]*\balt\s*=)[^>]*>/gi)) {
|
|
1713
|
+
violations.push(createAccessibilityViolation("image-alt", "serious", "Images should provide alternate text.", match[0]));
|
|
1714
|
+
}
|
|
1715
|
+
for (const match of source.matchAll(/<button\b(?![^>]*\baria-label\s*=)[^>]*>\s*<\/button>/gi)) {
|
|
1716
|
+
violations.push(createAccessibilityViolation("button-name", "critical", "Buttons should have an accessible name.", match[0]));
|
|
1717
|
+
}
|
|
1718
|
+
for (const match of source.matchAll(/<input\b(?![^>]*\baria-label\s*=)[^>]*\bid\s*=\s*["']?([a-zA-Z0-9_-]+)["']?[^>]*>/gi)) {
|
|
1719
|
+
const id = match[1] ?? "";
|
|
1720
|
+
const labelRegex = new RegExp(`<label\\b[^>]*\\bfor\\s*=\\s*["']?${escapeRegExp(id)}["']?[^>]*>`, "i");
|
|
1721
|
+
if (!labelRegex.test(source)) {
|
|
1722
|
+
violations.push(createAccessibilityViolation("label", "critical", "Form fields should have labels.", match[0]));
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
const focusOrder = [...source.matchAll(/<(a|button|input|select|textarea)\b[^>]*>/gi)]
|
|
1726
|
+
.map((match) => match[0]);
|
|
1727
|
+
const visibleFocusFailures = focusOrder.filter((element) => /outline\s*:\s*(none|0)\b/i.test(element)).length;
|
|
1728
|
+
const missingSkipLinkCount = /href\s*=\s*["']#(main|content)["']/i.test(source) ? 0 : 1;
|
|
1729
|
+
if (visibleFocusFailures > 0) {
|
|
1730
|
+
violations.push(createAccessibilityViolation("focus-visible", "serious", "Keyboard focus indicators should remain visible."));
|
|
1731
|
+
}
|
|
1732
|
+
if (missingSkipLinkCount > 0) {
|
|
1733
|
+
violations.push(createAccessibilityViolation("skip-link", "moderate", "Pages should provide a skip link to the main content."));
|
|
1734
|
+
}
|
|
1735
|
+
return attachAccessibilityCounts({
|
|
1736
|
+
url: options.url,
|
|
1737
|
+
violations,
|
|
1738
|
+
keyboardFocus: {
|
|
1739
|
+
tabbableElementCount: focusOrder.length,
|
|
1740
|
+
visibleFocusFailures,
|
|
1741
|
+
missingSkipLinkCount,
|
|
1742
|
+
focusOrder
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
static ScanHtml(options, html) {
|
|
1747
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
exports.LoadStrikeAccessibilityScanner = LoadStrikeAccessibilityScanner;
|
|
1751
|
+
class LoadStrikeAccessibilityAdapters {
|
|
1752
|
+
static async fromPlaywrightPageAsync(options, page) {
|
|
1753
|
+
const html = await readBrowserContent(page, ["ContentAsync", "contentAsync", "content"]);
|
|
1754
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1755
|
+
}
|
|
1756
|
+
static FromPlaywrightPageAsync(options, page) {
|
|
1757
|
+
return LoadStrikeAccessibilityAdapters.fromPlaywrightPageAsync(options, page);
|
|
1758
|
+
}
|
|
1759
|
+
static fromSeleniumDriver(options, driver) {
|
|
1760
|
+
const record = qualityRecord(driver);
|
|
1761
|
+
const html = String(qualityValue(record, "PageSource", "pageSource") ?? "");
|
|
1762
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1763
|
+
}
|
|
1764
|
+
static FromSeleniumDriver(options, driver) {
|
|
1765
|
+
return LoadStrikeAccessibilityAdapters.fromSeleniumDriver(options, driver);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
exports.LoadStrikeAccessibilityAdapters = LoadStrikeAccessibilityAdapters;
|
|
1769
|
+
class LoadStrikeBrowserWebVitalsCollector {
|
|
1770
|
+
static fromPerformanceSnapshot(options, snapshot) {
|
|
1771
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1772
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1773
|
+
}
|
|
1774
|
+
static FromPerformanceSnapshot(options, snapshot) {
|
|
1775
|
+
return LoadStrikeBrowserWebVitalsCollector.fromPerformanceSnapshot(options, snapshot);
|
|
1776
|
+
}
|
|
1777
|
+
static async fromPlaywrightPageAsync(options, page) {
|
|
1778
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1779
|
+
const snapshot = await invokeBrowserMethod(page, ["EvaluateAsync", "evaluate"], WEB_VITALS_SCRIPT);
|
|
1780
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1781
|
+
}
|
|
1782
|
+
static FromPlaywrightPageAsync(options, page) {
|
|
1783
|
+
return LoadStrikeBrowserWebVitalsCollector.fromPlaywrightPageAsync(options, page);
|
|
1784
|
+
}
|
|
1785
|
+
static fromSeleniumDriver(options, driver) {
|
|
1786
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1787
|
+
const snapshot = invokeBrowserMethodSync(driver, ["ExecuteScript", "executeScript"], WEB_VITALS_SCRIPT);
|
|
1788
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1789
|
+
}
|
|
1790
|
+
static FromSeleniumDriver(options, driver) {
|
|
1791
|
+
return LoadStrikeBrowserWebVitalsCollector.fromSeleniumDriver(options, driver);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
exports.LoadStrikeBrowserWebVitalsCollector = LoadStrikeBrowserWebVitalsCollector;
|
|
1795
|
+
class LoadStrikeAccessibility {
|
|
1796
|
+
static createScenario(name, options, check) {
|
|
1797
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
1798
|
+
throw new Error("Scenario name must be provided.");
|
|
1799
|
+
}
|
|
1800
|
+
validateAbsoluteUrl(options?.url, "Accessibility check URL");
|
|
1801
|
+
if (typeof check !== "function") {
|
|
1802
|
+
throw new TypeError("Accessibility check callback must be provided.");
|
|
1803
|
+
}
|
|
1804
|
+
return LoadStrikeScenario
|
|
1805
|
+
.create(name, async (scenarioContext) => {
|
|
1806
|
+
const result = await check({ options, scenarioContext });
|
|
1807
|
+
const failure = evaluateAccessibilityResult(options, result);
|
|
1808
|
+
return failure
|
|
1809
|
+
? LoadStrikeResponse.fail("accessibility", failure)
|
|
1810
|
+
: LoadStrikeResponse.ok("200", 0, `Accessibility check passed with ${(result.violations ?? []).length} violation(s).`);
|
|
1811
|
+
})
|
|
1812
|
+
.withoutWarmUp()
|
|
1813
|
+
.__loadStrikeWithInternalLicenseFeatures(ACCESSIBILITY_TESTING_FEATURE);
|
|
1814
|
+
}
|
|
1815
|
+
static CreateScenario(name, options, check) {
|
|
1816
|
+
return LoadStrikeAccessibility.createScenario(name, options, check);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
exports.LoadStrikeAccessibility = LoadStrikeAccessibility;
|
|
1820
|
+
class LoadStrikeBrowserWebVitals {
|
|
1821
|
+
static createScenario(name, options, measure) {
|
|
1822
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
1823
|
+
throw new Error("Scenario name must be provided.");
|
|
1824
|
+
}
|
|
1825
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1826
|
+
if (typeof measure !== "function") {
|
|
1827
|
+
throw new TypeError("Browser Web Vitals measurement callback must be provided.");
|
|
1828
|
+
}
|
|
1829
|
+
return LoadStrikeScenario
|
|
1830
|
+
.create(name, async (scenarioContext) => {
|
|
1831
|
+
const result = await measure({ options, scenarioContext });
|
|
1832
|
+
const failure = evaluateWebVitalsResult(options, result);
|
|
1833
|
+
return failure
|
|
1834
|
+
? LoadStrikeResponse.fail("web_vitals", failure)
|
|
1835
|
+
: LoadStrikeResponse.ok("200", 0, "Browser Web Vitals check passed.");
|
|
1836
|
+
})
|
|
1837
|
+
.withoutWarmUp()
|
|
1838
|
+
.__loadStrikeWithInternalLicenseFeatures(BROWSER_WEB_VITALS_FEATURE);
|
|
1839
|
+
}
|
|
1840
|
+
static CreateScenario(name, options, measure) {
|
|
1841
|
+
return LoadStrikeBrowserWebVitals.createScenario(name, options, measure);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
exports.LoadStrikeBrowserWebVitals = LoadStrikeBrowserWebVitals;
|
|
1845
|
+
const WEB_VITALS_SCRIPT = `
|
|
1846
|
+
(() => {
|
|
1847
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
1848
|
+
const paint = performance.getEntriesByType("paint");
|
|
1849
|
+
const fcp = paint.find((entry) => entry.name === "first-contentful-paint");
|
|
1850
|
+
return {
|
|
1851
|
+
largestContentfulPaintMs: globalThis.__loadstrikeLcp ?? 0,
|
|
1852
|
+
interactionToNextPaintMs: globalThis.__loadstrikeInp ?? 0,
|
|
1853
|
+
cumulativeLayoutShift: globalThis.__loadstrikeCls ?? 0,
|
|
1854
|
+
firstContentfulPaintMs: fcp ? fcp.startTime : 0,
|
|
1855
|
+
timeToFirstByteMs: navigation ? navigation.responseStart : 0
|
|
1856
|
+
};
|
|
1857
|
+
})()
|
|
1858
|
+
`;
|
|
1859
|
+
function createAccessibilityViolation(ruleId, impact, description, target) {
|
|
1860
|
+
return {
|
|
1861
|
+
ruleId,
|
|
1862
|
+
impact,
|
|
1863
|
+
description,
|
|
1864
|
+
helpUrl: `https://www.w3.org/WAI/WCAG22/quickref/?versions=2.2#${ruleId}`,
|
|
1865
|
+
targets: target ? [target] : []
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function attachAccessibilityCounts(result) {
|
|
1869
|
+
const violations = Array.isArray(result?.violations) ? result.violations : [];
|
|
1870
|
+
return {
|
|
1871
|
+
...result,
|
|
1872
|
+
violations,
|
|
1873
|
+
totalViolations: violations.length,
|
|
1874
|
+
criticalViolations: countAccessibilityImpact(violations, "critical"),
|
|
1875
|
+
seriousViolations: countAccessibilityImpact(violations, "serious"),
|
|
1876
|
+
moderateViolations: countAccessibilityImpact(violations, "moderate"),
|
|
1877
|
+
minorViolations: countAccessibilityImpact(violations, "minor")
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
function qualityRecord(value) {
|
|
1881
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
1882
|
+
? value
|
|
1883
|
+
: {};
|
|
1884
|
+
}
|
|
1885
|
+
function qualityValue(record, ...keys) {
|
|
1886
|
+
for (const key of keys) {
|
|
1887
|
+
if (key in record) {
|
|
1888
|
+
return record[key];
|
|
1889
|
+
}
|
|
1890
|
+
const normalized = key.toLowerCase();
|
|
1891
|
+
const match = Object.entries(record).find(([entryKey]) => entryKey.toLowerCase() === normalized);
|
|
1892
|
+
if (match) {
|
|
1893
|
+
return match[1];
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return undefined;
|
|
1897
|
+
}
|
|
1898
|
+
function qualityStringArray(value) {
|
|
1899
|
+
if (!Array.isArray(value)) {
|
|
1900
|
+
return [];
|
|
1901
|
+
}
|
|
1902
|
+
return value.map((entry) => String(entry ?? "").trim()).filter(Boolean);
|
|
1903
|
+
}
|
|
1904
|
+
function qualityNumber(record, fallback, ...keys) {
|
|
1905
|
+
const value = qualityValue(record, ...keys);
|
|
1906
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1907
|
+
return value;
|
|
1908
|
+
}
|
|
1909
|
+
if (typeof value === "string" && value.trim()) {
|
|
1910
|
+
const parsed = Number(value);
|
|
1911
|
+
if (Number.isFinite(parsed)) {
|
|
1912
|
+
return parsed;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return fallback;
|
|
1916
|
+
}
|
|
1917
|
+
function qualityOptionalNumber(record, ...keys) {
|
|
1918
|
+
const value = qualityValue(record, ...keys);
|
|
1919
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1920
|
+
return value;
|
|
1921
|
+
}
|
|
1922
|
+
if (typeof value === "string" && value.trim()) {
|
|
1923
|
+
const parsed = Number(value);
|
|
1924
|
+
if (Number.isFinite(parsed)) {
|
|
1925
|
+
return parsed;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1930
|
+
function escapeRegExp(value) {
|
|
1931
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1932
|
+
}
|
|
1933
|
+
async function readBrowserContent(target, methodNames) {
|
|
1934
|
+
const value = await invokeBrowserMethod(target, methodNames);
|
|
1935
|
+
return String(value ?? "");
|
|
1936
|
+
}
|
|
1937
|
+
async function invokeBrowserMethod(target, methodNames, ...args) {
|
|
1938
|
+
const record = qualityRecord(target);
|
|
1939
|
+
for (const name of methodNames) {
|
|
1940
|
+
const method = record[name];
|
|
1941
|
+
if (typeof method === "function") {
|
|
1942
|
+
return await method.apply(target, args);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
throw new Error(`Browser object does not expose ${methodNames.join(" or ")}.`);
|
|
1946
|
+
}
|
|
1947
|
+
function invokeBrowserMethodSync(target, methodNames, ...args) {
|
|
1948
|
+
const record = qualityRecord(target);
|
|
1949
|
+
for (const name of methodNames) {
|
|
1950
|
+
const method = record[name];
|
|
1951
|
+
if (typeof method === "function") {
|
|
1952
|
+
return method.apply(target, args);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
throw new Error(`Browser object does not expose ${methodNames.join(" or ")}.`);
|
|
1956
|
+
}
|
|
1957
|
+
function normalizeWebVitalsSnapshot(url, snapshot) {
|
|
1958
|
+
const source = qualityRecord(snapshot);
|
|
1959
|
+
return {
|
|
1960
|
+
url,
|
|
1961
|
+
largestContentfulPaintMs: qualityNumber(source, 0, "largestContentfulPaintMs", "LargestContentfulPaintMs", "LCP", "lcp"),
|
|
1962
|
+
interactionToNextPaintMs: qualityNumber(source, 0, "interactionToNextPaintMs", "InteractionToNextPaintMs", "INP", "inp"),
|
|
1963
|
+
cumulativeLayoutShift: qualityNumber(source, 0, "cumulativeLayoutShift", "CumulativeLayoutShift", "CLS", "cls"),
|
|
1964
|
+
firstContentfulPaintMs: qualityNumber(source, 0, "firstContentfulPaintMs", "FirstContentfulPaintMs", "FCP", "fcp"),
|
|
1965
|
+
timeToFirstByteMs: qualityNumber(source, 0, "timeToFirstByteMs", "TimeToFirstByteMs", "TTFB", "ttfb")
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
function validateAbsoluteUrl(value, label) {
|
|
1969
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
1970
|
+
throw new Error(`${label} must be provided.`);
|
|
1971
|
+
}
|
|
1972
|
+
try {
|
|
1973
|
+
const parsed = new URL(value);
|
|
1974
|
+
if (!parsed.protocol || !parsed.host) {
|
|
1975
|
+
throw new Error("invalid");
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
catch {
|
|
1979
|
+
throw new Error(`${label} must be an absolute URL.`);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
function evaluateAccessibilityResult(options, result) {
|
|
1983
|
+
if (!result) {
|
|
1984
|
+
return "Accessibility check did not return a result.";
|
|
1985
|
+
}
|
|
1986
|
+
const violations = Array.isArray(result.violations) ? result.violations : [];
|
|
1987
|
+
const maxViolations = Math.max(0, Number(options.maxViolations ?? 0));
|
|
1988
|
+
const critical = countAccessibilityImpact(violations, "critical");
|
|
1989
|
+
const serious = countAccessibilityImpact(violations, "serious");
|
|
1990
|
+
const maxCritical = Math.max(0, Number(options.maxCriticalViolations ?? 0));
|
|
1991
|
+
const maxSerious = Math.max(0, Number(options.maxSeriousViolations ?? 0));
|
|
1992
|
+
if (violations.length > maxViolations) {
|
|
1993
|
+
return `Accessibility violations ${violations.length} exceeded limit ${maxViolations}.`;
|
|
1994
|
+
}
|
|
1995
|
+
if (critical > maxCritical) {
|
|
1996
|
+
return `Critical accessibility violations ${critical} exceeded limit ${maxCritical}.`;
|
|
1997
|
+
}
|
|
1998
|
+
if (serious > maxSerious) {
|
|
1999
|
+
return `Serious accessibility violations ${serious} exceeded limit ${maxSerious}.`;
|
|
2000
|
+
}
|
|
2001
|
+
return "";
|
|
2002
|
+
}
|
|
2003
|
+
function countAccessibilityImpact(violations, impact) {
|
|
2004
|
+
return violations.filter((violation) => String(violation.impact ?? "").toLowerCase() === impact).length;
|
|
2005
|
+
}
|
|
2006
|
+
function evaluateWebVitalsResult(options, result) {
|
|
2007
|
+
if (!result) {
|
|
2008
|
+
return "Browser Web Vitals check did not return a result.";
|
|
2009
|
+
}
|
|
2010
|
+
return firstWebVitalViolation(["LCP", result.largestContentfulPaintMs, options.maxLargestContentfulPaintMs, "ms"], ["INP", result.interactionToNextPaintMs, options.maxInteractionToNextPaintMs, "ms"], ["CLS", result.cumulativeLayoutShift, options.maxCumulativeLayoutShift, ""], ["FCP", result.firstContentfulPaintMs, options.maxFirstContentfulPaintMs, "ms"], ["TTFB", result.timeToFirstByteMs, options.maxTimeToFirstByteMs, "ms"]);
|
|
2011
|
+
}
|
|
2012
|
+
function firstWebVitalViolation(...values) {
|
|
2013
|
+
for (const [name, actualRaw, limitRaw, unit] of values) {
|
|
2014
|
+
const actual = Number(actualRaw);
|
|
2015
|
+
const limit = Number(limitRaw);
|
|
2016
|
+
if (!Number.isFinite(actual) || !Number.isFinite(limit) || actual <= limit) {
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
const suffix = unit ? ` ${unit}` : "";
|
|
2020
|
+
return `${name} ${actual}${suffix} exceeded limit ${limit}${suffix}.`;
|
|
2021
|
+
}
|
|
2022
|
+
return "";
|
|
2023
|
+
}
|
|
1625
2024
|
class LoadStrikeScenario {
|
|
1626
2025
|
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
|
|
1627
2026
|
this.name = name;
|
|
@@ -1856,6 +2255,26 @@ class LoadStrikeScenario {
|
|
|
1856
2255
|
__loadStrikeInternalLicenseFeatures() {
|
|
1857
2256
|
return [...this.internalLicenseFeatures];
|
|
1858
2257
|
}
|
|
2258
|
+
__loadStrikeScenarioSourceAnalysis() {
|
|
2259
|
+
const source = this.runHandler.toString();
|
|
2260
|
+
const lines = source
|
|
2261
|
+
.split(/\r?\n/)
|
|
2262
|
+
.map((line) => line.trim())
|
|
2263
|
+
.filter((line) => line.length > 0 && !line.startsWith("//"));
|
|
2264
|
+
const lineCount = Math.max(lines.length, 1);
|
|
2265
|
+
return {
|
|
2266
|
+
ScenarioName: this.name,
|
|
2267
|
+
Language: "TypeScript/JavaScript",
|
|
2268
|
+
AnalyzerVersion: "function-source-v1",
|
|
2269
|
+
ScenarioBlockLineCount: lineCount,
|
|
2270
|
+
LocalMethodLineCount: 0,
|
|
2271
|
+
TotalCountedLineCount: lineCount,
|
|
2272
|
+
IgnoredExternalCallCount: 0,
|
|
2273
|
+
Warnings: [
|
|
2274
|
+
"Function-source analysis counts the scenario handler source available at runtime; imported library implementation lines are ignored."
|
|
2275
|
+
]
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
1859
2278
|
__loadStrikeWithInternalLicenseFeatures(...features) {
|
|
1860
2279
|
const merged = Array.from(new Set([...this.internalLicenseFeatures, ...normalizeStringArray(features)]));
|
|
1861
2280
|
return new LoadStrikeScenario(this.name, this.runHandler, this.initHandler, this.cleanHandler, this.loadSimulations, this.thresholds, this.trackingConfiguration, this.maxFailCount, this.withoutWarmUpValue, this.warmUpDurationSeconds, this.weight, this.restartIterationOnFail, merged);
|
|
@@ -7953,7 +8372,8 @@ function buildLicenseValidationPayload(options, scenarios) {
|
|
|
7953
8372
|
Weight: scenario.getWeight(),
|
|
7954
8373
|
LoadSimulations: [...scenario.getSimulations()],
|
|
7955
8374
|
Thresholds: [...scenario.getThresholds()],
|
|
7956
|
-
Tracking: scenario.getTrackingConfiguration() ?? {}
|
|
8375
|
+
Tracking: scenario.getTrackingConfiguration() ?? {},
|
|
8376
|
+
ScenarioSourceAnalysis: scenario.__loadStrikeScenarioSourceAnalysis()
|
|
7957
8377
|
};
|
|
7958
8378
|
const internalLicenseFeatures = scenario.__loadStrikeInternalLicenseFeatures();
|
|
7959
8379
|
if (internalLicenseFeatures.length > 0) {
|