2828import argparse
2929import datetime
3030import json
31+ import os
32+ import re
3133import sys
3234import xml .etree .ElementTree as ET
3335from pathlib import Path
3436
37+ # Canonical test-type taxonomy (mirrors agent's TEST_TYPES in
38+ # src/api/routes/reporting.py). Producers MUST emit one of these as
39+ # `test_suite` so the Reports drill-down can group by type.
40+ TEST_TYPES = ('unit' , 'integration' , 'ui' , 'smoke' , 'e2e' , 'perf' , 'chaos' )
41+
42+
43+ def _derive_test_type (source_file : str , suite_name : str , override : str | None ) -> str :
44+ """Pick a canonical test type from CLI override, then filename, then suite."""
45+ if override :
46+ return override
47+ haystack = f'{ source_file } { suite_name } ' .lower ()
48+ for t in TEST_TYPES :
49+ if re .search (rf'\b{ t } \b' , haystack ):
50+ return t
51+ return 'unit'
52+
3553try :
3654 import requests
3755 HAS_REQUESTS = True
3856except ImportError :
3957 HAS_REQUESTS = False
4058
4159
42- def parse_junit_xml (xml_path ):
43- """Parse a JUnit XML file and return a list of test result dicts."""
60+ def parse_junit_xml (xml_path , test_type_override : str | None = None ):
61+ """Parse a JUnit XML file and return a list of test result dicts.
62+
63+ Enriches each doc with CI correlation fields (repo, pipeline_id,
64+ build_number, branch, commit_sha) from the Jenkins env so the
65+ Dashboard Reports drill-down can join against test-coverage-*.
66+ """
4467 results = []
4568 tree = ET .parse (xml_path )
4669 root = tree .getroot ()
@@ -50,9 +73,24 @@ def parse_junit_xml(xml_path):
5073 if not suites and root .tag == 'testsuite' :
5174 suites = [root ]
5275
76+ # Jenkins env correlation (no-op outside Jenkins). repo defaults to
77+ # the agentic-taf repo when JOB_NAME is unset.
78+ job = os .environ .get ('JOB_NAME' , '' ) or ''
79+ repo = job .split ('/' )[0 ] if job else 'agentic-taf'
80+ branch = os .environ .get ('BRANCH_NAME' ) or os .environ .get ('GIT_BRANCH' ) or 'main'
81+ commit_sha = os .environ .get ('GIT_COMMIT' , '' )
82+ pipeline_id = os .environ .get ('BUILD_TAG' , '' )
83+ build_number_raw = os .environ .get ('BUILD_NUMBER' , '0' )
84+ try :
85+ build_number = int (build_number_raw )
86+ except ValueError :
87+ build_number = 0
88+ team = os .environ .get ('TEAM' , 'platform-team' )
89+
5390 for suite in suites :
5491 suite_name = suite .get ('name' , 'unknown' )
5592 suite_time = float (suite .get ('time' , 0 ))
93+ test_type = _derive_test_type (str (xml_path ), suite_name , test_type_override )
5694
5795 for tc in suite .findall ('testcase' ):
5896 name = tc .get ('name' , 'unknown' )
@@ -72,16 +110,30 @@ def parse_junit_xml(xml_path):
72110 message = tc .find ('skipped' ).get ('message' , '' )
73111
74112 results .append ({
113+ # JUnit-derived fields (kept for backward compatibility)
75114 'suite' : suite_name ,
76115 'classname' : classname ,
77116 'name' : name ,
78117 'status' : status ,
79118 'duration_seconds' : time_taken ,
119+ 'duration_ms' : int (time_taken * 1000 ),
80120 'message' : message ,
81121 'source_file' : str (xml_path ),
82122 'timestamp' : datetime .datetime .utcnow ().isoformat () + 'Z' ,
83123 'framework' : 'agentic-taf' ,
84124 'suite_duration' : suite_time ,
125+ # Canonical fields the agent reads for joining + grouping
126+ 'test_suite' : test_type , # canonical type taxonomy
127+ 'test_name' : name , # mirror of `name`
128+ 'test_type' : test_type , # explicit alias
129+ 'repo' : repo ,
130+ 'branch' : branch ,
131+ 'commit_sha' : commit_sha ,
132+ 'pipeline_id' : pipeline_id ,
133+ 'build_number' : build_number ,
134+ 'team' : team ,
135+ 'is_flaky' : False ,
136+ 'retry_count' : 0 ,
85137 })
86138
87139 return results
@@ -159,6 +211,13 @@ def main():
159211 parser .add_argument ('--opensearch-url' , help = 'OpenSearch URL (e.g. http://opensearch:9200)' )
160212 parser .add_argument ('--agent-url' , help = 'Agent API URL (e.g. http://agent:8000)' )
161213 parser .add_argument ('--index' , default = 'test-results' , help = 'OpenSearch index name' )
214+ parser .add_argument (
215+ '--test-type' ,
216+ choices = TEST_TYPES ,
217+ help = 'Override the canonical test type. If omitted, derived from the '
218+ 'XML filename / suite name (regex against the TEST_TYPES list); '
219+ 'falls back to "unit".' ,
220+ )
162221 args = parser .parse_args ()
163222
164223 reports_dir = Path (args .reports_dir )
@@ -174,7 +233,7 @@ def main():
174233 all_results = []
175234 for xml_file in xml_files :
176235 try :
177- results = parse_junit_xml (xml_file )
236+ results = parse_junit_xml (xml_file , test_type_override = args . test_type )
178237 all_results .extend (results )
179238 print (f'Parsed { len (results )} results from { xml_file .name } ' )
180239 except Exception as exc :
0 commit comments