@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/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 {
@@ -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) {