-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathJUnitParser.js
More file actions
143 lines (122 loc) · 3.44 KB
/
Copy pathJUnitParser.js
File metadata and controls
143 lines (122 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { XMLParser } from "fast-xml-parser";
import { BaseParser, ReportCategory } from "./BaseParser.js";
import { ReportData, TestResult } from "../models/ReportData.js";
/**
* Parser for JUnit XML format
* Supports standard JUnit XML as produced by many testing frameworks
*/
export class JUnitParser extends BaseParser {
constructor() {
super();
this.xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_",
textNodeName: "#text",
});
}
canParse(filePath, content) {
const normalizedPath = filePath.toLowerCase();
const hasSupportedName =
this.matchesAutoPatterns(filePath) ||
normalizedPath.includes("junit") ||
normalizedPath.endsWith(".xml");
return (
hasSupportedName &&
(content.includes("<testsuite") || content.includes("<testsuites"))
);
}
getPriority() {
return 10;
}
getCategory() {
return ReportCategory.TEST;
}
getAutoPatterns() {
return [
"**/junit*.xml",
"**/test-results/**/*.xml",
"**/test-reports/**/*.xml",
"**/*test*.xml",
];
}
parse(content) {
const reportData = new ReportData();
reportData.reportType = "test";
try {
const parsed = this.xmlParser.parse(content);
// Handle both <testsuites> and single <testsuite> root elements
const testsuites = this._normalizeTestSuites(parsed);
for (const testsuite of testsuites) {
this._parseTestSuite(testsuite, reportData);
}
} catch (error) {
throw new Error(`Failed to parse JUnit XML: ${error.message}`);
}
return reportData;
}
_normalizeTestSuites(parsed) {
// Handle different root elements
if (parsed.testsuites) {
const suites = parsed.testsuites.testsuite;
return Array.isArray(suites) ? suites : [suites];
} else if (parsed.testsuite) {
return Array.isArray(parsed.testsuite)
? parsed.testsuite
: [parsed.testsuite];
}
return [];
}
_parseTestSuite(testsuite, reportData) {
if (!testsuite) return;
const suiteName = testsuite["@_name"] || "Unknown Suite";
const testcases = testsuite.testcase;
if (!testcases) return;
const cases = Array.isArray(testcases) ? testcases : [testcases];
for (const testcase of cases) {
const test = this._parseTestCase(testcase, suiteName);
reportData.addTest(test);
}
}
_parseTestCase(testcase, suiteName) {
const name = testcase["@_name"] || "Unknown Test";
const className = testcase["@_classname"] || testcase["@_class"] || "";
const time = parseFloat(testcase["@_time"] || 0);
const file = testcase["@_file"] || "";
let status = "passed";
let message = "";
let errorType = "";
let stackTrace = "";
// Check for failure
if (testcase.failure) {
status = "failed";
const failure = testcase.failure;
message = failure["@_message"] || failure["#text"] || "Test failed";
errorType = failure["@_type"] || "AssertionError";
stackTrace = failure["#text"] || "";
}
// Check for error
else if (testcase.error) {
status = "error";
const error = testcase.error;
message = error["@_message"] || error["#text"] || "Test error";
errorType = error["@_type"] || "Error";
stackTrace = error["#text"] || "";
}
// Check for skipped
else if (testcase.skipped) {
status = "skipped";
const skipped = testcase.skipped;
message = skipped["@_message"] || skipped["#text"] || "Test skipped";
}
return new TestResult({
name,
status,
duration: time,
message,
errorType,
stackTrace,
file: file || className,
suite: suiteName,
});
}
}