Skip to content

junit-upload fails on <testsuites> without attributes #52

@AndrianBdn

Description

@AndrianBdn

Bug

junit-upload fails when parsing JUnit XML files where <testsuites> or <testsuite> elements have no attributes.

Error

[
  {
    "code": "invalid_type",
    "expected": "object",
    "received": "undefined",
    "path": ["testsuites", "$"],
    "message": "Required"
  }
]

Cause

In src/utils/result-upload/junitXmlParser.ts, the Zod schema requires $ (xml2js attributes object) on both testsuites and testsuite elements:

const junitXmlSchema = z.object({
    testsuites: z.object({
        $: z.object({...}),        // ← required
        testsuite: z.array(
            z.object({
                $: z.object({...}),  // ← required

When xml2js parses elements without attributes, it omits the $ key entirely. The schema then fails validation.

Reproduction

Any JUnit XML where <testsuites> has no attributes triggers this. Common producers:

  • junit-merge (npm) — produces <testsuites> with no attributes
  • TestNG JUnitReportReporter — produces <testsuite> with attributes but junit-merge strips <testsuites> attributes when wrapping

Example failing XML:

<?xml version="1.0"?>
<testsuites>
  <testsuite name="com.example.MyTest" tests="1" time="0.5">
    <testcase classname="com.example.MyTest" name="test1" time="0.5"/>
  </testsuite>
</testsuites>

Fix

Make $ optional on both testsuites and testsuite, and use optional chaining on suite.$?.name:

 const junitXmlSchema = z.object({
     testsuites: z.object({
-        $: z.object({
-            name: z.string().optional(),
-            time: z.string().optional(),
-            timeStamp: z.string().optional(),
-        }),
+        $: z.object({
+            name: z.string().optional(),
+            time: z.string().optional(),
+            timeStamp: z.string().optional(),
+        }).optional(),
         testsuite: z.array(
             z.object({
-                $: z.object({
-                    name: z.string().optional(),
-                    time: z.string().optional(),
-                    timeStamp: z.string().optional(),
-                }),
+                $: z.object({
+                    name: z.string().optional(),
+                    time: z.string().optional(),
+                    timeStamp: z.string().optional(),
+                }).optional(),
- const folder = tcase.$.classname ?? suite.$.name ?? ''
+ const folder = tcase.$.classname ?? suite.$?.name ?? ''

Context

Discovered while testing TestNG JUnit XML uploads for a customer using junit-merge to combine per-class report files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions