agentic-qa-maestro 0.1.0__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.
- agentic_qa_maestro/__init__.py +3 -0
- agentic_qa_maestro/agents/__init__.py +1 -0
- agentic_qa_maestro/agents/factory.py +213 -0
- agentic_qa_maestro/config.py +115 -0
- agentic_qa_maestro/main.py +469 -0
- agentic_qa_maestro/models/__init__.py +1 -0
- agentic_qa_maestro/models/azure_openai.py +79 -0
- agentic_qa_maestro/observability/__init__.py +1 -0
- agentic_qa_maestro/observability/tracing.py +80 -0
- agentic_qa_maestro/resources/app_flows/example-app.yaml +28 -0
- agentic_qa_maestro/resources/application.yaml +118 -0
- agentic_qa_maestro/resources/example.env +27 -0
- agentic_qa_maestro/runtime_assets.py +57 -0
- agentic_qa_maestro/teams/__init__.py +1 -0
- agentic_qa_maestro/teams/group_chat_team.py +39 -0
- agentic_qa_maestro/teams/sequential_team.py +41 -0
- agentic_qa_maestro/tools/__init__.py +1 -0
- agentic_qa_maestro/tools/app_knowledge_tools.py +118 -0
- agentic_qa_maestro/tools/browser_tools.py +419 -0
- agentic_qa_maestro/tools/jira_tools.py +360 -0
- agentic_qa_maestro/tools/local_tools.py +77 -0
- agentic_qa_maestro/web_ui/__init__.py +1 -0
- agentic_qa_maestro/web_ui/app.py +558 -0
- agentic_qa_maestro/web_ui/templates/index.html +631 -0
- agentic_qa_maestro-0.1.0.dist-info/METADATA +128 -0
- agentic_qa_maestro-0.1.0.dist-info/RECORD +30 -0
- agentic_qa_maestro-0.1.0.dist-info/WHEEL +5 -0
- agentic_qa_maestro-0.1.0.dist-info/entry_points.txt +2 -0
- agentic_qa_maestro-0.1.0.dist-info/licenses/LICENSE +182 -0
- agentic_qa_maestro-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent definitions for Agentic QA Maestro."""
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent factory for Agentic QA Maestro.
|
|
3
|
+
|
|
4
|
+
Creates Agent Framework Agent instances from YAML configuration,
|
|
5
|
+
assigning system prompts, tools, and model clients.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from agent_framework import Agent
|
|
11
|
+
from agent_framework.openai import OpenAIChatCompletionClient
|
|
12
|
+
|
|
13
|
+
from agentic_qa_maestro.tools.local_tools import collect_pytest_tests, get_current_time, run_pytest
|
|
14
|
+
from agentic_qa_maestro.tools.jira_tools import JIRA_TOOLS
|
|
15
|
+
from agentic_qa_maestro.tools.browser_tools import BROWSER_TOOLS
|
|
16
|
+
from agentic_qa_maestro.tools.app_knowledge_tools import APP_KNOWLEDGE_TOOLS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DEFAULT_PROMPTS = {
|
|
20
|
+
"orchestrator": (
|
|
21
|
+
"You are the QA Maestro orchestrator. You coordinate a 6-phase E2E testing pipeline.\n\n"
|
|
22
|
+
"When given a JIRA ticket and a target application URL, execute these phases IN ORDER:\n\n"
|
|
23
|
+
"PHASE 1 — REQUIREMENT ANALYSIS:\n"
|
|
24
|
+
" Ask jira_agent to fetch the ticket and extract all acceptance criteria (AC-1, AC-2, etc.),\n"
|
|
25
|
+
" testable scenarios, and functional requirements. This is the foundation.\n\n"
|
|
26
|
+
"PHASE 2 — TARGETED APP DISCOVERY:\n"
|
|
27
|
+
" Ask browser_agent to: start_browser → authenticate (if auth config provided) →\n"
|
|
28
|
+
" open_url → get_page_content to discover real UI elements, navigation, forms.\n"
|
|
29
|
+
" The browser agent should take a screenshot and report what it finds.\n"
|
|
30
|
+
" Limit to 5-6 tool calls for quick recon — just enough to map the UI.\n\n"
|
|
31
|
+
"PHASE 2.5 — KNOWLEDGE ENRICHMENT (optional):\n"
|
|
32
|
+
" Ask app_knowledge_agent to load pre-recorded app flow data from app_flows/*.yaml.\n"
|
|
33
|
+
" This provides known selectors, navigation flows, and feature availability maps\n"
|
|
34
|
+
" that supplement the live browser discovery. Merge both reports for Phase 3.\n\n"
|
|
35
|
+
"PHASE 3 — TEST CASE GENERATION:\n"
|
|
36
|
+
" Using Phase 1 requirements, Phase 2 live discovery, AND Phase 2.5 app knowledge,\n"
|
|
37
|
+
" ask jira_agent to\n"
|
|
38
|
+
" generate comprehensive test cases. Each test case must include:\n"
|
|
39
|
+
" - Test ID, Title, Priority\n"
|
|
40
|
+
" - Preconditions, Steps (with real selectors from Phase 2), Expected Result\n"
|
|
41
|
+
" - Acceptance Criteria coverage mapping\n"
|
|
42
|
+
" Include 'Missing Feature' tests for AC items not found in the UI.\n\n"
|
|
43
|
+
"PHASE 4 — TEST EXECUTION:\n"
|
|
44
|
+
" Ask browser_agent to execute ALL test cases against the live application.\n"
|
|
45
|
+
" For each test: navigate, interact with UI elements, validate expected results,\n"
|
|
46
|
+
" take screenshots as evidence. Report PASS/FAIL for each test.\n\n"
|
|
47
|
+
"PHASE 5 — BUG REPORTING:\n"
|
|
48
|
+
" For any FAILED tests, ask jira_agent to create Bug tickets using jira_create_bug.\n"
|
|
49
|
+
" Each bug should include: steps to reproduce, expected vs actual result, priority.\n"
|
|
50
|
+
" Link bugs to the parent story. Post a summary comment on the original ticket.\n\n"
|
|
51
|
+
"PHASE 6 — CLEANUP:\n"
|
|
52
|
+
" Ask browser_agent to close_browser and provide a final summary.\n\n"
|
|
53
|
+
"RULES:\n"
|
|
54
|
+
"- Never ask the user for input — work autonomously.\n"
|
|
55
|
+
"- If no target URL is provided, skip Phases 2, 4, 6 and do analysis-only mode.\n"
|
|
56
|
+
"- Always post results to JIRA via jira_add_comment.\n"
|
|
57
|
+
"- Report final status: total tests, passed, failed, bugs created."
|
|
58
|
+
),
|
|
59
|
+
"browser_agent": (
|
|
60
|
+
"You are a browser automation agent specialized in UI testing with Playwright.\n\n"
|
|
61
|
+
"CAPABILITIES:\n"
|
|
62
|
+
"- start_browser: Launch browser (always call first)\n"
|
|
63
|
+
"- authenticate_aad: Handle Azure AD authentication if config provided\n"
|
|
64
|
+
"- open_url: Navigate to target application\n"
|
|
65
|
+
"- get_page_content: Read page HTML to discover real selectors (ALWAYS do this before clicking/filling)\n"
|
|
66
|
+
"- click: Click elements using CSS/XPath selectors\n"
|
|
67
|
+
"- fill: Fill form inputs (use CREDENTIAL_USERNAME/CREDENTIAL_PASSWORD for login fields)\n"
|
|
68
|
+
"- select_option: Select dropdown options\n"
|
|
69
|
+
"- wait_for_selector: Wait for dynamic elements\n"
|
|
70
|
+
"- get_text: Read text from specific elements\n"
|
|
71
|
+
"- screenshot: Capture visual evidence\n"
|
|
72
|
+
"- close_browser: Cleanup when done\n\n"
|
|
73
|
+
"CRITICAL RULES:\n"
|
|
74
|
+
"1. ALWAYS call get_page_content BEFORE interacting with elements — never guess selectors.\n"
|
|
75
|
+
"2. Use the REAL selectors you discover from the page content.\n"
|
|
76
|
+
"3. Take screenshots after key actions as test evidence.\n"
|
|
77
|
+
"4. For each test case, report PASS or FAIL with clear reasoning.\n"
|
|
78
|
+
"5. If an element is not found, report it as a potential Missing Feature.\n"
|
|
79
|
+
"6. Handle errors gracefully — if one test fails, continue with the next.\n"
|
|
80
|
+
"7. After test execution, provide a structured summary:\n"
|
|
81
|
+
" Test ID | Title | Status (PASS/FAIL) | Evidence | Notes"
|
|
82
|
+
),
|
|
83
|
+
"api_agent": (
|
|
84
|
+
"You are an API testing agent. You test REST APIs by:\n"
|
|
85
|
+
"- Sending HTTP requests (GET, POST, PUT, DELETE)\n"
|
|
86
|
+
"- Validating response status codes, headers, and body content\n"
|
|
87
|
+
"- Testing error scenarios and edge cases\n"
|
|
88
|
+
"- Verifying API contract compliance\n\n"
|
|
89
|
+
"Report results with request/response details and assertion outcomes."
|
|
90
|
+
),
|
|
91
|
+
"jira_agent": (
|
|
92
|
+
"You are a JIRA integration agent with full JIRA API access.\n\n"
|
|
93
|
+
"TOOLS AVAILABLE:\n"
|
|
94
|
+
"- jira_get_issue: Fetch issue details (summary, description, AC, status)\n"
|
|
95
|
+
"- jira_add_comment: Post comments with test results\n"
|
|
96
|
+
"- jira_get_comments: Read existing comments\n"
|
|
97
|
+
"- jira_search_issues: Search with JQL\n"
|
|
98
|
+
"- jira_create_issue: Create new issues\n"
|
|
99
|
+
"- jira_create_bug: Create Bug with labels and link to parent story\n"
|
|
100
|
+
"- jira_transition_issue: Move issues between statuses\n\n"
|
|
101
|
+
"RESPONSIBILITIES:\n"
|
|
102
|
+
"1. When analyzing a ticket: Extract ALL acceptance criteria, testable scenarios,\n"
|
|
103
|
+
" business rules, and edge cases from the description.\n"
|
|
104
|
+
"2. When generating test cases: Create detailed test cases with IDs, steps,\n"
|
|
105
|
+
" expected results, and AC coverage mapping.\n"
|
|
106
|
+
"3. When reporting bugs: Create Bug tickets with:\n"
|
|
107
|
+
" - Clear summary: '[E2E] <brief description>'\n"
|
|
108
|
+
" - Steps to reproduce (from test execution)\n"
|
|
109
|
+
" - Expected vs Actual result\n"
|
|
110
|
+
" - Priority based on AC importance\n"
|
|
111
|
+
" - Link to parent story via parent_story_key\n"
|
|
112
|
+
"4. Always post a final summary comment on the original ticket.\n\n"
|
|
113
|
+
"Never ask the user for info — use your tools to fetch what you need."
|
|
114
|
+
),
|
|
115
|
+
"research_agent": (
|
|
116
|
+
"You are a web research agent. You help the QA team by:\n"
|
|
117
|
+
"- Researching documentation and specifications\n"
|
|
118
|
+
"- Looking up error messages and known issues\n"
|
|
119
|
+
"- Finding relevant test data or configuration information\n\n"
|
|
120
|
+
"Provide concise, relevant summaries of findings."
|
|
121
|
+
),
|
|
122
|
+
"test_runner_agent": (
|
|
123
|
+
"You are a test execution agent. You manage pytest test suites:\n"
|
|
124
|
+
"- Collect available tests from specified paths\n"
|
|
125
|
+
"- Run tests with specific markers or filters\n"
|
|
126
|
+
"- Analyze test results and identify failures\n"
|
|
127
|
+
"- Suggest fixes for common test failures\n\n"
|
|
128
|
+
"Report results with counts (passed/failed/errors) and failure details."
|
|
129
|
+
),
|
|
130
|
+
"app_knowledge_agent": (
|
|
131
|
+
"You are an app knowledge agent. You provide pre-recorded application context\n"
|
|
132
|
+
"to supplement live browser discovery.\n\n"
|
|
133
|
+
"CAPABILITIES:\n"
|
|
134
|
+
"- get_app_knowledge: Load all known pages, selectors, navigation flows,\n"
|
|
135
|
+
" and feature availability from app_flows/*.yaml files\n"
|
|
136
|
+
"- list_app_flows: List available app flow definition files\n\n"
|
|
137
|
+
"When called, load and return the full app knowledge report so the orchestrator\n"
|
|
138
|
+
"can combine it with live discovery results for better test case generation.\n"
|
|
139
|
+
"If no app flow files exist, report that and the pipeline will proceed with\n"
|
|
140
|
+
"live discovery only."
|
|
141
|
+
),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def create_agent(
|
|
146
|
+
agent_name: str,
|
|
147
|
+
agent_config: Dict[str, Any],
|
|
148
|
+
model_client: OpenAIChatCompletionClient,
|
|
149
|
+
tools: Optional[List[Any]] = None,
|
|
150
|
+
) -> Agent:
|
|
151
|
+
"""Create a single Agent Framework Agent from configuration."""
|
|
152
|
+
system_prompt = agent_config.get("prompt", DEFAULT_PROMPTS.get(agent_name, ""))
|
|
153
|
+
description = agent_config.get("description", f"Agent '{agent_name}' for QA automation tasks")
|
|
154
|
+
|
|
155
|
+
agent_tools = list(tools or [])
|
|
156
|
+
|
|
157
|
+
if agent_name == "test_runner_agent":
|
|
158
|
+
agent_tools.extend([run_pytest, collect_pytest_tests, get_current_time])
|
|
159
|
+
elif agent_name == "browser_agent":
|
|
160
|
+
agent_tools.extend(BROWSER_TOOLS)
|
|
161
|
+
if get_current_time not in agent_tools:
|
|
162
|
+
agent_tools.append(get_current_time)
|
|
163
|
+
elif agent_name in ("jira_agent", "orchestrator"):
|
|
164
|
+
agent_tools.extend(JIRA_TOOLS)
|
|
165
|
+
if get_current_time not in agent_tools:
|
|
166
|
+
agent_tools.append(get_current_time)
|
|
167
|
+
elif agent_name == "app_knowledge_agent":
|
|
168
|
+
agent_tools.extend(APP_KNOWLEDGE_TOOLS)
|
|
169
|
+
elif get_current_time not in agent_tools:
|
|
170
|
+
agent_tools.append(get_current_time)
|
|
171
|
+
|
|
172
|
+
return Agent(
|
|
173
|
+
name=agent_name,
|
|
174
|
+
client=model_client,
|
|
175
|
+
instructions=system_prompt,
|
|
176
|
+
description=description,
|
|
177
|
+
tools=agent_tools,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def create_agents_from_config(
|
|
182
|
+
agents_config: Dict[str, Any],
|
|
183
|
+
model_clients: Dict[str, OpenAIChatCompletionClient],
|
|
184
|
+
mcp_tools: Optional[Dict[str, List[Any]]] = None,
|
|
185
|
+
) -> Dict[str, Agent]:
|
|
186
|
+
"""Create all agents from YAML configuration."""
|
|
187
|
+
agents = {}
|
|
188
|
+
mcp_tools = mcp_tools or {}
|
|
189
|
+
|
|
190
|
+
for agent_name, agent_config in agents_config.items():
|
|
191
|
+
model_name = agent_config.get("model", "default")
|
|
192
|
+
if model_name not in model_clients:
|
|
193
|
+
available = list(model_clients.keys())
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Agent '{agent_name}' references model '{model_name}' "
|
|
196
|
+
f"but available models are: {available}"
|
|
197
|
+
)
|
|
198
|
+
model_client = model_clients[model_name]
|
|
199
|
+
|
|
200
|
+
agent_tool_list: List[Any] = []
|
|
201
|
+
tool_refs = agent_config.get("tools", [])
|
|
202
|
+
for tool_ref in tool_refs:
|
|
203
|
+
if tool_ref in mcp_tools:
|
|
204
|
+
agent_tool_list.extend(mcp_tools[tool_ref])
|
|
205
|
+
|
|
206
|
+
agents[agent_name] = create_agent(
|
|
207
|
+
agent_name=agent_name,
|
|
208
|
+
agent_config=agent_config,
|
|
209
|
+
model_client=model_client,
|
|
210
|
+
tools=agent_tool_list,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return agents
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loader for Agentic QA Maestro.
|
|
3
|
+
|
|
4
|
+
Loads application.yaml with environment variable substitution (${env:VAR})
|
|
5
|
+
and self-referencing templates (${this:path.to.value}).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import copy
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
from agentic_qa_maestro.runtime_assets import read_packaged_text
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConfigError(Exception):
|
|
21
|
+
"""Raised when there's an error in the application configuration."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AppConfig:
|
|
25
|
+
"""Configuration manager for Agentic QA Maestro application."""
|
|
26
|
+
|
|
27
|
+
_ENV_PATTERN = re.compile(r"\$\{env:([^}]+)\}")
|
|
28
|
+
_THIS_PATTERN = re.compile(r"\$\{this:([^}]+)\}")
|
|
29
|
+
|
|
30
|
+
def __init__(self, config_path: Optional[str] = None, env_file: Optional[str] = None):
|
|
31
|
+
env_path = env_file or ".env"
|
|
32
|
+
if Path(env_path).exists():
|
|
33
|
+
load_dotenv(env_path)
|
|
34
|
+
|
|
35
|
+
config_path = config_path or "application.yaml"
|
|
36
|
+
config_file = Path(config_path)
|
|
37
|
+
if config_file.exists():
|
|
38
|
+
with open(config_file, "r") as f:
|
|
39
|
+
raw_config = yaml.safe_load(f)
|
|
40
|
+
elif config_path == "application.yaml":
|
|
41
|
+
raw_config = yaml.safe_load(read_packaged_text("application.yaml"))
|
|
42
|
+
else:
|
|
43
|
+
raise ConfigError(f"Configuration file not found: {config_path}")
|
|
44
|
+
|
|
45
|
+
if not isinstance(raw_config, dict):
|
|
46
|
+
raise ConfigError("Configuration must be a YAML dictionary")
|
|
47
|
+
|
|
48
|
+
self._raw = raw_config
|
|
49
|
+
self._config = self._apply_substitutions(copy.deepcopy(raw_config))
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def data(self) -> Dict[str, Any]:
|
|
53
|
+
return self._config
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def models(self) -> Dict[str, Any]:
|
|
57
|
+
return self._config.get("models", {})
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def agents(self) -> Dict[str, Any]:
|
|
61
|
+
return self._config.get("agents", {})
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def mcp_servers(self) -> Dict[str, Any]:
|
|
65
|
+
return self._config.get("mcp", {}).get("servers", {})
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def teams(self) -> Dict[str, Any]:
|
|
69
|
+
return self._config.get("teams", {})
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def observability(self) -> Dict[str, Any]:
|
|
73
|
+
return self._config.get("observability", {})
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def web_ui(self) -> Dict[str, Any]:
|
|
77
|
+
return self._config.get("web_ui", {})
|
|
78
|
+
|
|
79
|
+
def _apply_substitutions(self, obj: Any) -> Any:
|
|
80
|
+
if isinstance(obj, str):
|
|
81
|
+
return self._substitute_string(obj)
|
|
82
|
+
elif isinstance(obj, dict):
|
|
83
|
+
return {k: self._apply_substitutions(v) for k, v in obj.items()}
|
|
84
|
+
elif isinstance(obj, list):
|
|
85
|
+
return [self._apply_substitutions(item) for item in obj]
|
|
86
|
+
return obj
|
|
87
|
+
|
|
88
|
+
def _substitute_string(self, value: str) -> str:
|
|
89
|
+
result = self._ENV_PATTERN.sub(self._env_replacer, value)
|
|
90
|
+
result = self._THIS_PATTERN.sub(self._this_replacer, result)
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
def _env_replacer(self, match: re.Match) -> str:
|
|
94
|
+
var_name = match.group(1)
|
|
95
|
+
value = os.environ.get(var_name)
|
|
96
|
+
if value is None:
|
|
97
|
+
raise ConfigError(
|
|
98
|
+
f"Environment variable '{var_name}' is not set (referenced as ${{env:{var_name}}})"
|
|
99
|
+
)
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
def _this_replacer(self, match: re.Match) -> str:
|
|
103
|
+
path = match.group(1)
|
|
104
|
+
keys = path.split(".")
|
|
105
|
+
current = self._raw
|
|
106
|
+
for key in keys:
|
|
107
|
+
if isinstance(current, dict) and key in current:
|
|
108
|
+
current = current[key]
|
|
109
|
+
else:
|
|
110
|
+
raise ConfigError(
|
|
111
|
+
f"Config path '{path}' not found (referenced as ${{this:{path}}})"
|
|
112
|
+
)
|
|
113
|
+
if not isinstance(current, str):
|
|
114
|
+
return str(current)
|
|
115
|
+
return current
|