nat-engine 1__py3-none-any.whl
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.
- mannf/__init__.py +33 -0
- mannf/__main__.py +10 -0
- mannf/_version.py +8 -0
- mannf/agents/__init__.py +7 -0
- mannf/agents/analyzer_agent.py +9 -0
- mannf/agents/base.py +9 -0
- mannf/agents/bdi_agent.py +9 -0
- mannf/agents/belief_state.py +9 -0
- mannf/agents/coordinator_agent.py +9 -0
- mannf/agents/executor_agent.py +9 -0
- mannf/agents/monitor_agent.py +9 -0
- mannf/agents/oracle_agent.py +9 -0
- mannf/agents/planner_agent.py +9 -0
- mannf/agents/test_agent.py +9 -0
- mannf/anomaly/__init__.py +7 -0
- mannf/anomaly/enhanced_detector.py +9 -0
- mannf/cli.py +9 -0
- mannf/core/__init__.py +26 -0
- mannf/core/agents/__init__.py +52 -0
- mannf/core/agents/accessibility_scanner_agent.py +245 -0
- mannf/core/agents/analyzer_agent.py +224 -0
- mannf/core/agents/autonomous_loop_agent.py +1086 -0
- mannf/core/agents/autonomous_loop_models.py +62 -0
- mannf/core/agents/autonomous_run_differ.py +427 -0
- mannf/core/agents/base.py +128 -0
- mannf/core/agents/bdi_agent.py +330 -0
- mannf/core/agents/belief_state.py +202 -0
- mannf/core/agents/browser_coordinator_agent.py +224 -0
- mannf/core/agents/browser_executor_agent.py +410 -0
- mannf/core/agents/coordinator_agent.py +262 -0
- mannf/core/agents/executor_agent.py +222 -0
- mannf/core/agents/monitor_agent.py +188 -0
- mannf/core/agents/oracle_agent.py +150 -0
- mannf/core/agents/performance_testing_agent.py +279 -0
- mannf/core/agents/planner_agent.py +128 -0
- mannf/core/agents/test_agent.py +249 -0
- mannf/core/agents/visual_regression_agent.py +311 -0
- mannf/core/agents/web_crawler_agent.py +510 -0
- mannf/core/agents/worker_pool.py +366 -0
- mannf/core/anomaly/__init__.py +14 -0
- mannf/core/anomaly/enhanced_detector.py +541 -0
- mannf/core/browser/__init__.py +63 -0
- mannf/core/browser/accessibility_scanner.py +424 -0
- mannf/core/browser/discovery_model.py +178 -0
- mannf/core/browser/dom_snapshot.py +349 -0
- mannf/core/browser/ingestor_bridge.py +371 -0
- mannf/core/browser/performance_metrics.py +217 -0
- mannf/core/browser/reflection_analyzer.py +442 -0
- mannf/core/browser/scenario_generator.py +1100 -0
- mannf/core/browser/security_scenario_generator.py +695 -0
- mannf/core/browser/visual_comparer.py +159 -0
- mannf/core/diagnostics/__init__.py +28 -0
- mannf/core/diagnostics/failure_clusterer.py +211 -0
- mannf/core/diagnostics/flake_detector.py +233 -0
- mannf/core/diagnostics/root_cause_analyzer.py +273 -0
- mannf/core/distributed/__init__.py +16 -0
- mannf/core/distributed/endpoint.py +139 -0
- mannf/core/distributed/system_under_test.py +207 -0
- mannf/core/functional_orchestrator.py +428 -0
- mannf/core/messaging/__init__.py +11 -0
- mannf/core/messaging/bus.py +113 -0
- mannf/core/messaging/messages.py +89 -0
- mannf/core/nat_orchestrator.py +342 -0
- mannf/core/neural/__init__.py +183 -0
- mannf/core/orchestrator.py +272 -0
- mannf/core/prioritization/__init__.py +17 -0
- mannf/core/prioritization/adaptive_controller.py +509 -0
- mannf/core/prioritization/belief_prioritizer.py +231 -0
- mannf/core/prioritization/risk_scorer.py +430 -0
- mannf/core/reporting/__init__.py +12 -0
- mannf/core/reporting/unified_report.py +664 -0
- mannf/core/testing/__init__.py +17 -0
- mannf/core/testing/adaptive_controller.py +149 -0
- mannf/core/testing/models.py +179 -0
- mannf/core/validation/__init__.py +10 -0
- mannf/core/validation/self_validation_runner.py +180 -0
- mannf/dashboard/__init__.py +7 -0
- mannf/dashboard/app.py +9 -0
- mannf/dashboard/models.py +9 -0
- mannf/dashboard/static/index.html +2538 -0
- mannf/dashboard/telemetry.py +9 -0
- mannf/distributed/__init__.py +7 -0
- mannf/distributed/endpoint.py +9 -0
- mannf/distributed/system_under_test.py +9 -0
- mannf/healing/__init__.py +7 -0
- mannf/healing/graphql_schema_diff.py +9 -0
- mannf/healing/healer.py +9 -0
- mannf/healing/models.py +9 -0
- mannf/healing/schema_diff.py +9 -0
- mannf/integrations/__init__.py +7 -0
- mannf/integrations/auth.py +9 -0
- mannf/integrations/graphql_parser.py +9 -0
- mannf/integrations/graphql_sut.py +9 -0
- mannf/integrations/http_sut.py +9 -0
- mannf/integrations/openapi_parser.py +9 -0
- mannf/integrations/postman_parser.py +9 -0
- mannf/llm/__init__.py +7 -0
- mannf/llm/anthropic_provider.py +9 -0
- mannf/llm/base.py +9 -0
- mannf/llm/config.py +9 -0
- mannf/llm/factory.py +9 -0
- mannf/llm/openai_provider.py +9 -0
- mannf/llm/prompts.py +9 -0
- mannf/messaging/__init__.py +7 -0
- mannf/messaging/bus.py +9 -0
- mannf/messaging/messages.py +9 -0
- mannf/nat_orchestrator.py +9 -0
- mannf/neural/__init__.py +7 -0
- mannf/orchestrator.py +9 -0
- mannf/prioritization/__init__.py +7 -0
- mannf/prioritization/adaptive_controller.py +9 -0
- mannf/prioritization/belief_prioritizer.py +9 -0
- mannf/prioritization/risk_scorer.py +9 -0
- mannf/product/__init__.py +29 -0
- mannf/product/admin/__init__.py +3 -0
- mannf/product/admin/routes.py +514 -0
- mannf/product/auth/__init__.py +5 -0
- mannf/product/auth/saml.py +212 -0
- mannf/product/billing/__init__.py +5 -0
- mannf/product/billing/audit.py +160 -0
- mannf/product/billing/feature_gates.py +180 -0
- mannf/product/billing/metering.py +179 -0
- mannf/product/billing/notifications.py +181 -0
- mannf/product/billing/plans.py +133 -0
- mannf/product/billing/rate_limits.py +35 -0
- mannf/product/billing/stripe_billing.py +906 -0
- mannf/product/billing/tenant_auth.py +233 -0
- mannf/product/billing/tenant_manager.py +873 -0
- mannf/product/cli.py +3900 -0
- mannf/product/cli_admin.py +408 -0
- mannf/product/dashboard/__init__.py +61 -0
- mannf/product/dashboard/app.py +3567 -0
- mannf/product/dashboard/models.py +460 -0
- mannf/product/dashboard/static/index.html +6347 -0
- mannf/product/dashboard/static/manifest.json +25 -0
- mannf/product/dashboard/static/pwa-icon-192.png +0 -0
- mannf/product/dashboard/static/pwa-icon-512.png +0 -0
- mannf/product/dashboard/static/sw.js +64 -0
- mannf/product/dashboard/telemetry.py +547 -0
- mannf/product/database.py +145 -0
- mannf/product/demo.py +844 -0
- mannf/product/doctor.py +509 -0
- mannf/product/exporters/__init__.py +65 -0
- mannf/product/exporters/azuredevops_exporter.py +257 -0
- mannf/product/exporters/base.py +307 -0
- mannf/product/exporters/bugzilla_exporter.py +200 -0
- mannf/product/exporters/dedup.py +275 -0
- mannf/product/exporters/finding_adapter.py +216 -0
- mannf/product/exporters/github_exporter.py +197 -0
- mannf/product/exporters/gitlab_exporter.py +215 -0
- mannf/product/exporters/jira_exporter.py +180 -0
- mannf/product/exporters/linear_exporter.py +195 -0
- mannf/product/exporters/loader.py +233 -0
- mannf/product/exporters/pagerduty_exporter.py +363 -0
- mannf/product/exporters/sentry_exporter.py +322 -0
- mannf/product/exporters/servicenow_exporter.py +240 -0
- mannf/product/exporters/shortcut_exporter.py +231 -0
- mannf/product/exporters/webhook_exporter.py +383 -0
- mannf/product/formatters/__init__.py +18 -0
- mannf/product/formatters/allure_formatter.py +161 -0
- mannf/product/formatters/ctrf_formatter.py +149 -0
- mannf/product/healing/__init__.py +30 -0
- mannf/product/healing/graphql_schema_diff.py +152 -0
- mannf/product/healing/healer.py +141 -0
- mannf/product/healing/models.py +175 -0
- mannf/product/healing/schema_diff.py +251 -0
- mannf/product/ingestors/__init__.py +77 -0
- mannf/product/ingestors/base.py +256 -0
- mannf/product/ingestors/bgstm_ingestor.py +764 -0
- mannf/product/ingestors/curl_ingestor.py +1019 -0
- mannf/product/ingestors/cypress_ingestor.py +487 -0
- mannf/product/ingestors/gherkin_ingestor.py +967 -0
- mannf/product/ingestors/graphql_ingestor.py +845 -0
- mannf/product/ingestors/grpc_ingestor.py +591 -0
- mannf/product/ingestors/har_ingestor.py +976 -0
- mannf/product/ingestors/loader.py +284 -0
- mannf/product/ingestors/models.py +146 -0
- mannf/product/ingestors/openapi_ingestor.py +606 -0
- mannf/product/ingestors/playwright_ingestor.py +449 -0
- mannf/product/ingestors/postman_ingestor.py +631 -0
- mannf/product/ingestors/traffic_ingestor.py +679 -0
- mannf/product/ingestors/websocket_ingestor.py +526 -0
- mannf/product/integrations/__init__.py +21 -0
- mannf/product/integrations/auth.py +190 -0
- mannf/product/integrations/graphql_parser.py +436 -0
- mannf/product/integrations/graphql_sut.py +247 -0
- mannf/product/integrations/grpc_sut.py +469 -0
- mannf/product/integrations/http_sut.py +237 -0
- mannf/product/integrations/kafka_adapter.py +342 -0
- mannf/product/integrations/openapi_parser.py +513 -0
- mannf/product/integrations/postman_parser.py +467 -0
- mannf/product/integrations/webhook_receiver.py +344 -0
- mannf/product/integrations/websocket_sut.py +434 -0
- mannf/product/llm/__init__.py +25 -0
- mannf/product/llm/anthropic_provider.py +94 -0
- mannf/product/llm/base.py +267 -0
- mannf/product/llm/config.py +48 -0
- mannf/product/llm/factory.py +42 -0
- mannf/product/llm/openai_provider.py +93 -0
- mannf/product/llm/prompts.py +403 -0
- mannf/product/llm/root_cause_service.py +311 -0
- mannf/product/llm/test_plan_models.py +78 -0
- mannf/product/metrics.py +149 -0
- mannf/product/middleware/__init__.py +3 -0
- mannf/product/middleware/audit_middleware.py +112 -0
- mannf/product/middleware/tenant_isolation.py +114 -0
- mannf/product/models.py +347 -0
- mannf/product/notifications/__init__.py +24 -0
- mannf/product/notifications/dispatcher.py +411 -0
- mannf/product/onboarding.py +190 -0
- mannf/product/orchestration/__init__.py +39 -0
- mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
- mannf/product/orchestration/pipeline.py +401 -0
- mannf/product/orchestrator.py +987 -0
- mannf/product/orchestrator_models.py +269 -0
- mannf/product/regression/__init__.py +36 -0
- mannf/product/regression/differ.py +172 -0
- mannf/product/regression/masking.py +100 -0
- mannf/product/regression/models.py +232 -0
- mannf/product/regression/recorder.py +124 -0
- mannf/product/regression/replayer.py +168 -0
- mannf/product/reports/__init__.py +10 -0
- mannf/product/reports/pdf.py +132 -0
- mannf/product/scheduling/__init__.py +57 -0
- mannf/product/scheduling/cron_utils.py +251 -0
- mannf/product/scheduling/engine.py +473 -0
- mannf/product/scheduling/models.py +86 -0
- mannf/product/scheduling/queue.py +894 -0
- mannf/product/scheduling/store.py +235 -0
- mannf/product/security/__init__.py +21 -0
- mannf/product/security/belief_guided.py +143 -0
- mannf/product/security/checks/__init__.py +55 -0
- mannf/product/security/checks/base.py +69 -0
- mannf/product/security/checks/bfla.py +77 -0
- mannf/product/security/checks/bola.py +77 -0
- mannf/product/security/checks/bopla.py +80 -0
- mannf/product/security/checks/broken_auth.py +86 -0
- mannf/product/security/checks/graphql_security.py +299 -0
- mannf/product/security/checks/inventory.py +70 -0
- mannf/product/security/checks/misconfig.py +158 -0
- mannf/product/security/checks/resource_consumption.py +70 -0
- mannf/product/security/checks/sensitive_flows.py +80 -0
- mannf/product/security/checks/ssrf.py +101 -0
- mannf/product/security/checks/unsafe_consumption.py +120 -0
- mannf/product/security/models.py +92 -0
- mannf/product/security/plugin_loader.py +182 -0
- mannf/product/security/reporter.py +92 -0
- mannf/product/security/scanner.py +183 -0
- mannf/product/server.py +6220 -0
- mannf/product/setup_wizard.py +873 -0
- mannf/product/status.py +404 -0
- mannf/product/storage/__init__.py +10 -0
- mannf/product/storage/artifact_store.py +343 -0
- mannf/product/telemetry.py +300 -0
- mannf/product/uninstall.py +169 -0
- mannf/product/upgrade.py +139 -0
- mannf/product/weights/__init__.py +13 -0
- mannf/product/weights/blob_store.py +299 -0
- mannf/product/weights/factory.py +42 -0
- mannf/product/weights/registry.py +159 -0
- mannf/product/weights/store.py +210 -0
- mannf/regression/__init__.py +7 -0
- mannf/regression/differ.py +9 -0
- mannf/regression/masking.py +9 -0
- mannf/regression/models.py +9 -0
- mannf/regression/recorder.py +9 -0
- mannf/regression/replayer.py +9 -0
- mannf/security/__init__.py +7 -0
- mannf/security/belief_guided.py +9 -0
- mannf/security/checks/__init__.py +7 -0
- mannf/security/checks/base.py +9 -0
- mannf/security/checks/bfla.py +9 -0
- mannf/security/checks/bola.py +9 -0
- mannf/security/checks/bopla.py +9 -0
- mannf/security/checks/broken_auth.py +9 -0
- mannf/security/checks/graphql_security.py +9 -0
- mannf/security/checks/inventory.py +9 -0
- mannf/security/checks/misconfig.py +9 -0
- mannf/security/checks/resource_consumption.py +9 -0
- mannf/security/checks/sensitive_flows.py +9 -0
- mannf/security/checks/ssrf.py +9 -0
- mannf/security/checks/unsafe_consumption.py +9 -0
- mannf/security/models.py +9 -0
- mannf/security/reporter.py +9 -0
- mannf/security/scanner.py +9 -0
- mannf/server.py +9 -0
- mannf/testing/__init__.py +7 -0
- mannf/testing/adaptive_controller.py +9 -0
- mannf/testing/models.py +9 -0
- mannf/weights/__init__.py +7 -0
- mannf/weights/registry.py +9 -0
- mannf/weights/store.py +9 -0
- nat_engine-1.dist-info/METADATA +555 -0
- nat_engine-1.dist-info/RECORD +299 -0
- nat_engine-1.dist-info/WHEEL +5 -0
- nat_engine-1.dist-info/entry_points.txt +4 -0
- nat_engine-1.dist-info/licenses/LICENSE +651 -0
- nat_engine-1.dist-info/licenses/NOTICE +178 -0
- nat_engine-1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""FunctionalTestOrchestrator – high-level lifecycle manager for browser-based
|
|
7
|
+
functional testing.
|
|
8
|
+
|
|
9
|
+
Analogous to :class:`~mannf.core.nat_orchestrator.NATOrchestrator` but
|
|
10
|
+
purpose-built for Playwright-driven UI tests:
|
|
11
|
+
|
|
12
|
+
* Spins up N :class:`~mannf.core.agents.browser_executor_agent.BrowserExecutorAgent`
|
|
13
|
+
instances and one :class:`~mannf.core.agents.browser_coordinator_agent.BrowserCoordinatorAgent`.
|
|
14
|
+
* Dispatches a list of ``{url, steps}`` task dicts through the coordinator.
|
|
15
|
+
* Monitors ``INTERACTION_RESULT`` messages for completion.
|
|
16
|
+
* Returns a structured report with pass/fail counts, per-task outcomes,
|
|
17
|
+
timing, screenshots and DOM snapshots.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from mannf.core.agents.browser_coordinator_agent import BrowserCoordinatorAgent
|
|
28
|
+
from mannf.core.agents.browser_executor_agent import BrowserExecutorAgent
|
|
29
|
+
from mannf.core.agents.visual_regression_agent import VisualRegressionAgent
|
|
30
|
+
from mannf.core.agents.accessibility_scanner_agent import AccessibilityScannerAgent
|
|
31
|
+
from mannf.core.agents.performance_testing_agent import PerformanceTestingAgent
|
|
32
|
+
from mannf.core.messaging.bus import MessageBus
|
|
33
|
+
from mannf.core.messaging.messages import Message, MessageType
|
|
34
|
+
from mannf.core.reporting.unified_report import UnifiedReportGenerator
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FunctionalTestOrchestrator:
|
|
40
|
+
"""Manages the full lifecycle for browser-based functional testing.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
num_browser_agents:
|
|
45
|
+
Number of :class:`BrowserExecutorAgent` instances to create.
|
|
46
|
+
browser_type:
|
|
47
|
+
Playwright browser family: ``"chromium"``, ``"firefox"``, or
|
|
48
|
+
``"webkit"``.
|
|
49
|
+
headless:
|
|
50
|
+
Run browsers in headless mode (no visible window).
|
|
51
|
+
max_tasks:
|
|
52
|
+
Maximum number of functional tests to execute before stopping.
|
|
53
|
+
bid_timeout_s:
|
|
54
|
+
ECNP bid collection window (seconds).
|
|
55
|
+
enable_visual_regression:
|
|
56
|
+
When ``True``, create a :class:`VisualRegressionAgent` and include
|
|
57
|
+
visual regression results in the :meth:`report` output.
|
|
58
|
+
baseline_dir:
|
|
59
|
+
Directory to store baseline PNG screenshots (used when
|
|
60
|
+
*enable_visual_regression* is ``True``).
|
|
61
|
+
diff_threshold:
|
|
62
|
+
Maximum allowed diff percentage before a visual regression is flagged.
|
|
63
|
+
update_baselines:
|
|
64
|
+
When ``True``, always save new screenshots as baselines instead of
|
|
65
|
+
comparing them.
|
|
66
|
+
enable_accessibility_scan:
|
|
67
|
+
When ``True``, create an :class:`AccessibilityScannerAgent` and include
|
|
68
|
+
accessibility results in the :meth:`report` output.
|
|
69
|
+
min_compliance_score:
|
|
70
|
+
Minimum compliance percentage (0–100) used by
|
|
71
|
+
:class:`AccessibilityScannerAgent` when *enable_accessibility_scan*
|
|
72
|
+
is ``True``.
|
|
73
|
+
enable_performance_testing:
|
|
74
|
+
When ``True``, create a :class:`PerformanceTestingAgent` and include
|
|
75
|
+
performance results in the :meth:`report` output.
|
|
76
|
+
min_performance_score:
|
|
77
|
+
Minimum performance score (0–100) used by
|
|
78
|
+
:class:`PerformanceTestingAgent` when *enable_performance_testing*
|
|
79
|
+
is ``True``.
|
|
80
|
+
custom_perf_thresholds:
|
|
81
|
+
Optional threshold overrides for :class:`PerformanceTestingAgent`.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
num_browser_agents: int = 2,
|
|
87
|
+
browser_type: str = "chromium",
|
|
88
|
+
headless: bool = True,
|
|
89
|
+
max_tasks: int = 50,
|
|
90
|
+
bid_timeout_s: float = 0.05,
|
|
91
|
+
enable_visual_regression: bool = False,
|
|
92
|
+
baseline_dir: str = ".nat/visual_baselines",
|
|
93
|
+
diff_threshold: float = 0.1,
|
|
94
|
+
update_baselines: bool = False,
|
|
95
|
+
enable_accessibility_scan: bool = False,
|
|
96
|
+
min_compliance_score: float = 80.0,
|
|
97
|
+
enable_performance_testing: bool = False,
|
|
98
|
+
min_performance_score: float = 70.0,
|
|
99
|
+
custom_perf_thresholds: dict[str, tuple[float, float, str]] | None = None,
|
|
100
|
+
llm_provider: Any | None = None,
|
|
101
|
+
root_cause_quota: int = -1,
|
|
102
|
+
) -> None:
|
|
103
|
+
self._num_browser_agents = num_browser_agents
|
|
104
|
+
self._browser_type = browser_type
|
|
105
|
+
self._headless = headless
|
|
106
|
+
self._max_tasks = max_tasks
|
|
107
|
+
self._enable_visual_regression = enable_visual_regression
|
|
108
|
+
self._enable_accessibility_scan = enable_accessibility_scan
|
|
109
|
+
self._enable_performance_testing = enable_performance_testing
|
|
110
|
+
self._llm_provider = llm_provider
|
|
111
|
+
self._root_cause_quota = root_cause_quota
|
|
112
|
+
|
|
113
|
+
self._bus = MessageBus()
|
|
114
|
+
|
|
115
|
+
# Browser executor agents
|
|
116
|
+
self._browser_agents: list[BrowserExecutorAgent] = [
|
|
117
|
+
BrowserExecutorAgent(
|
|
118
|
+
agent_id=f"browser-executor-{i}",
|
|
119
|
+
bus=self._bus,
|
|
120
|
+
service_names=["browser"],
|
|
121
|
+
browser_type=browser_type,
|
|
122
|
+
headless=headless,
|
|
123
|
+
)
|
|
124
|
+
for i in range(num_browser_agents)
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
# Browser coordinator
|
|
128
|
+
self._coordinator = BrowserCoordinatorAgent(
|
|
129
|
+
agent_id="browser-coordinator-0",
|
|
130
|
+
bus=self._bus,
|
|
131
|
+
service_names=["browser"],
|
|
132
|
+
bid_timeout_s=bid_timeout_s,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Optional visual regression agent
|
|
136
|
+
self._visual_agent: VisualRegressionAgent | None = None
|
|
137
|
+
if enable_visual_regression:
|
|
138
|
+
self._visual_agent = VisualRegressionAgent(
|
|
139
|
+
agent_id="visual-regression-0",
|
|
140
|
+
bus=self._bus,
|
|
141
|
+
service_names=["browser"],
|
|
142
|
+
baseline_dir=baseline_dir,
|
|
143
|
+
diff_threshold=diff_threshold,
|
|
144
|
+
update_baselines=update_baselines,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Optional accessibility scanner agent
|
|
148
|
+
self._accessibility_agent: AccessibilityScannerAgent | None = None
|
|
149
|
+
if enable_accessibility_scan:
|
|
150
|
+
self._accessibility_agent = AccessibilityScannerAgent(
|
|
151
|
+
agent_id="accessibility-scanner-0",
|
|
152
|
+
bus=self._bus,
|
|
153
|
+
service_names=["browser"],
|
|
154
|
+
min_compliance_score=min_compliance_score,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Optional performance testing agent
|
|
158
|
+
self._performance_agent: PerformanceTestingAgent | None = None
|
|
159
|
+
if enable_performance_testing:
|
|
160
|
+
self._performance_agent = PerformanceTestingAgent(
|
|
161
|
+
agent_id="performance-testing-0",
|
|
162
|
+
bus=self._bus,
|
|
163
|
+
service_names=["browser"],
|
|
164
|
+
custom_thresholds=custom_perf_thresholds,
|
|
165
|
+
min_performance_score=min_performance_score,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Subscribe orchestrator to INTERACTION_RESULT for completion tracking
|
|
169
|
+
subscribed_types: set[MessageType] = {MessageType.INTERACTION_RESULT}
|
|
170
|
+
if enable_accessibility_scan:
|
|
171
|
+
subscribed_types.add(MessageType.ACCESSIBILITY_SCAN_RESULT)
|
|
172
|
+
|
|
173
|
+
self._result_queue: asyncio.Queue[Message] = self._bus.subscribe(
|
|
174
|
+
"functional-orchestrator",
|
|
175
|
+
subscribed_types,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self._completed = 0
|
|
179
|
+
self._start_time: float | None = None
|
|
180
|
+
|
|
181
|
+
# ------------------------------------------------------------------
|
|
182
|
+
# Main entry point
|
|
183
|
+
# ------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
async def run(self, tasks: list[dict[str, Any]]) -> dict[str, Any]:
|
|
186
|
+
"""Dispatch browser tasks and wait for all to complete.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
tasks:
|
|
191
|
+
List of task dicts, each with the shape::
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
"url": "https://example.com",
|
|
195
|
+
"steps": [
|
|
196
|
+
{"action": "fill", "selector": "#email", "value": "..."},
|
|
197
|
+
{"action": "click", "selector": "#submit"},
|
|
198
|
+
],
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
dict
|
|
204
|
+
Summary with ``passed``, ``failed``, ``total``, ``tasks``,
|
|
205
|
+
``elapsed_seconds``.
|
|
206
|
+
"""
|
|
207
|
+
num_tasks = min(len(tasks), self._max_tasks)
|
|
208
|
+
self._start_time = time.monotonic()
|
|
209
|
+
|
|
210
|
+
all_agents = self._browser_agents + [self._coordinator] # type: ignore[list-item]
|
|
211
|
+
if self._visual_agent is not None:
|
|
212
|
+
all_agents = all_agents + [self._visual_agent] # type: ignore[list-item]
|
|
213
|
+
if self._accessibility_agent is not None:
|
|
214
|
+
all_agents = all_agents + [self._accessibility_agent] # type: ignore[list-item]
|
|
215
|
+
if self._performance_agent is not None:
|
|
216
|
+
all_agents = all_agents + [self._performance_agent] # type: ignore[list-item]
|
|
217
|
+
|
|
218
|
+
# Start agent run loops (each calls start() internally)
|
|
219
|
+
agent_tasks = [asyncio.create_task(a.run()) for a in all_agents]
|
|
220
|
+
|
|
221
|
+
# Yield so all run() tasks execute their start() and register subscriptions
|
|
222
|
+
await asyncio.sleep(0)
|
|
223
|
+
|
|
224
|
+
# Start browsers on all executor agents
|
|
225
|
+
for browser_agent in self._browser_agents:
|
|
226
|
+
await browser_agent.start_browser()
|
|
227
|
+
|
|
228
|
+
# Allow subscriptions to propagate
|
|
229
|
+
await asyncio.sleep(0)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Dispatch tasks through the coordinator
|
|
233
|
+
dispatched_ids: list[str] = []
|
|
234
|
+
for task in tasks[:num_tasks]:
|
|
235
|
+
task_id = await self._coordinator.dispatch_browser_task(
|
|
236
|
+
url=task["url"],
|
|
237
|
+
steps=task.get("steps", []),
|
|
238
|
+
)
|
|
239
|
+
dispatched_ids.append(task_id)
|
|
240
|
+
|
|
241
|
+
# Wait for all dispatched tasks to produce results
|
|
242
|
+
await self._monitor_completions(num_tasks)
|
|
243
|
+
|
|
244
|
+
# Yield briefly so coordinator can drain its inbox (it processes
|
|
245
|
+
# INTERACTION_RESULT / DOM_SNAPSHOT / SCREENSHOT_CAPTURED that
|
|
246
|
+
# were queued while the orchestrator was handling completions)
|
|
247
|
+
await asyncio.sleep(0.05)
|
|
248
|
+
|
|
249
|
+
finally:
|
|
250
|
+
# Stop browsers
|
|
251
|
+
for browser_agent in self._browser_agents:
|
|
252
|
+
await browser_agent.stop_browser()
|
|
253
|
+
|
|
254
|
+
# Stop all agents
|
|
255
|
+
for agent in all_agents:
|
|
256
|
+
agent._running = False
|
|
257
|
+
await asyncio.gather(*agent_tasks, return_exceptions=True)
|
|
258
|
+
|
|
259
|
+
# Phase 10.3: best-effort root cause analysis for failed tasks
|
|
260
|
+
await self._schedule_root_cause_analysis()
|
|
261
|
+
|
|
262
|
+
return self.report()
|
|
263
|
+
|
|
264
|
+
# ------------------------------------------------------------------
|
|
265
|
+
# Completion monitoring
|
|
266
|
+
# ------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
async def _monitor_completions(self, expected: int) -> None:
|
|
269
|
+
"""Read INTERACTION_RESULT messages until *expected* tasks complete."""
|
|
270
|
+
while self._completed < expected:
|
|
271
|
+
try:
|
|
272
|
+
await asyncio.wait_for(self._result_queue.get(), timeout=10.0)
|
|
273
|
+
self._completed += 1
|
|
274
|
+
except asyncio.TimeoutError:
|
|
275
|
+
logger.warning(
|
|
276
|
+
"FunctionalTestOrchestrator: no result after 10s "
|
|
277
|
+
"(completed=%d / expected=%d)",
|
|
278
|
+
self._completed, expected,
|
|
279
|
+
)
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
# ------------------------------------------------------------------
|
|
283
|
+
# Report
|
|
284
|
+
# ------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
def report(self) -> dict[str, Any]:
|
|
287
|
+
"""Return structured results for the completed functional test run.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
dict
|
|
292
|
+
``{passed, failed, total, tasks, elapsed_seconds,
|
|
293
|
+
dom_snapshots, screenshots, agent_count}``
|
|
294
|
+
When visual regression is enabled, a ``visual_regression`` key is
|
|
295
|
+
also included.
|
|
296
|
+
When accessibility scanning is enabled, an ``accessibility`` key is
|
|
297
|
+
also included.
|
|
298
|
+
"""
|
|
299
|
+
elapsed = time.monotonic() - (self._start_time or 0.0)
|
|
300
|
+
functional = self._coordinator.get_functional_results()
|
|
301
|
+
|
|
302
|
+
result: dict[str, Any] = {
|
|
303
|
+
"passed": functional["passed"],
|
|
304
|
+
"failed": functional["failed"],
|
|
305
|
+
"total": functional["total"],
|
|
306
|
+
"tasks": functional["tasks"],
|
|
307
|
+
"dom_snapshots": functional["dom_snapshots"],
|
|
308
|
+
"screenshots": functional["screenshots"],
|
|
309
|
+
"elapsed_seconds": round(elapsed, 2),
|
|
310
|
+
"agent_count": len(self._browser_agents) + 1,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if self._visual_agent is not None:
|
|
314
|
+
result["visual_regression"] = self._visual_agent.get_visual_report()
|
|
315
|
+
|
|
316
|
+
if self._accessibility_agent is not None:
|
|
317
|
+
result["accessibility"] = self._accessibility_agent.get_accessibility_report()
|
|
318
|
+
|
|
319
|
+
if self._performance_agent is not None:
|
|
320
|
+
result["performance"] = self._performance_agent.get_performance_report()
|
|
321
|
+
|
|
322
|
+
# Phase 10.3: include any previously computed root cause suggestions
|
|
323
|
+
if hasattr(self, "_root_cause_suggestions"):
|
|
324
|
+
result["root_cause_analysis"] = self._root_cause_suggestions
|
|
325
|
+
|
|
326
|
+
return result
|
|
327
|
+
|
|
328
|
+
# ------------------------------------------------------------------
|
|
329
|
+
# Properties
|
|
330
|
+
# ------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def coordinator(self) -> BrowserCoordinatorAgent:
|
|
334
|
+
return self._coordinator
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def browser_agents(self) -> list[BrowserExecutorAgent]:
|
|
338
|
+
return list(self._browser_agents)
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def visual_agent(self) -> VisualRegressionAgent | None:
|
|
342
|
+
"""The :class:`VisualRegressionAgent` instance, or ``None`` if not enabled."""
|
|
343
|
+
return self._visual_agent
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def accessibility_agent(self) -> AccessibilityScannerAgent | None:
|
|
347
|
+
"""The :class:`AccessibilityScannerAgent` instance, or ``None`` if not enabled."""
|
|
348
|
+
return self._accessibility_agent
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def performance_agent(self) -> PerformanceTestingAgent | None:
|
|
352
|
+
"""The :class:`PerformanceTestingAgent` instance, or ``None`` if not enabled."""
|
|
353
|
+
return self._performance_agent
|
|
354
|
+
|
|
355
|
+
# ------------------------------------------------------------------
|
|
356
|
+
# Unified report generation
|
|
357
|
+
# ------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
async def _schedule_root_cause_analysis(self) -> None:
|
|
360
|
+
"""Best-effort root cause analysis for failed functional test tasks."""
|
|
361
|
+
if self._llm_provider is None:
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
from mannf.product.llm.root_cause_service import ScanRootCauseAnalyzer # noqa: PLC0415
|
|
366
|
+
|
|
367
|
+
functional = self._coordinator.get_functional_results()
|
|
368
|
+
failed_tasks = [
|
|
369
|
+
t for t in functional.get("tasks", [])
|
|
370
|
+
if not t.get("passed", True)
|
|
371
|
+
]
|
|
372
|
+
if not failed_tasks:
|
|
373
|
+
self._root_cause_suggestions: list[dict[str, Any]] = []
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
failures = [
|
|
377
|
+
{
|
|
378
|
+
"id": t.get("task_id", ""),
|
|
379
|
+
"type": "functional",
|
|
380
|
+
"url": t.get("url", ""),
|
|
381
|
+
"error": t.get("error") or t.get("outcome", "Task failed"),
|
|
382
|
+
"severity": "medium",
|
|
383
|
+
}
|
|
384
|
+
for t in failed_tasks
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
analyzer = ScanRootCauseAnalyzer(
|
|
388
|
+
llm_provider=self._llm_provider,
|
|
389
|
+
quota=self._root_cause_quota,
|
|
390
|
+
)
|
|
391
|
+
suggestions = await analyzer.analyze_failures(failures, {"scan_type": "functional"})
|
|
392
|
+
self._root_cause_suggestions = [s.model_dump() for s in suggestions]
|
|
393
|
+
logger.info(
|
|
394
|
+
"FunctionalTestOrchestrator: root cause analysis produced %d suggestion(s)",
|
|
395
|
+
len(suggestions),
|
|
396
|
+
)
|
|
397
|
+
except Exception as exc: # noqa: BLE001
|
|
398
|
+
logger.debug("FunctionalTestOrchestrator: root cause analysis skipped — %s", exc)
|
|
399
|
+
self._root_cause_suggestions = []
|
|
400
|
+
|
|
401
|
+
def generate_report(
|
|
402
|
+
self,
|
|
403
|
+
output_dir: str = "nat_reports",
|
|
404
|
+
formats: list[str] | None = None,
|
|
405
|
+
) -> list[str]:
|
|
406
|
+
"""Generate a unified HTML/JSON report from the last completed run.
|
|
407
|
+
|
|
408
|
+
This method calls :meth:`report` internally to obtain the latest
|
|
409
|
+
results, then passes them to
|
|
410
|
+
:class:`~mannf.core.reporting.unified_report.UnifiedReportGenerator`
|
|
411
|
+
to produce the output files.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
output_dir:
|
|
416
|
+
Directory to write report files into. Created if it does not
|
|
417
|
+
exist. Defaults to ``"nat_reports"``.
|
|
418
|
+
formats:
|
|
419
|
+
List of ``"html"`` and/or ``"json"``. Defaults to both.
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
list[str]
|
|
424
|
+
Absolute paths of the files that were written.
|
|
425
|
+
"""
|
|
426
|
+
report_dict = self.report()
|
|
427
|
+
generator = UnifiedReportGenerator()
|
|
428
|
+
return generator.save_report(report_dict, output_dir=output_dir, formats=formats)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""Messaging sub-package for the Multi-Agent Neural Network Framework."""
|
|
7
|
+
|
|
8
|
+
from mannf.core.messaging.messages import Message, MessageType
|
|
9
|
+
from mannf.core.messaging.bus import MessageBus
|
|
10
|
+
|
|
11
|
+
__all__ = ["Message", "MessageType", "MessageBus"]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""Async publish-subscribe message bus for inter-agent communication.
|
|
7
|
+
|
|
8
|
+
Agents subscribe to one or more :class:`MessageType` values and receive
|
|
9
|
+
matching :class:`Message` objects through an ``asyncio.Queue``. The bus is
|
|
10
|
+
the *only* shared state between agents, keeping coupling low.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Dict, List, Optional, Set
|
|
18
|
+
|
|
19
|
+
from mannf.core.messaging.messages import Message, MessageType
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MessageBus:
|
|
25
|
+
"""Lightweight, in-process publish/subscribe broker.
|
|
26
|
+
|
|
27
|
+
Usage::
|
|
28
|
+
|
|
29
|
+
bus = MessageBus()
|
|
30
|
+
queue = bus.subscribe("agent-1", {MessageType.TEST_RESULT})
|
|
31
|
+
await bus.publish(Message(MessageType.TEST_RESULT, sender_id="agent-2", payload=result))
|
|
32
|
+
msg = await queue.get()
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
# subscriber_id -> (set of subscribed types, asyncio Queue)
|
|
37
|
+
self._subscribers: Dict[str, tuple[Set[MessageType], asyncio.Queue[Message]]] = {}
|
|
38
|
+
self._lock = asyncio.Lock()
|
|
39
|
+
|
|
40
|
+
# ------------------------------------------------------------------
|
|
41
|
+
# Subscription management
|
|
42
|
+
# ------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def subscribe(
|
|
45
|
+
self,
|
|
46
|
+
subscriber_id: str,
|
|
47
|
+
message_types: Set[MessageType],
|
|
48
|
+
maxsize: int = 4096,
|
|
49
|
+
) -> asyncio.Queue[Message]:
|
|
50
|
+
"""Register *subscriber_id* to receive messages of *message_types*.
|
|
51
|
+
|
|
52
|
+
Returns an ``asyncio.Queue`` the subscriber should read from.
|
|
53
|
+
"""
|
|
54
|
+
q: asyncio.Queue[Message] = asyncio.Queue(maxsize=maxsize)
|
|
55
|
+
self._subscribers[subscriber_id] = (message_types, q)
|
|
56
|
+
logger.debug("Subscribed %s to %s", subscriber_id, message_types)
|
|
57
|
+
return q
|
|
58
|
+
|
|
59
|
+
def unsubscribe(self, subscriber_id: str) -> None:
|
|
60
|
+
self._subscribers.pop(subscriber_id, None)
|
|
61
|
+
logger.debug("Unsubscribed %s", subscriber_id)
|
|
62
|
+
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
# Publishing
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
async def publish(self, message: Message) -> int:
|
|
68
|
+
"""Deliver *message* to all matching subscribers.
|
|
69
|
+
|
|
70
|
+
Returns the number of queues the message was delivered to.
|
|
71
|
+
"""
|
|
72
|
+
delivered = 0
|
|
73
|
+
for sub_id, (types, queue) in list(self._subscribers.items()):
|
|
74
|
+
if message.type in types:
|
|
75
|
+
try:
|
|
76
|
+
queue.put_nowait(message)
|
|
77
|
+
delivered += 1
|
|
78
|
+
except asyncio.QueueFull:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"Queue full for subscriber %s; dropping message %s",
|
|
81
|
+
sub_id,
|
|
82
|
+
message.id,
|
|
83
|
+
)
|
|
84
|
+
logger.debug(
|
|
85
|
+
"Published %s from %s to %d subscriber(s)",
|
|
86
|
+
message.type.name,
|
|
87
|
+
message.sender_id,
|
|
88
|
+
delivered,
|
|
89
|
+
)
|
|
90
|
+
return delivered
|
|
91
|
+
|
|
92
|
+
def publish_sync(self, message: Message) -> int:
|
|
93
|
+
"""Synchronous variant (does not await); safe from non-async contexts."""
|
|
94
|
+
delivered = 0
|
|
95
|
+
for sub_id, (types, queue) in list(self._subscribers.items()):
|
|
96
|
+
if message.type in types:
|
|
97
|
+
try:
|
|
98
|
+
queue.put_nowait(message)
|
|
99
|
+
delivered += 1
|
|
100
|
+
except asyncio.QueueFull:
|
|
101
|
+
logger.warning(
|
|
102
|
+
"Queue full for subscriber %s; dropping message %s",
|
|
103
|
+
sub_id,
|
|
104
|
+
message.id,
|
|
105
|
+
)
|
|
106
|
+
return delivered
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def subscriber_count(self) -> int:
|
|
110
|
+
return len(self._subscribers)
|
|
111
|
+
|
|
112
|
+
def subscriber_ids(self) -> List[str]:
|
|
113
|
+
return list(self._subscribers.keys())
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Copyright (C) 2026 Brad Guider
|
|
2
|
+
# This file is part of NAT (Neural Agent Testing Framework).
|
|
3
|
+
# Licensed under the AGPL-3.0. See LICENSE for details.
|
|
4
|
+
# Commercial licensing available — see COMMERCIAL_LICENSE.md.
|
|
5
|
+
|
|
6
|
+
"""Message types used by agents to communicate over the message bus."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import uuid
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum, auto
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MessageType(Enum):
|
|
17
|
+
# Lifecycle
|
|
18
|
+
AGENT_STARTED = auto()
|
|
19
|
+
AGENT_STOPPED = auto()
|
|
20
|
+
|
|
21
|
+
# Test coordination (basic orchestrator)
|
|
22
|
+
TEST_REQUEST = auto() # ask TestAgent to generate a test case
|
|
23
|
+
TEST_CASE = auto() # a generated TestCase ready for execution
|
|
24
|
+
TEST_RESULT = auto() # result after execution
|
|
25
|
+
TEST_VERDICT = auto() # OracleAgent's pass/fail decision
|
|
26
|
+
|
|
27
|
+
# Monitoring
|
|
28
|
+
METRICS_UPDATE = auto() # MonitorAgent reports system metrics
|
|
29
|
+
ANOMALY_DETECTED = auto() # MonitorAgent detected an anomaly
|
|
30
|
+
|
|
31
|
+
# Adaptation
|
|
32
|
+
STRATEGY_UPDATE = auto() # AdaptiveController broadcasts new weights/strategy
|
|
33
|
+
FEEDBACK = auto() # generic feedback signal for NN training
|
|
34
|
+
|
|
35
|
+
# ── NAT / ECNP messages (Extended Contract Net Protocol) ────────────
|
|
36
|
+
TASK_ANNOUNCEMENT = auto() # CoordinatorAgent broadcasts a testing task
|
|
37
|
+
TASK_BID = auto() # ExecutorAgent submits a bid for a task
|
|
38
|
+
CONTRACT_AWARD = auto() # CoordinatorAgent awards the contract to a bidder
|
|
39
|
+
CONTRACT_RESULT = auto() # ExecutorAgent reports execution outcome to Coordinator
|
|
40
|
+
|
|
41
|
+
# BDI belief propagation
|
|
42
|
+
BELIEF_UPDATE = auto() # AnalyzerAgent broadcasts updated fault-likelihood beliefs
|
|
43
|
+
BELIEF_REQUEST = auto() # Any agent requests the current belief snapshot
|
|
44
|
+
|
|
45
|
+
# ── Browser / Functional Testing messages ────────────────────────────
|
|
46
|
+
INTERACTION_REQUEST = auto() # Request a browser interaction (click, fill, navigate)
|
|
47
|
+
INTERACTION_RESULT = auto() # Result of a browser interaction
|
|
48
|
+
DOM_SNAPSHOT = auto() # DOM snapshot captured by a browser agent
|
|
49
|
+
SCREENSHOT_CAPTURED = auto() # Screenshot taken by a browser agent
|
|
50
|
+
|
|
51
|
+
# ── Visual Regression messages ────────────────────────────────────────
|
|
52
|
+
VISUAL_DIFF_DETECTED = auto() # Screenshot differs from baseline beyond threshold
|
|
53
|
+
VISUAL_BASELINE_SAVED = auto() # New baseline screenshot stored
|
|
54
|
+
|
|
55
|
+
# ── Accessibility Scanning messages ──────────────────────────────────
|
|
56
|
+
ACCESSIBILITY_SCAN_REQUEST = auto() # Request an accessibility scan on a page
|
|
57
|
+
ACCESSIBILITY_SCAN_RESULT = auto() # Result of an accessibility scan (violations found)
|
|
58
|
+
|
|
59
|
+
# ── Performance Testing messages ──────────────────────────────────────
|
|
60
|
+
PERFORMANCE_METRICS_CAPTURED = auto() # Performance metrics collected from a page
|
|
61
|
+
PERFORMANCE_THRESHOLD_VIOLATED = auto() # A performance metric exceeded its threshold
|
|
62
|
+
|
|
63
|
+
# ── Web Crawler messages ──────────────────────────────────────────────
|
|
64
|
+
CRAWL_PROGRESS = auto() # WebCrawlerAgent reports discovery progress (live updates)
|
|
65
|
+
CRAWL_RESULT = auto() # WebCrawlerAgent publishes completed discovery model
|
|
66
|
+
|
|
67
|
+
# ── Autonomous Test Loop messages ─────────────────────────────────────
|
|
68
|
+
AUTONOMOUS_ITERATION = auto() # AutonomousTestLoopAgent completed one iteration
|
|
69
|
+
AUTONOMOUS_COMPLETE = auto() # AutonomousTestLoopAgent finished the full run
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Message:
|
|
74
|
+
"""Envelope that carries a payload between agents on the message bus."""
|
|
75
|
+
|
|
76
|
+
type: MessageType
|
|
77
|
+
sender_id: str
|
|
78
|
+
payload: Any = None
|
|
79
|
+
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
80
|
+
correlation_id: Optional[str] = None # links replies to requests
|
|
81
|
+
|
|
82
|
+
def reply(self, msg_type: MessageType, payload: Any, sender_id: str) -> "Message":
|
|
83
|
+
"""Convenience constructor for a reply to this message."""
|
|
84
|
+
return Message(
|
|
85
|
+
type=msg_type,
|
|
86
|
+
sender_id=sender_id,
|
|
87
|
+
payload=payload,
|
|
88
|
+
correlation_id=self.id,
|
|
89
|
+
)
|