alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__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.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test execution utilities for Alita CLI.
|
|
3
|
+
|
|
4
|
+
This package provides utilities for executing test cases, including:
|
|
5
|
+
- Test case parsing
|
|
6
|
+
- Prompt building
|
|
7
|
+
- Result validation
|
|
8
|
+
- Execution logging
|
|
9
|
+
- Executor cache management
|
|
10
|
+
- Agent setup and loading
|
|
11
|
+
- Test case discovery
|
|
12
|
+
- Test data generation
|
|
13
|
+
- Test execution and validation
|
|
14
|
+
- Result reporting
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .parser import parse_test_case, resolve_toolkit_config_path
|
|
18
|
+
from .prompts import (
|
|
19
|
+
build_bulk_data_gen_prompt,
|
|
20
|
+
build_single_test_execution_prompt,
|
|
21
|
+
build_single_test_validation_prompt
|
|
22
|
+
)
|
|
23
|
+
from .validation import (
|
|
24
|
+
extract_json_from_text,
|
|
25
|
+
create_fallback_result_for_test,
|
|
26
|
+
print_validation_diagnostics
|
|
27
|
+
)
|
|
28
|
+
from .logger import TestLogCapture
|
|
29
|
+
from .executor import create_executor_from_cache, cleanup_executor_cache
|
|
30
|
+
from .utils import extract_toolkit_name
|
|
31
|
+
from .setup import (
|
|
32
|
+
load_test_runner_agent,
|
|
33
|
+
load_data_generator_agent,
|
|
34
|
+
load_validator_agent
|
|
35
|
+
)
|
|
36
|
+
from .discovery import (
|
|
37
|
+
discover_test_case_files,
|
|
38
|
+
validate_test_case_files,
|
|
39
|
+
print_test_execution_header
|
|
40
|
+
)
|
|
41
|
+
from .data_generation import execute_bulk_data_generation
|
|
42
|
+
from .test_runner import execute_single_test_case, validate_single_test_case
|
|
43
|
+
from .reporting import (
|
|
44
|
+
generate_summary_report,
|
|
45
|
+
save_structured_report,
|
|
46
|
+
print_test_execution_summary
|
|
47
|
+
)
|
|
48
|
+
from .workflow import (
|
|
49
|
+
parse_all_test_cases,
|
|
50
|
+
filter_test_cases_needing_data_gen,
|
|
51
|
+
execute_all_test_cases
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Parser
|
|
56
|
+
'parse_test_case',
|
|
57
|
+
'resolve_toolkit_config_path',
|
|
58
|
+
# Prompts
|
|
59
|
+
'build_bulk_data_gen_prompt',
|
|
60
|
+
'build_single_test_execution_prompt',
|
|
61
|
+
'build_single_test_validation_prompt',
|
|
62
|
+
# Validation
|
|
63
|
+
'extract_json_from_text',
|
|
64
|
+
'create_fallback_result_for_test',
|
|
65
|
+
'print_validation_diagnostics',
|
|
66
|
+
# Logger
|
|
67
|
+
'TestLogCapture',
|
|
68
|
+
# Executor
|
|
69
|
+
'create_executor_from_cache',
|
|
70
|
+
'cleanup_executor_cache',
|
|
71
|
+
# Utils
|
|
72
|
+
'extract_toolkit_name',
|
|
73
|
+
# Setup
|
|
74
|
+
'load_test_runner_agent',
|
|
75
|
+
'load_data_generator_agent',
|
|
76
|
+
'load_validator_agent',
|
|
77
|
+
# Discovery
|
|
78
|
+
'discover_test_case_files',
|
|
79
|
+
'validate_test_case_files',
|
|
80
|
+
'print_test_execution_header',
|
|
81
|
+
# Data Generation
|
|
82
|
+
'execute_bulk_data_generation',
|
|
83
|
+
# Test Runner
|
|
84
|
+
'execute_single_test_case',
|
|
85
|
+
'validate_single_test_case',
|
|
86
|
+
# Reporting
|
|
87
|
+
'generate_summary_report',
|
|
88
|
+
'save_structured_report',
|
|
89
|
+
'print_test_execution_summary',
|
|
90
|
+
# Workflow
|
|
91
|
+
'parse_all_test_cases',
|
|
92
|
+
'filter_test_cases_needing_data_gen',
|
|
93
|
+
'execute_all_test_cases',
|
|
94
|
+
]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bulk test data generation utilities.
|
|
3
|
+
|
|
4
|
+
Handles executing the data generator agent to provision test data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sqlite3
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any, List, Optional
|
|
11
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
12
|
+
|
|
13
|
+
from langchain_core.runnables import RunnableConfig
|
|
14
|
+
|
|
15
|
+
from ..callbacks import create_cli_callback
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def execute_bulk_data_generation(
|
|
21
|
+
data_gen_def: Dict[str, Any],
|
|
22
|
+
test_cases_needing_data_gen: List[Dict[str, Any]],
|
|
23
|
+
parsed_test_cases: List[Dict[str, Any]],
|
|
24
|
+
test_cases_path: Path,
|
|
25
|
+
client,
|
|
26
|
+
config,
|
|
27
|
+
model: Optional[str],
|
|
28
|
+
temperature: Optional[float],
|
|
29
|
+
max_tokens: Optional[int],
|
|
30
|
+
work_dir: str,
|
|
31
|
+
master_log,
|
|
32
|
+
setup_executor_func,
|
|
33
|
+
verbose: bool = True,
|
|
34
|
+
debug: bool = False,
|
|
35
|
+
) -> List[Dict[str, str]]:
|
|
36
|
+
"""Execute bulk test data generation.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
data_gen_def: Data generator agent definition
|
|
40
|
+
test_cases_needing_data_gen: Test cases requiring data generation
|
|
41
|
+
parsed_test_cases: All parsed test cases
|
|
42
|
+
test_cases_path: Path to test cases directory
|
|
43
|
+
client: API client
|
|
44
|
+
config: CLI configuration
|
|
45
|
+
model: Model override
|
|
46
|
+
temperature: Temperature override
|
|
47
|
+
max_tokens: Max tokens override
|
|
48
|
+
work_dir: Working directory
|
|
49
|
+
master_log: Log capture instance
|
|
50
|
+
setup_executor_func: Function to setup executor
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Chat history list from data generation
|
|
54
|
+
"""
|
|
55
|
+
from .parser import resolve_toolkit_config_path
|
|
56
|
+
from .prompts import build_bulk_data_gen_prompt
|
|
57
|
+
from ..agent_ui import extract_output_from_result
|
|
58
|
+
|
|
59
|
+
master_log.print(f"\n[bold yellow]🔧 Bulk Test Data Generation[/bold yellow]")
|
|
60
|
+
master_log.print(f"Generating test data for {len(test_cases_needing_data_gen)} test cases...\n")
|
|
61
|
+
master_log.print(f"[dim]Skipping {len(parsed_test_cases) - len(test_cases_needing_data_gen)} test cases with generateTestData: false[/dim]\n")
|
|
62
|
+
|
|
63
|
+
bulk_data_gen_prompt = build_bulk_data_gen_prompt(test_cases_needing_data_gen)
|
|
64
|
+
master_log.print(f"Executing test data generation prompt \n{bulk_data_gen_prompt}\n")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# Setup data generator agent
|
|
68
|
+
bulk_memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
|
|
69
|
+
|
|
70
|
+
# Use first test case's config or empty tuple
|
|
71
|
+
first_config_path = None
|
|
72
|
+
if parsed_test_cases:
|
|
73
|
+
first_tc = parsed_test_cases[0]
|
|
74
|
+
first_config_path = resolve_toolkit_config_path(
|
|
75
|
+
first_tc['data'].get('config_path', ''),
|
|
76
|
+
first_tc['file'],
|
|
77
|
+
test_cases_path
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
data_gen_config_tuple = (first_config_path,) if first_config_path else ()
|
|
81
|
+
data_gen_executor, _, _, _, _, _, _ = setup_executor_func(
|
|
82
|
+
client, data_gen_def, data_gen_config_tuple, config,
|
|
83
|
+
model, temperature, max_tokens, bulk_memory, work_dir
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if data_gen_executor:
|
|
87
|
+
master_log.print("\n" + "=" * 80)
|
|
88
|
+
master_log.print("[bold yellow]📋 BULK DATA GENERATION[/bold yellow]")
|
|
89
|
+
master_log.print("=" * 80 + "\n")
|
|
90
|
+
invoke_config = None
|
|
91
|
+
if verbose:
|
|
92
|
+
cli_callback = create_cli_callback(verbose=True, debug=debug)
|
|
93
|
+
invoke_config = RunnableConfig(callbacks=[cli_callback])
|
|
94
|
+
with master_log.status("[yellow]Generating test data for all test cases...[/yellow]", spinner="dots"):
|
|
95
|
+
bulk_gen_result = data_gen_executor.invoke(
|
|
96
|
+
{
|
|
97
|
+
"input": bulk_data_gen_prompt,
|
|
98
|
+
"chat_history": [],
|
|
99
|
+
},
|
|
100
|
+
config=invoke_config,
|
|
101
|
+
)
|
|
102
|
+
bulk_gen_output = extract_output_from_result(bulk_gen_result)
|
|
103
|
+
master_log.print(f"[green]✓ Bulk test data generation completed[/green]")
|
|
104
|
+
master_log.print(f"\n{bulk_gen_output}\n")
|
|
105
|
+
|
|
106
|
+
# Store chat history from data generation to pass to test executors
|
|
107
|
+
return [
|
|
108
|
+
{"role": "user", "content": bulk_data_gen_prompt},
|
|
109
|
+
{"role": "assistant", "content": bulk_gen_output}
|
|
110
|
+
]
|
|
111
|
+
else:
|
|
112
|
+
master_log.print(f"[yellow]⚠ Warning: Data generator has no executor[/yellow]\n")
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
master_log.print(f"[yellow]⚠ Warning: Bulk data generation failed: {e}[/yellow]")
|
|
117
|
+
master_log.print("[yellow]Continuing with test execution...[/yellow]\n")
|
|
118
|
+
logger.debug(f"Bulk data generation error: {e}", exc_info=True)
|
|
119
|
+
return []
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test case discovery and filtering utilities.
|
|
3
|
+
|
|
4
|
+
Handles finding test case files and filtering based on user selections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Tuple
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def discover_test_case_files(
|
|
17
|
+
test_cases_dir: str,
|
|
18
|
+
test_case_files: Tuple[str, ...]
|
|
19
|
+
) -> List[Path]:
|
|
20
|
+
"""Discover and filter test case files based on user selection.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
test_cases_dir: Directory containing test case files
|
|
24
|
+
test_case_files: Specific test case files to execute (empty = all)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
List of test case file paths
|
|
28
|
+
"""
|
|
29
|
+
test_cases_path = Path(test_cases_dir)
|
|
30
|
+
|
|
31
|
+
if test_case_files:
|
|
32
|
+
# User specified specific test case files
|
|
33
|
+
test_case_files_set = set(test_case_files)
|
|
34
|
+
all_test_cases = sorted(test_cases_path.rglob('TC-*.md'))
|
|
35
|
+
test_case_files_list = [
|
|
36
|
+
tc for tc in all_test_cases
|
|
37
|
+
if tc.name in test_case_files_set
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Check if all specified files were found
|
|
41
|
+
found_names = {tc.name for tc in test_case_files_list}
|
|
42
|
+
not_found = test_case_files_set - found_names
|
|
43
|
+
if not_found:
|
|
44
|
+
console.print(f"[yellow]⚠ Warning: Test case files not found: {', '.join(not_found)}[/yellow]")
|
|
45
|
+
else:
|
|
46
|
+
# Execute all test cases
|
|
47
|
+
test_case_files_list = sorted(test_cases_path.rglob('TC-*.md'))
|
|
48
|
+
|
|
49
|
+
return test_case_files_list
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate_test_case_files(
|
|
53
|
+
test_case_files_list: List[Path],
|
|
54
|
+
test_cases_dir: str,
|
|
55
|
+
test_case_files: Tuple[str, ...]
|
|
56
|
+
) -> bool:
|
|
57
|
+
"""Validate that test case files were found.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
test_case_files_list: List of discovered test case files
|
|
61
|
+
test_cases_dir: Directory that was searched
|
|
62
|
+
test_case_files: Specific files that were requested
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if files were found, False otherwise (prints warning)
|
|
66
|
+
"""
|
|
67
|
+
if not test_case_files_list:
|
|
68
|
+
if test_case_files:
|
|
69
|
+
console.print(f"[yellow]No matching test case files found in {test_cases_dir}[/yellow]")
|
|
70
|
+
else:
|
|
71
|
+
console.print(f"[yellow]No test case files found in {test_cases_dir}[/yellow]")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def print_test_execution_header(
|
|
78
|
+
agent_name: str,
|
|
79
|
+
test_case_files_list: List[Path],
|
|
80
|
+
test_case_files: Tuple[str, ...],
|
|
81
|
+
results_dir: str
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Print test execution header information.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
agent_name: Name of the test runner agent
|
|
87
|
+
test_case_files_list: List of test cases to execute
|
|
88
|
+
test_case_files: Specific files requested (if any)
|
|
89
|
+
results_dir: Directory where results will be saved
|
|
90
|
+
"""
|
|
91
|
+
console.print(f"\n[bold cyan]🧪 Test Execution Started[/bold cyan]")
|
|
92
|
+
console.print(f"Agent: [bold]{agent_name}[/bold]")
|
|
93
|
+
console.print(f"Test Cases: {len(test_case_files_list)}")
|
|
94
|
+
if test_case_files:
|
|
95
|
+
console.print(f"Selected: [cyan]{', '.join(test_case_files)}[/cyan]")
|
|
96
|
+
console.print(f"Results Directory: {results_dir}\n")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Executor cache management for test execution.
|
|
3
|
+
|
|
4
|
+
Handles creating, caching, and cleaning up agent executors.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sqlite3
|
|
9
|
+
from typing import Dict, Optional, Any, Tuple
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cleanup_executor_cache(cache: Dict[str, tuple], cache_name: str = "executor") -> None:
|
|
17
|
+
"""Clean up executor cache resources.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
cache: Dictionary of cached executors
|
|
21
|
+
cache_name: Name of cache for logging
|
|
22
|
+
"""
|
|
23
|
+
console.print(f"[dim]Cleaning up {cache_name} cache...[/dim]")
|
|
24
|
+
for cache_key, cached_items in cache.items():
|
|
25
|
+
try:
|
|
26
|
+
# Extract memory from tuple (second element)
|
|
27
|
+
memory = cached_items[1] if len(cached_items) > 1 else None
|
|
28
|
+
|
|
29
|
+
# Close SQLite memory connection
|
|
30
|
+
if memory and hasattr(memory, 'conn') and memory.conn:
|
|
31
|
+
memory.conn.close()
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logger.debug(f"Error cleaning up {cache_name} cache for {cache_key}: {e}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_executor_from_cache(
|
|
37
|
+
cache: Dict[str, tuple],
|
|
38
|
+
cache_key: str,
|
|
39
|
+
client,
|
|
40
|
+
agent_def: Dict[str, Any],
|
|
41
|
+
toolkit_config_path: Optional[str],
|
|
42
|
+
config,
|
|
43
|
+
model: Optional[str],
|
|
44
|
+
temperature: Optional[float],
|
|
45
|
+
max_tokens: Optional[int],
|
|
46
|
+
work_dir: Optional[str],
|
|
47
|
+
setup_executor_func
|
|
48
|
+
) -> Tuple:
|
|
49
|
+
"""Get or create executor from cache.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
cache: Executor cache dictionary
|
|
53
|
+
cache_key: Key for caching
|
|
54
|
+
client: API client
|
|
55
|
+
agent_def: Agent definition
|
|
56
|
+
toolkit_config_path: Path to toolkit config
|
|
57
|
+
config: CLI configuration
|
|
58
|
+
model: Model override
|
|
59
|
+
temperature: Temperature override
|
|
60
|
+
max_tokens: Max tokens override
|
|
61
|
+
work_dir: Working directory
|
|
62
|
+
setup_executor_func: Function to setup local agent executor
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Tuple of (agent_executor, memory, mcp_session_manager)
|
|
66
|
+
"""
|
|
67
|
+
if cache_key in cache:
|
|
68
|
+
return cache[cache_key]
|
|
69
|
+
|
|
70
|
+
# Create new executor
|
|
71
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
72
|
+
|
|
73
|
+
memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
|
|
74
|
+
toolkit_config_tuple = (toolkit_config_path,) if toolkit_config_path else ()
|
|
75
|
+
|
|
76
|
+
agent_executor, mcp_session_manager, _, _, _, _, _ = setup_executor_func(
|
|
77
|
+
client, agent_def, toolkit_config_tuple, config, model, temperature,
|
|
78
|
+
max_tokens, memory, work_dir
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Cache the executor
|
|
82
|
+
cached_tuple = (agent_executor, memory, mcp_session_manager)
|
|
83
|
+
cache[cache_key] = cached_tuple
|
|
84
|
+
return cached_tuple
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test execution logging utilities.
|
|
3
|
+
|
|
4
|
+
Provides the TestLogCapture context manager for capturing console output to log files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestLogCapture:
|
|
15
|
+
"""
|
|
16
|
+
Context manager to capture console output to a log file.
|
|
17
|
+
Creates a single log file for the entire test execution session.
|
|
18
|
+
Strips Rich markup for plain text output with UTF-8 encoding.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, results_dir: Path, session_name: str, console: Optional[Console] = None):
|
|
22
|
+
"""
|
|
23
|
+
Initialize log capture.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
results_dir: Base results directory
|
|
27
|
+
session_name: Name for this test session
|
|
28
|
+
"""
|
|
29
|
+
self.results_dir = results_dir
|
|
30
|
+
self.session_name = session_name
|
|
31
|
+
self.log_file_path = None
|
|
32
|
+
self.was_recording = False
|
|
33
|
+
# Use injected console if provided; otherwise create a dedicated console.
|
|
34
|
+
# Injecting allows other components (e.g., tool-call callbacks) to print to the
|
|
35
|
+
# same console and be captured in the session log.
|
|
36
|
+
self.console: Console = console or Console()
|
|
37
|
+
|
|
38
|
+
def __enter__(self):
|
|
39
|
+
"""Start capturing console output."""
|
|
40
|
+
# Extract toolkit name from session name (e.g., "test-execution-confluence" -> "confluence")
|
|
41
|
+
toolkit_name = self.session_name.replace('test-execution-', '')
|
|
42
|
+
|
|
43
|
+
# Create toolkit-specific directory
|
|
44
|
+
toolkit_dir = self.results_dir / toolkit_name
|
|
45
|
+
toolkit_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
# Generate timestamped filename
|
|
48
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
49
|
+
log_filename = f"{timestamp}.{self.session_name}.txt"
|
|
50
|
+
self.log_file_path = toolkit_dir / log_filename
|
|
51
|
+
|
|
52
|
+
# Enable recording on this console
|
|
53
|
+
self.was_recording = self.console.record
|
|
54
|
+
self.console.record = True
|
|
55
|
+
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def print(self, *args, **kwargs):
|
|
59
|
+
"""Print to console (both display and recording)."""
|
|
60
|
+
self.console.print(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
def status(self, message, **kwargs):
|
|
63
|
+
"""Create status spinner and log the message."""
|
|
64
|
+
# Manually record the status message (spinners are transient)
|
|
65
|
+
self.console.print(message)
|
|
66
|
+
# Show spinner (this won't be recorded due to transient nature)
|
|
67
|
+
return self.console.status(message, **kwargs)
|
|
68
|
+
|
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
70
|
+
"""Stop capturing and save log file."""
|
|
71
|
+
if self.log_file_path:
|
|
72
|
+
# Export recorded text (strips Rich markup)
|
|
73
|
+
plain_text = self.console.export_text()
|
|
74
|
+
|
|
75
|
+
# Save to file with UTF-8 encoding
|
|
76
|
+
with open(self.log_file_path, 'w', encoding='utf-8') as f:
|
|
77
|
+
f.write(f"Test Execution Session: {self.session_name}\n")
|
|
78
|
+
f.write(f"Timestamp: {datetime.now().isoformat()}\n")
|
|
79
|
+
f.write("=" * 80 + "\n\n")
|
|
80
|
+
f.write(plain_text)
|
|
81
|
+
|
|
82
|
+
# Restore previous recording state
|
|
83
|
+
self.console.record = self.was_recording
|
|
84
|
+
|
|
85
|
+
return False
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test case parsing utilities.
|
|
3
|
+
|
|
4
|
+
Handles parsing of test case markdown files and resolving configuration paths.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Dict, Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def resolve_toolkit_config_path(config_path_str: str, test_file: Path, test_cases_dir: Path) -> Optional[str]:
|
|
13
|
+
"""
|
|
14
|
+
Resolve toolkit configuration file path from test case.
|
|
15
|
+
|
|
16
|
+
Tries multiple locations in order:
|
|
17
|
+
1. Absolute path
|
|
18
|
+
2. Relative to test case file directory
|
|
19
|
+
3. Relative to test cases directory
|
|
20
|
+
4. Relative to workspace root
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config_path_str: Config path from test case
|
|
24
|
+
test_file: Path to the test case file
|
|
25
|
+
test_cases_dir: Path to test cases directory
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Absolute path to config file if found, None otherwise
|
|
29
|
+
"""
|
|
30
|
+
if not config_path_str:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
# Normalize path separators
|
|
34
|
+
config_path_str = config_path_str.replace('\\', '/')
|
|
35
|
+
|
|
36
|
+
# Try absolute path first
|
|
37
|
+
config_path = Path(config_path_str)
|
|
38
|
+
if config_path.is_absolute() and config_path.exists():
|
|
39
|
+
return str(config_path)
|
|
40
|
+
|
|
41
|
+
# Try relative to test case file directory
|
|
42
|
+
config_path = test_file.parent / config_path_str
|
|
43
|
+
if config_path.exists():
|
|
44
|
+
return str(config_path)
|
|
45
|
+
|
|
46
|
+
# Try relative to test_cases_dir
|
|
47
|
+
config_path = test_cases_dir / config_path_str
|
|
48
|
+
if config_path.exists():
|
|
49
|
+
return str(config_path)
|
|
50
|
+
|
|
51
|
+
# Try relative to workspace root
|
|
52
|
+
workspace_root = Path.cwd()
|
|
53
|
+
config_path = workspace_root / config_path_str
|
|
54
|
+
if config_path.exists():
|
|
55
|
+
return str(config_path)
|
|
56
|
+
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_test_case(test_case_path: str) -> Dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Parse a test case markdown file to extract configuration, steps, and expectations.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
test_case_path: Path to the test case markdown file
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dictionary containing:
|
|
69
|
+
- name: Test case name
|
|
70
|
+
- objective: Test objective
|
|
71
|
+
- config_path: Path to toolkit config file
|
|
72
|
+
- generate_test_data: Boolean flag indicating if test data generation is needed (default: True)
|
|
73
|
+
- test_data_config: Dictionary of test data configuration from table
|
|
74
|
+
- prerequisites: Pre-requisites section text
|
|
75
|
+
- variables: List of variable placeholders found (e.g., {{TEST_PR_NUMBER}})
|
|
76
|
+
- steps: List of test steps with their descriptions
|
|
77
|
+
- expectations: List of expectations/assertions
|
|
78
|
+
"""
|
|
79
|
+
path = Path(test_case_path)
|
|
80
|
+
if not path.exists():
|
|
81
|
+
raise FileNotFoundError(f"Test case not found: {test_case_path}")
|
|
82
|
+
|
|
83
|
+
content = path.read_text(encoding='utf-8')
|
|
84
|
+
|
|
85
|
+
# Extract test case name from the first heading
|
|
86
|
+
name_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
|
|
87
|
+
name = name_match.group(1) if name_match else path.stem
|
|
88
|
+
|
|
89
|
+
# Extract objective
|
|
90
|
+
objective_match = re.search(r'##\s+Objective\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
|
|
91
|
+
objective = objective_match.group(1).strip() if objective_match else ""
|
|
92
|
+
|
|
93
|
+
# Extract config path and generateTestData flag
|
|
94
|
+
config_section_match = re.search(r'##\s+Config\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
|
|
95
|
+
config_path = None
|
|
96
|
+
generate_test_data = True # Default to True if not specified
|
|
97
|
+
|
|
98
|
+
if config_section_match:
|
|
99
|
+
config_section = config_section_match.group(1)
|
|
100
|
+
# Extract path
|
|
101
|
+
path_match = re.search(r'path:\s*(.+?)(?=\n|$)', config_section, re.MULTILINE)
|
|
102
|
+
if path_match:
|
|
103
|
+
config_path = path_match.group(1).strip()
|
|
104
|
+
|
|
105
|
+
# Extract generateTestData flag
|
|
106
|
+
gen_data_match = re.search(r'generateTestData\s*:\s*(true|false)', config_section, re.IGNORECASE)
|
|
107
|
+
if gen_data_match:
|
|
108
|
+
generate_test_data = gen_data_match.group(1).lower() == 'true'
|
|
109
|
+
|
|
110
|
+
# Extract Test Data Configuration section as a raw fenced code block string
|
|
111
|
+
# NOTE: We intentionally store the entire section as a single string rather than parsing
|
|
112
|
+
# individual table rows. This preserves the original formatting for downstream tools
|
|
113
|
+
# which may prefer the raw markdown block.
|
|
114
|
+
test_data_config = None
|
|
115
|
+
config_section_match = re.search(r'##\s+Test Data Configuration\s*\n(.+?)(?=\n##|\Z)', content, re.DOTALL)
|
|
116
|
+
if config_section_match:
|
|
117
|
+
config_section = config_section_match.group(1).strip()
|
|
118
|
+
# Store as a fenced code block to make it clear this is a raw block of text
|
|
119
|
+
test_data_config = f"\n{config_section}\n"
|
|
120
|
+
|
|
121
|
+
# Extract Pre-requisites section
|
|
122
|
+
prerequisites = ""
|
|
123
|
+
prereq_match = re.search(r'##\s+Pre-requisites\s*\n\n(.+?)(?=\n\n##|\Z)', content, re.DOTALL)
|
|
124
|
+
if prereq_match:
|
|
125
|
+
prerequisites = prereq_match.group(1).strip()
|
|
126
|
+
|
|
127
|
+
# Find all variable placeholders ({{VARIABLE_NAME}})
|
|
128
|
+
variables = list(set(re.findall(r'\{\{([A-Z_]+)\}\}', content)))
|
|
129
|
+
|
|
130
|
+
# Extract test steps and expectations
|
|
131
|
+
steps = []
|
|
132
|
+
expectations = []
|
|
133
|
+
|
|
134
|
+
# Find all Step sections
|
|
135
|
+
step_pattern = r'###\s+Step\s+(\d+):\s+(.+?)\n\n(.+?)(?=\n\n###|\n\n##|\Z)'
|
|
136
|
+
for step_match in re.finditer(step_pattern, content, re.DOTALL):
|
|
137
|
+
step_num = step_match.group(1)
|
|
138
|
+
step_title = step_match.group(2).strip()
|
|
139
|
+
step_content = step_match.group(3).strip()
|
|
140
|
+
|
|
141
|
+
# Extract the actual instruction (first paragraph before "Expectation:")
|
|
142
|
+
instruction_match = re.search(r'(.+?)(?=\n\n\*\*Expectation:\*\*|\Z)', step_content, re.DOTALL)
|
|
143
|
+
instruction = instruction_match.group(1).strip() if instruction_match else step_content
|
|
144
|
+
|
|
145
|
+
# Extract expectation if present
|
|
146
|
+
expectation_match = re.search(r'\*\*Expectation:\*\*\s+(.+)', step_content, re.DOTALL)
|
|
147
|
+
expectation = expectation_match.group(1).strip() if expectation_match else None
|
|
148
|
+
|
|
149
|
+
steps.append({
|
|
150
|
+
'number': int(step_num),
|
|
151
|
+
'title': step_title,
|
|
152
|
+
'instruction': instruction,
|
|
153
|
+
'expectation': expectation
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
if expectation:
|
|
157
|
+
expectations.append({
|
|
158
|
+
'step': int(step_num),
|
|
159
|
+
'description': expectation
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
'name': name,
|
|
164
|
+
'objective': objective,
|
|
165
|
+
'config_path': config_path,
|
|
166
|
+
'generate_test_data': generate_test_data,
|
|
167
|
+
'test_data_config': test_data_config,
|
|
168
|
+
'prerequisites': prerequisites,
|
|
169
|
+
'variables': variables,
|
|
170
|
+
'steps': steps,
|
|
171
|
+
'expectations': expectations
|
|
172
|
+
}
|