@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/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { LoadStrikeAutopilot, LoadStrikeAutopilotResult } from "./autopilot.js";
|
|
2
2
|
export { LoadStrikeAutopilotReadiness } from "./autopilot-contracts.js";
|
|
3
|
-
export { CrossPlatformScenarioConfigurator, ScenarioTrackingExtensions, LoadStrikeContext, LoadStrikePluginData, LoadStrikePluginDataTable, LoadStrikeNodeType, LoadStrikeReportFormat, LoadStrikeResponse, LoadStrikeLogLevel, LoadStrikeScenarioOperation, LoadStrikeOperationType, LoadStrikeRunner, LoadStrikeScenario, LoadStrikeScenarioShare, LoadStrikeSimulation, LoadStrikeTrafficMix, CrossPlatformTrackingConfiguration, LoadStrikeMetric, LoadStrikeCounter, LoadStrikeGauge, LoadStrikeStep, LoadStrikeThreshold } from "./runtime.js";
|
|
3
|
+
export { CrossPlatformScenarioConfigurator, ScenarioTrackingExtensions, LoadStrikeContext, LoadStrikeAccessibility, LoadStrikeAccessibilityAdapters, LoadStrikeAccessibilityBaseline, LoadStrikeAccessibilityCiGate, LoadStrikeAccessibilityScanner, LoadStrikeBrowserWebVitals, LoadStrikeBrowserWebVitalsCollector, LoadStrikePluginData, LoadStrikePluginDataTable, LoadStrikeNodeType, LoadStrikeReportFormat, LoadStrikeResponse, LoadStrikeLogLevel, LoadStrikeScenarioOperation, LoadStrikeOperationType, LoadStrikeRunner, LoadStrikeScenario, LoadStrikeScenarioShare, LoadStrikeSimulation, LoadStrikeTrafficMix, CrossPlatformTrackingConfiguration, LoadStrikeMetric, LoadStrikeCounter, LoadStrikeGauge, LoadStrikeStep, LoadStrikeThreshold } from "./runtime.js";
|
|
4
4
|
export { CorrelationStoreConfiguration, CrossPlatformTrackingRuntime, InMemoryCorrelationStore, RedisCorrelationStoreOptions, RedisCorrelationStore, TrackingPayloadBuilder, TrackingFieldSelector } from "./correlation.js";
|
|
5
|
-
export { LOADSTRIKE_TRACE_ID_HEADER, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, TrafficEndpointDefinition, HttpEndpointDefinition, KafkaEndpointDefinition, KafkaSaslOptions, RabbitMqEndpointDefinition, NatsEndpointDefinition, RedisStreamsEndpointDefinition, AzureEventHubsEndpointDefinition, SqsEndpointDefinition, DelegateStreamEndpointDefinition, PushDiffusionEndpointDefinition, HttpOAuth2ClientCredentialsOptions, HttpAuthOptions } from "./transports.js";
|
|
6
|
-
export { DatadogReportingSink, DatadogReportingSinkOptions, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, InfluxDbReportingSink, InfluxDbReportingSinkOptions, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, PortalReportingSink, SplunkReportingSink, SplunkReportingSinkOptions, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
|
5
|
+
export { LOADSTRIKE_TRACE_ID_HEADER, LOADSTRIKE_TRACE_ID_TRACKING_FIELD, TrafficEndpointDefinition, HttpEndpointDefinition, KafkaEndpointDefinition, KafkaSaslOptions, RabbitMqEndpointDefinition, NatsEndpointDefinition, RedisStreamsEndpointDefinition, AzureEventHubsEndpointDefinition, SqsEndpointDefinition, DelegateStreamEndpointDefinition, PushDiffusionEndpointDefinition, GrpcEndpointDefinition, GrpcMethodTypes, GrpcNativeClientOptions, GrpcStatusMapper, ProtocolMetricSnapshot, WebSocketEndpointDefinition, WebSocketExpectedMessage, WebSocketMessageSpec, WebSocketNativeClientOptions, WebSocketReconnectPolicy, HttpOAuth2ClientCredentialsOptions, HttpAuthOptions } from "./transports.js";
|
|
6
|
+
export { DatadogReportingSink, DatadogReportingSinkOptions, DogStatsDReportingSink, CloudWatchReportingSink, DynatraceReportingSink, ElasticsearchReportingSink, GrafanaLokiReportingSink, GrafanaLokiReportingSinkOptions, GenericWebhookReportingSink, InfluxDbReportingSink, InfluxDbReportingSinkOptions, JsonlFileReportingSink, KafkaReportingSink, NetdataStatsDReportingSink, NewRelicReportingSink, OtelCollectorReportingSink, OtelCollectorReportingSinkOptions, OpenSearchReportingSink, PortalReportingSink, PrometheusRemoteWriteReportingSink, SplunkReportingSink, SplunkReportingSinkOptions, StatsDReportingSink, TimescaleDbReportingSink, TimescaleDbReportingSinkOptions } from "./sinks.js";
|
package/dist/esm/local.js
CHANGED
|
@@ -40,7 +40,9 @@ const TRACKING_FEATURE_BY_KIND = {
|
|
|
40
40
|
delegatestream: "endpoint.delegate_stream",
|
|
41
41
|
nats: "endpoint.nats",
|
|
42
42
|
redisstreams: "endpoint.redis_streams",
|
|
43
|
-
sqs: "endpoint.sqs"
|
|
43
|
+
sqs: "endpoint.sqs",
|
|
44
|
+
grpc: "endpoint.grpc",
|
|
45
|
+
websocket: "endpoint.websocket"
|
|
44
46
|
};
|
|
45
47
|
const CI_ENVIRONMENT_VARIABLES = [
|
|
46
48
|
"GITHUB_ACTIONS",
|
|
@@ -166,7 +168,9 @@ export class LoadStrikeLocalClient {
|
|
|
166
168
|
NodeType: stringOrDefault(context.NodeType, nodeType),
|
|
167
169
|
MachineName: machineName,
|
|
168
170
|
EnvironmentClassification: environmentClassification,
|
|
169
|
-
DeviceHash: computedDeviceHash
|
|
171
|
+
DeviceHash: computedDeviceHash,
|
|
172
|
+
Scenarios: buildLicenseScenarioMetadata(request),
|
|
173
|
+
ScenarioSourceAnalysis: buildScenarioSourceAnalysisMetadata(request)
|
|
170
174
|
};
|
|
171
175
|
const controller = new AbortController();
|
|
172
176
|
const timer = setTimeout(() => controller.abort(), this.licenseValidationTimeoutMs);
|
|
@@ -1536,6 +1540,74 @@ function computeScenarioRequestCount(scenario) {
|
|
|
1536
1540
|
}
|
|
1537
1541
|
return Math.max(total, 0);
|
|
1538
1542
|
}
|
|
1543
|
+
function buildLicenseScenarioMetadata(request) {
|
|
1544
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
1545
|
+
return scenarios.map((item) => {
|
|
1546
|
+
const scenario = asRecord(item);
|
|
1547
|
+
const scenarioName = stringOrDefault(scenario.ScenarioName ?? scenario.scenarioName ?? scenario.Name ?? scenario.name, "");
|
|
1548
|
+
return {
|
|
1549
|
+
Name: scenarioName,
|
|
1550
|
+
ScenarioName: scenarioName,
|
|
1551
|
+
Simulations: normalizeLicenseSimulations(scenario.LoadSimulations)
|
|
1552
|
+
};
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
function normalizeLicenseSimulations(value) {
|
|
1556
|
+
const simulations = Array.isArray(value) ? value : [];
|
|
1557
|
+
return simulations.map((item) => {
|
|
1558
|
+
const simulation = asRecord(item);
|
|
1559
|
+
return {
|
|
1560
|
+
Kind: stringOrDefault(simulation.Kind ?? simulation.kind, ""),
|
|
1561
|
+
Rate: positiveOrUndefined(simulation.Rate ?? simulation.rate),
|
|
1562
|
+
MinRate: positiveOrUndefined(simulation.MinRate ?? simulation.minRate),
|
|
1563
|
+
MaxRate: positiveOrUndefined(simulation.MaxRate ?? simulation.maxRate),
|
|
1564
|
+
Copies: positiveOrUndefined(simulation.Copies ?? simulation.copies),
|
|
1565
|
+
Iterations: positiveOrUndefined(simulation.Iterations ?? simulation.iterations),
|
|
1566
|
+
IntervalSeconds: positiveNumberOrUndefined(simulation.IntervalSeconds ?? simulation.intervalSeconds),
|
|
1567
|
+
DurationSeconds: positiveNumberOrUndefined(simulation.DurationSeconds ?? simulation.durationSeconds ?? simulation.DuringSeconds ?? simulation.duringSeconds),
|
|
1568
|
+
Children: normalizeLicenseSimulations(simulation.Children ?? simulation.children)
|
|
1569
|
+
};
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
function buildScenarioSourceAnalysisMetadata(request) {
|
|
1573
|
+
const scenarios = Array.isArray(request.Scenarios) ? request.Scenarios : [];
|
|
1574
|
+
return scenarios.map((item) => {
|
|
1575
|
+
const scenario = asRecord(item);
|
|
1576
|
+
const source = asRecord(scenario.ScenarioSourceAnalysis ?? scenario.scenarioSourceAnalysis ?? scenario.SourceAnalysis);
|
|
1577
|
+
const scenarioName = stringOrDefault(source.ScenarioName ?? source.scenarioName ?? scenario.Name ?? scenario.name, "");
|
|
1578
|
+
const scenarioBlockLineCount = Math.max(asInt(source.ScenarioBlockLineCount ?? source.scenarioBlockLineCount), 1);
|
|
1579
|
+
const localMethodLineCount = Math.max(asInt(source.LocalMethodLineCount
|
|
1580
|
+
?? source.localMethodLineCount
|
|
1581
|
+
?? source.TransitiveLocalMethodLineCount
|
|
1582
|
+
?? source.transitiveLocalMethodLineCount), 0);
|
|
1583
|
+
const totalCountedLineCount = Math.max(asInt(source.TotalCountedLineCount ?? source.totalCountedLineCount), scenarioBlockLineCount + localMethodLineCount);
|
|
1584
|
+
const warningsValue = source.Warnings ?? source.warnings;
|
|
1585
|
+
const warnings = Array.isArray(warningsValue)
|
|
1586
|
+
? warningsValue.map((warning) => stringOrDefault(warning, "")).filter((warning) => warning.length > 0)
|
|
1587
|
+
: ["Scenario source analysis was synthesized from scenario metadata."];
|
|
1588
|
+
return {
|
|
1589
|
+
ScenarioName: scenarioName,
|
|
1590
|
+
Language: stringOrDefault(source.Language ?? source.language, "TypeScript/JavaScript"),
|
|
1591
|
+
AnalyzerVersion: stringOrDefault(source.AnalyzerVersion ?? source.analyzerVersion, "function-source-v1"),
|
|
1592
|
+
ScenarioBlockLineCount: scenarioBlockLineCount,
|
|
1593
|
+
LocalMethodLineCount: localMethodLineCount,
|
|
1594
|
+
TotalCountedLineCount: totalCountedLineCount,
|
|
1595
|
+
IgnoredExternalCallCount: Math.max(asInt(source.IgnoredExternalCallCount
|
|
1596
|
+
?? source.ignoredExternalCallCount
|
|
1597
|
+
?? source.IgnoredLibraryCallCount
|
|
1598
|
+
?? source.ignoredLibraryCallCount), 0),
|
|
1599
|
+
Warnings: warnings
|
|
1600
|
+
};
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
function positiveOrUndefined(value) {
|
|
1604
|
+
const normalized = asInt(value);
|
|
1605
|
+
return normalized > 0 ? normalized : undefined;
|
|
1606
|
+
}
|
|
1607
|
+
function positiveNumberOrUndefined(value) {
|
|
1608
|
+
const normalized = asNumber(value);
|
|
1609
|
+
return normalized > 0 ? normalized : undefined;
|
|
1610
|
+
}
|
|
1539
1611
|
function buildNodeStatsContract(context, createdUtc, completedUtc, values) {
|
|
1540
1612
|
const durationMs = Math.max(completedUtc.getTime() - createdUtc.getTime(), 0);
|
|
1541
1613
|
return {
|
package/dist/esm/runtime.js
CHANGED
|
@@ -1604,6 +1604,398 @@ export class LoadStrikeContext {
|
|
|
1604
1604
|
return this.assignState(this.values, scenarios, this.runArgs);
|
|
1605
1605
|
}
|
|
1606
1606
|
}
|
|
1607
|
+
const ACCESSIBILITY_TESTING_FEATURE = "testing.accessibility";
|
|
1608
|
+
const BROWSER_WEB_VITALS_FEATURE = "testing.browser_web_vitals";
|
|
1609
|
+
export class LoadStrikeAccessibilityBaseline {
|
|
1610
|
+
constructor(initial = {}) {
|
|
1611
|
+
const source = qualityRecord(initial);
|
|
1612
|
+
this.allowedRuleIds = qualityStringArray(qualityValue(source, "allowedRuleIds", "AllowedRuleIds"));
|
|
1613
|
+
this.AllowedRuleIds = this.allowedRuleIds;
|
|
1614
|
+
this.maxNewViolations = Math.max(0, qualityNumber(source, 0, "maxNewViolations", "MaxNewViolations"));
|
|
1615
|
+
this.MaxNewViolations = this.maxNewViolations;
|
|
1616
|
+
}
|
|
1617
|
+
compare(result) {
|
|
1618
|
+
const violations = Array.isArray(result?.violations) ? result.violations : [];
|
|
1619
|
+
const allowed = new Set(this.allowedRuleIds.map((value) => value.toLowerCase()));
|
|
1620
|
+
const newViolations = violations.filter((violation) => !allowed.has(String(violation.ruleId ?? "").toLowerCase()));
|
|
1621
|
+
const newRuleIds = Array.from(new Set(newViolations
|
|
1622
|
+
.map((violation) => String(violation.ruleId ?? "").trim())
|
|
1623
|
+
.filter(Boolean)));
|
|
1624
|
+
return {
|
|
1625
|
+
passed: newViolations.length <= this.maxNewViolations,
|
|
1626
|
+
Passed: newViolations.length <= this.maxNewViolations,
|
|
1627
|
+
newViolationCount: newViolations.length,
|
|
1628
|
+
NewViolationCount: newViolations.length,
|
|
1629
|
+
newRuleIds,
|
|
1630
|
+
NewRuleIds: newRuleIds
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
Compare(result) {
|
|
1634
|
+
return this.compare(result);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
export class LoadStrikeAccessibilityCiGate {
|
|
1638
|
+
constructor(initial = {}) {
|
|
1639
|
+
const source = qualityRecord(initial);
|
|
1640
|
+
this.maxViolations = qualityOptionalNumber(source, "maxViolations", "MaxViolations");
|
|
1641
|
+
this.MaxViolations = this.maxViolations;
|
|
1642
|
+
this.maxCriticalViolations = qualityOptionalNumber(source, "maxCriticalViolations", "MaxCriticalViolations");
|
|
1643
|
+
this.MaxCriticalViolations = this.maxCriticalViolations;
|
|
1644
|
+
this.maxSeriousViolations = qualityOptionalNumber(source, "maxSeriousViolations", "MaxSeriousViolations");
|
|
1645
|
+
this.MaxSeriousViolations = this.maxSeriousViolations;
|
|
1646
|
+
const baseline = qualityValue(source, "baseline", "Baseline");
|
|
1647
|
+
this.baseline = baseline instanceof LoadStrikeAccessibilityBaseline
|
|
1648
|
+
? baseline
|
|
1649
|
+
: (baseline && typeof baseline === "object" ? new LoadStrikeAccessibilityBaseline(baseline) : undefined);
|
|
1650
|
+
this.Baseline = this.baseline;
|
|
1651
|
+
}
|
|
1652
|
+
evaluate(result) {
|
|
1653
|
+
const normalized = attachAccessibilityCounts(result);
|
|
1654
|
+
const reasons = [];
|
|
1655
|
+
if (this.maxViolations != null && (normalized.totalViolations ?? 0) > this.maxViolations) {
|
|
1656
|
+
reasons.push(`total accessibility violations ${normalized.totalViolations} exceeded ${this.maxViolations}.`);
|
|
1657
|
+
}
|
|
1658
|
+
if (this.maxCriticalViolations != null && (normalized.criticalViolations ?? 0) > this.maxCriticalViolations) {
|
|
1659
|
+
reasons.push(`critical accessibility violations ${normalized.criticalViolations} exceeded ${this.maxCriticalViolations}.`);
|
|
1660
|
+
}
|
|
1661
|
+
if (this.maxSeriousViolations != null && (normalized.seriousViolations ?? 0) > this.maxSeriousViolations) {
|
|
1662
|
+
reasons.push(`serious accessibility violations ${normalized.seriousViolations} exceeded ${this.maxSeriousViolations}.`);
|
|
1663
|
+
}
|
|
1664
|
+
if (this.baseline) {
|
|
1665
|
+
const baseline = this.baseline.compare(normalized);
|
|
1666
|
+
if (!baseline.passed) {
|
|
1667
|
+
reasons.push(`baseline found ${baseline.newViolationCount} new accessibility violation(s).`);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
passed: reasons.length === 0,
|
|
1672
|
+
Passed: reasons.length === 0,
|
|
1673
|
+
reasons,
|
|
1674
|
+
Reasons: reasons
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
Evaluate(result) {
|
|
1678
|
+
return this.evaluate(result);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
export class LoadStrikeAccessibilityScanner {
|
|
1682
|
+
static scanHtml(options, html) {
|
|
1683
|
+
validateAbsoluteUrl(options?.url, "Accessibility check URL");
|
|
1684
|
+
const source = String(html ?? "");
|
|
1685
|
+
const violations = [];
|
|
1686
|
+
if (!/<html\b[^>]*\blang\s*=\s*["'][^"']+["']/i.test(source)) {
|
|
1687
|
+
violations.push(createAccessibilityViolation("html-has-lang", "serious", "The html element should declare a non-empty language."));
|
|
1688
|
+
}
|
|
1689
|
+
if (/<title\b[^>]*>\s*<\/title>/i.test(source) || !/<title\b/i.test(source)) {
|
|
1690
|
+
violations.push(createAccessibilityViolation("document-title", "serious", "The page should provide a useful title."));
|
|
1691
|
+
}
|
|
1692
|
+
for (const match of source.matchAll(/<img\b(?![^>]*\balt\s*=)[^>]*>/gi)) {
|
|
1693
|
+
violations.push(createAccessibilityViolation("image-alt", "serious", "Images should provide alternate text.", match[0]));
|
|
1694
|
+
}
|
|
1695
|
+
for (const match of source.matchAll(/<button\b(?![^>]*\baria-label\s*=)[^>]*>\s*<\/button>/gi)) {
|
|
1696
|
+
violations.push(createAccessibilityViolation("button-name", "critical", "Buttons should have an accessible name.", match[0]));
|
|
1697
|
+
}
|
|
1698
|
+
for (const match of source.matchAll(/<input\b(?![^>]*\baria-label\s*=)[^>]*\bid\s*=\s*["']?([a-zA-Z0-9_-]+)["']?[^>]*>/gi)) {
|
|
1699
|
+
const id = match[1] ?? "";
|
|
1700
|
+
const labelRegex = new RegExp(`<label\\b[^>]*\\bfor\\s*=\\s*["']?${escapeRegExp(id)}["']?[^>]*>`, "i");
|
|
1701
|
+
if (!labelRegex.test(source)) {
|
|
1702
|
+
violations.push(createAccessibilityViolation("label", "critical", "Form fields should have labels.", match[0]));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
const focusOrder = [...source.matchAll(/<(a|button|input|select|textarea)\b[^>]*>/gi)]
|
|
1706
|
+
.map((match) => match[0]);
|
|
1707
|
+
const visibleFocusFailures = focusOrder.filter((element) => /outline\s*:\s*(none|0)\b/i.test(element)).length;
|
|
1708
|
+
const missingSkipLinkCount = /href\s*=\s*["']#(main|content)["']/i.test(source) ? 0 : 1;
|
|
1709
|
+
if (visibleFocusFailures > 0) {
|
|
1710
|
+
violations.push(createAccessibilityViolation("focus-visible", "serious", "Keyboard focus indicators should remain visible."));
|
|
1711
|
+
}
|
|
1712
|
+
if (missingSkipLinkCount > 0) {
|
|
1713
|
+
violations.push(createAccessibilityViolation("skip-link", "moderate", "Pages should provide a skip link to the main content."));
|
|
1714
|
+
}
|
|
1715
|
+
return attachAccessibilityCounts({
|
|
1716
|
+
url: options.url,
|
|
1717
|
+
violations,
|
|
1718
|
+
keyboardFocus: {
|
|
1719
|
+
tabbableElementCount: focusOrder.length,
|
|
1720
|
+
visibleFocusFailures,
|
|
1721
|
+
missingSkipLinkCount,
|
|
1722
|
+
focusOrder
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
static ScanHtml(options, html) {
|
|
1727
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
export class LoadStrikeAccessibilityAdapters {
|
|
1731
|
+
static async fromPlaywrightPageAsync(options, page) {
|
|
1732
|
+
const html = await readBrowserContent(page, ["ContentAsync", "contentAsync", "content"]);
|
|
1733
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1734
|
+
}
|
|
1735
|
+
static FromPlaywrightPageAsync(options, page) {
|
|
1736
|
+
return LoadStrikeAccessibilityAdapters.fromPlaywrightPageAsync(options, page);
|
|
1737
|
+
}
|
|
1738
|
+
static fromSeleniumDriver(options, driver) {
|
|
1739
|
+
const record = qualityRecord(driver);
|
|
1740
|
+
const html = String(qualityValue(record, "PageSource", "pageSource") ?? "");
|
|
1741
|
+
return LoadStrikeAccessibilityScanner.scanHtml(options, html);
|
|
1742
|
+
}
|
|
1743
|
+
static FromSeleniumDriver(options, driver) {
|
|
1744
|
+
return LoadStrikeAccessibilityAdapters.fromSeleniumDriver(options, driver);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
export class LoadStrikeBrowserWebVitalsCollector {
|
|
1748
|
+
static fromPerformanceSnapshot(options, snapshot) {
|
|
1749
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1750
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1751
|
+
}
|
|
1752
|
+
static FromPerformanceSnapshot(options, snapshot) {
|
|
1753
|
+
return LoadStrikeBrowserWebVitalsCollector.fromPerformanceSnapshot(options, snapshot);
|
|
1754
|
+
}
|
|
1755
|
+
static async fromPlaywrightPageAsync(options, page) {
|
|
1756
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1757
|
+
const snapshot = await invokeBrowserMethod(page, ["EvaluateAsync", "evaluate"], WEB_VITALS_SCRIPT);
|
|
1758
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1759
|
+
}
|
|
1760
|
+
static FromPlaywrightPageAsync(options, page) {
|
|
1761
|
+
return LoadStrikeBrowserWebVitalsCollector.fromPlaywrightPageAsync(options, page);
|
|
1762
|
+
}
|
|
1763
|
+
static fromSeleniumDriver(options, driver) {
|
|
1764
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1765
|
+
const snapshot = invokeBrowserMethodSync(driver, ["ExecuteScript", "executeScript"], WEB_VITALS_SCRIPT);
|
|
1766
|
+
return normalizeWebVitalsSnapshot(options.url, snapshot);
|
|
1767
|
+
}
|
|
1768
|
+
static FromSeleniumDriver(options, driver) {
|
|
1769
|
+
return LoadStrikeBrowserWebVitalsCollector.fromSeleniumDriver(options, driver);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
export class LoadStrikeAccessibility {
|
|
1773
|
+
static createScenario(name, options, check) {
|
|
1774
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
1775
|
+
throw new Error("Scenario name must be provided.");
|
|
1776
|
+
}
|
|
1777
|
+
validateAbsoluteUrl(options?.url, "Accessibility check URL");
|
|
1778
|
+
if (typeof check !== "function") {
|
|
1779
|
+
throw new TypeError("Accessibility check callback must be provided.");
|
|
1780
|
+
}
|
|
1781
|
+
return LoadStrikeScenario
|
|
1782
|
+
.create(name, async (scenarioContext) => {
|
|
1783
|
+
const result = await check({ options, scenarioContext });
|
|
1784
|
+
const failure = evaluateAccessibilityResult(options, result);
|
|
1785
|
+
return failure
|
|
1786
|
+
? LoadStrikeResponse.fail("accessibility", failure)
|
|
1787
|
+
: LoadStrikeResponse.ok("200", 0, `Accessibility check passed with ${(result.violations ?? []).length} violation(s).`);
|
|
1788
|
+
})
|
|
1789
|
+
.withoutWarmUp()
|
|
1790
|
+
.__loadStrikeWithInternalLicenseFeatures(ACCESSIBILITY_TESTING_FEATURE);
|
|
1791
|
+
}
|
|
1792
|
+
static CreateScenario(name, options, check) {
|
|
1793
|
+
return LoadStrikeAccessibility.createScenario(name, options, check);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
export class LoadStrikeBrowserWebVitals {
|
|
1797
|
+
static createScenario(name, options, measure) {
|
|
1798
|
+
if (typeof name !== "string" || !name.trim()) {
|
|
1799
|
+
throw new Error("Scenario name must be provided.");
|
|
1800
|
+
}
|
|
1801
|
+
validateAbsoluteUrl(options?.url, "Browser Web Vitals URL");
|
|
1802
|
+
if (typeof measure !== "function") {
|
|
1803
|
+
throw new TypeError("Browser Web Vitals measurement callback must be provided.");
|
|
1804
|
+
}
|
|
1805
|
+
return LoadStrikeScenario
|
|
1806
|
+
.create(name, async (scenarioContext) => {
|
|
1807
|
+
const result = await measure({ options, scenarioContext });
|
|
1808
|
+
const failure = evaluateWebVitalsResult(options, result);
|
|
1809
|
+
return failure
|
|
1810
|
+
? LoadStrikeResponse.fail("web_vitals", failure)
|
|
1811
|
+
: LoadStrikeResponse.ok("200", 0, "Browser Web Vitals check passed.");
|
|
1812
|
+
})
|
|
1813
|
+
.withoutWarmUp()
|
|
1814
|
+
.__loadStrikeWithInternalLicenseFeatures(BROWSER_WEB_VITALS_FEATURE);
|
|
1815
|
+
}
|
|
1816
|
+
static CreateScenario(name, options, measure) {
|
|
1817
|
+
return LoadStrikeBrowserWebVitals.createScenario(name, options, measure);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const WEB_VITALS_SCRIPT = `
|
|
1821
|
+
(() => {
|
|
1822
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
1823
|
+
const paint = performance.getEntriesByType("paint");
|
|
1824
|
+
const fcp = paint.find((entry) => entry.name === "first-contentful-paint");
|
|
1825
|
+
return {
|
|
1826
|
+
largestContentfulPaintMs: globalThis.__loadstrikeLcp ?? 0,
|
|
1827
|
+
interactionToNextPaintMs: globalThis.__loadstrikeInp ?? 0,
|
|
1828
|
+
cumulativeLayoutShift: globalThis.__loadstrikeCls ?? 0,
|
|
1829
|
+
firstContentfulPaintMs: fcp ? fcp.startTime : 0,
|
|
1830
|
+
timeToFirstByteMs: navigation ? navigation.responseStart : 0
|
|
1831
|
+
};
|
|
1832
|
+
})()
|
|
1833
|
+
`;
|
|
1834
|
+
function createAccessibilityViolation(ruleId, impact, description, target) {
|
|
1835
|
+
return {
|
|
1836
|
+
ruleId,
|
|
1837
|
+
impact,
|
|
1838
|
+
description,
|
|
1839
|
+
helpUrl: `https://www.w3.org/WAI/WCAG22/quickref/?versions=2.2#${ruleId}`,
|
|
1840
|
+
targets: target ? [target] : []
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
function attachAccessibilityCounts(result) {
|
|
1844
|
+
const violations = Array.isArray(result?.violations) ? result.violations : [];
|
|
1845
|
+
return {
|
|
1846
|
+
...result,
|
|
1847
|
+
violations,
|
|
1848
|
+
totalViolations: violations.length,
|
|
1849
|
+
criticalViolations: countAccessibilityImpact(violations, "critical"),
|
|
1850
|
+
seriousViolations: countAccessibilityImpact(violations, "serious"),
|
|
1851
|
+
moderateViolations: countAccessibilityImpact(violations, "moderate"),
|
|
1852
|
+
minorViolations: countAccessibilityImpact(violations, "minor")
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
function qualityRecord(value) {
|
|
1856
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
1857
|
+
? value
|
|
1858
|
+
: {};
|
|
1859
|
+
}
|
|
1860
|
+
function qualityValue(record, ...keys) {
|
|
1861
|
+
for (const key of keys) {
|
|
1862
|
+
if (key in record) {
|
|
1863
|
+
return record[key];
|
|
1864
|
+
}
|
|
1865
|
+
const normalized = key.toLowerCase();
|
|
1866
|
+
const match = Object.entries(record).find(([entryKey]) => entryKey.toLowerCase() === normalized);
|
|
1867
|
+
if (match) {
|
|
1868
|
+
return match[1];
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return undefined;
|
|
1872
|
+
}
|
|
1873
|
+
function qualityStringArray(value) {
|
|
1874
|
+
if (!Array.isArray(value)) {
|
|
1875
|
+
return [];
|
|
1876
|
+
}
|
|
1877
|
+
return value.map((entry) => String(entry ?? "").trim()).filter(Boolean);
|
|
1878
|
+
}
|
|
1879
|
+
function qualityNumber(record, fallback, ...keys) {
|
|
1880
|
+
const value = qualityValue(record, ...keys);
|
|
1881
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1882
|
+
return value;
|
|
1883
|
+
}
|
|
1884
|
+
if (typeof value === "string" && value.trim()) {
|
|
1885
|
+
const parsed = Number(value);
|
|
1886
|
+
if (Number.isFinite(parsed)) {
|
|
1887
|
+
return parsed;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
return fallback;
|
|
1891
|
+
}
|
|
1892
|
+
function qualityOptionalNumber(record, ...keys) {
|
|
1893
|
+
const value = qualityValue(record, ...keys);
|
|
1894
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1895
|
+
return value;
|
|
1896
|
+
}
|
|
1897
|
+
if (typeof value === "string" && value.trim()) {
|
|
1898
|
+
const parsed = Number(value);
|
|
1899
|
+
if (Number.isFinite(parsed)) {
|
|
1900
|
+
return parsed;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return undefined;
|
|
1904
|
+
}
|
|
1905
|
+
function escapeRegExp(value) {
|
|
1906
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1907
|
+
}
|
|
1908
|
+
async function readBrowserContent(target, methodNames) {
|
|
1909
|
+
const value = await invokeBrowserMethod(target, methodNames);
|
|
1910
|
+
return String(value ?? "");
|
|
1911
|
+
}
|
|
1912
|
+
async function invokeBrowserMethod(target, methodNames, ...args) {
|
|
1913
|
+
const record = qualityRecord(target);
|
|
1914
|
+
for (const name of methodNames) {
|
|
1915
|
+
const method = record[name];
|
|
1916
|
+
if (typeof method === "function") {
|
|
1917
|
+
return await method.apply(target, args);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
throw new Error(`Browser object does not expose ${methodNames.join(" or ")}.`);
|
|
1921
|
+
}
|
|
1922
|
+
function invokeBrowserMethodSync(target, methodNames, ...args) {
|
|
1923
|
+
const record = qualityRecord(target);
|
|
1924
|
+
for (const name of methodNames) {
|
|
1925
|
+
const method = record[name];
|
|
1926
|
+
if (typeof method === "function") {
|
|
1927
|
+
return method.apply(target, args);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
throw new Error(`Browser object does not expose ${methodNames.join(" or ")}.`);
|
|
1931
|
+
}
|
|
1932
|
+
function normalizeWebVitalsSnapshot(url, snapshot) {
|
|
1933
|
+
const source = qualityRecord(snapshot);
|
|
1934
|
+
return {
|
|
1935
|
+
url,
|
|
1936
|
+
largestContentfulPaintMs: qualityNumber(source, 0, "largestContentfulPaintMs", "LargestContentfulPaintMs", "LCP", "lcp"),
|
|
1937
|
+
interactionToNextPaintMs: qualityNumber(source, 0, "interactionToNextPaintMs", "InteractionToNextPaintMs", "INP", "inp"),
|
|
1938
|
+
cumulativeLayoutShift: qualityNumber(source, 0, "cumulativeLayoutShift", "CumulativeLayoutShift", "CLS", "cls"),
|
|
1939
|
+
firstContentfulPaintMs: qualityNumber(source, 0, "firstContentfulPaintMs", "FirstContentfulPaintMs", "FCP", "fcp"),
|
|
1940
|
+
timeToFirstByteMs: qualityNumber(source, 0, "timeToFirstByteMs", "TimeToFirstByteMs", "TTFB", "ttfb")
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
function validateAbsoluteUrl(value, label) {
|
|
1944
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
1945
|
+
throw new Error(`${label} must be provided.`);
|
|
1946
|
+
}
|
|
1947
|
+
try {
|
|
1948
|
+
const parsed = new URL(value);
|
|
1949
|
+
if (!parsed.protocol || !parsed.host) {
|
|
1950
|
+
throw new Error("invalid");
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
catch {
|
|
1954
|
+
throw new Error(`${label} must be an absolute URL.`);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function evaluateAccessibilityResult(options, result) {
|
|
1958
|
+
if (!result) {
|
|
1959
|
+
return "Accessibility check did not return a result.";
|
|
1960
|
+
}
|
|
1961
|
+
const violations = Array.isArray(result.violations) ? result.violations : [];
|
|
1962
|
+
const maxViolations = Math.max(0, Number(options.maxViolations ?? 0));
|
|
1963
|
+
const critical = countAccessibilityImpact(violations, "critical");
|
|
1964
|
+
const serious = countAccessibilityImpact(violations, "serious");
|
|
1965
|
+
const maxCritical = Math.max(0, Number(options.maxCriticalViolations ?? 0));
|
|
1966
|
+
const maxSerious = Math.max(0, Number(options.maxSeriousViolations ?? 0));
|
|
1967
|
+
if (violations.length > maxViolations) {
|
|
1968
|
+
return `Accessibility violations ${violations.length} exceeded limit ${maxViolations}.`;
|
|
1969
|
+
}
|
|
1970
|
+
if (critical > maxCritical) {
|
|
1971
|
+
return `Critical accessibility violations ${critical} exceeded limit ${maxCritical}.`;
|
|
1972
|
+
}
|
|
1973
|
+
if (serious > maxSerious) {
|
|
1974
|
+
return `Serious accessibility violations ${serious} exceeded limit ${maxSerious}.`;
|
|
1975
|
+
}
|
|
1976
|
+
return "";
|
|
1977
|
+
}
|
|
1978
|
+
function countAccessibilityImpact(violations, impact) {
|
|
1979
|
+
return violations.filter((violation) => String(violation.impact ?? "").toLowerCase() === impact).length;
|
|
1980
|
+
}
|
|
1981
|
+
function evaluateWebVitalsResult(options, result) {
|
|
1982
|
+
if (!result) {
|
|
1983
|
+
return "Browser Web Vitals check did not return a result.";
|
|
1984
|
+
}
|
|
1985
|
+
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"]);
|
|
1986
|
+
}
|
|
1987
|
+
function firstWebVitalViolation(...values) {
|
|
1988
|
+
for (const [name, actualRaw, limitRaw, unit] of values) {
|
|
1989
|
+
const actual = Number(actualRaw);
|
|
1990
|
+
const limit = Number(limitRaw);
|
|
1991
|
+
if (!Number.isFinite(actual) || !Number.isFinite(limit) || actual <= limit) {
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
const suffix = unit ? ` ${unit}` : "";
|
|
1995
|
+
return `${name} ${actual}${suffix} exceeded limit ${limit}${suffix}.`;
|
|
1996
|
+
}
|
|
1997
|
+
return "";
|
|
1998
|
+
}
|
|
1607
1999
|
export class LoadStrikeScenario {
|
|
1608
2000
|
constructor(name, runHandler, initHandler, cleanHandler, loadSimulations, thresholds, trackingConfiguration, maxFailCount, withoutWarmUpValue, warmUpDurationSeconds, weight, restartIterationOnFail, internalLicenseFeatures = []) {
|
|
1609
2001
|
this.name = name;
|
|
@@ -1838,6 +2230,26 @@ export class LoadStrikeScenario {
|
|
|
1838
2230
|
__loadStrikeInternalLicenseFeatures() {
|
|
1839
2231
|
return [...this.internalLicenseFeatures];
|
|
1840
2232
|
}
|
|
2233
|
+
__loadStrikeScenarioSourceAnalysis() {
|
|
2234
|
+
const source = this.runHandler.toString();
|
|
2235
|
+
const lines = source
|
|
2236
|
+
.split(/\r?\n/)
|
|
2237
|
+
.map((line) => line.trim())
|
|
2238
|
+
.filter((line) => line.length > 0 && !line.startsWith("//"));
|
|
2239
|
+
const lineCount = Math.max(lines.length, 1);
|
|
2240
|
+
return {
|
|
2241
|
+
ScenarioName: this.name,
|
|
2242
|
+
Language: "TypeScript/JavaScript",
|
|
2243
|
+
AnalyzerVersion: "function-source-v1",
|
|
2244
|
+
ScenarioBlockLineCount: lineCount,
|
|
2245
|
+
LocalMethodLineCount: 0,
|
|
2246
|
+
TotalCountedLineCount: lineCount,
|
|
2247
|
+
IgnoredExternalCallCount: 0,
|
|
2248
|
+
Warnings: [
|
|
2249
|
+
"Function-source analysis counts the scenario handler source available at runtime; imported library implementation lines are ignored."
|
|
2250
|
+
]
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
1841
2253
|
__loadStrikeWithInternalLicenseFeatures(...features) {
|
|
1842
2254
|
const merged = Array.from(new Set([...this.internalLicenseFeatures, ...normalizeStringArray(features)]));
|
|
1843
2255
|
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);
|
|
@@ -7931,7 +8343,8 @@ function buildLicenseValidationPayload(options, scenarios) {
|
|
|
7931
8343
|
Weight: scenario.getWeight(),
|
|
7932
8344
|
LoadSimulations: [...scenario.getSimulations()],
|
|
7933
8345
|
Thresholds: [...scenario.getThresholds()],
|
|
7934
|
-
Tracking: scenario.getTrackingConfiguration() ?? {}
|
|
8346
|
+
Tracking: scenario.getTrackingConfiguration() ?? {},
|
|
8347
|
+
ScenarioSourceAnalysis: scenario.__loadStrikeScenarioSourceAnalysis()
|
|
7935
8348
|
};
|
|
7936
8349
|
const internalLicenseFeatures = scenario.__loadStrikeInternalLicenseFeatures();
|
|
7937
8350
|
if (internalLicenseFeatures.length > 0) {
|