Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions docs/guide/reporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,17 @@ AssertionError: expected 5 to be 4 // Object.is equality
</testsuites>
```

The outputted XML contains nested `testsuites` and `testcase` tags. You can use the reporter options to configure these attributes:
The outputted XML contains nested `testsuites` and `testcase` tags. These can also be customized via reporter options `suiteName` and `classnameTemplate`. `classnameTemplate` can either be a template string or a function.

The supported placeholders for the `classnameTemplate` option are:
- filename
- filepath

```ts
export default defineConfig({
test: {
reporters: [
['junit', { suiteName: 'custom suite name', classname: 'custom-classname' }]
['junit', { suiteName: 'custom suite name', classnameTemplate: 'filename:{filename} - filepath:{filepath}' }]
]
},
})
Expand Down
32 changes: 31 additions & 1 deletion packages/vitest/src/node/reporters/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,20 @@ import { getOutputFile } from '../../utils/config-helpers'
import { capturePrintError } from '../error'
import { IndentedLogger } from './renderers/indented-logger'

interface ClassnameTemplateVariables {
filename: string
filepath: string
}

export interface JUnitOptions {
outputFile?: string
/** @deprecated Use `classnameTemplate` instead. */
classname?: string

/**
* Template for the classname attribute. Can be either a string or a function. The string can contain placeholders {filename} and {filepath}.
*/
classnameTemplate?: string | ((classnameVariables: ClassnameTemplateVariables) => string)
suiteName?: string
/**
* Write <system-out> and <system-err> for console output
Expand Down Expand Up @@ -195,10 +206,29 @@ export class JUnitReporter implements Reporter {

async writeTasks(tasks: Task[], filename: string): Promise<void> {
for (const task of tasks) {
let classname = filename

const templateVars: ClassnameTemplateVariables = {
filename: task.file.name,
filepath: task.file.filepath,
}

if (typeof this.options.classnameTemplate === 'function') {
classname = this.options.classnameTemplate(templateVars)
}
else if (typeof this.options.classnameTemplate === 'string') {
classname = this.options.classnameTemplate
.replace(/\{filename\}/g, templateVars.filename)
.replace(/\{filepath\}/g, templateVars.filepath)
}
else if (typeof this.options.classname === 'string') {
classname = this.options.classname
}

await this.writeElement(
'testcase',
{
classname: this.options.classname ?? filename,
classname,
file: this.options.addFileAttribute ? filename : undefined,
name: task.name,
time: getDuration(task),
Expand Down
34 changes: 33 additions & 1 deletion test/reporters/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,37 @@ const suite: Suite = {
tasks: [],
}

const passedFile: File = {
id: '1223128da3',
name: 'basic.test.ts',
type: 'suite',
suite,
meta: {},
mode: 'run',
filepath: '/vitest/test/core/test/basic.test.ts',
result: { state: 'pass', duration: 145.99284195899963 },
tasks: [
],
projectName: '',
file: null!,
}
passedFile.file = passedFile
passedFile.tasks.push({
id: '1223128da3_0_0',
type: 'test',
name: 'Math.sqrt()',
mode: 'run',
fails: undefined,
suite,
meta: {},
file: passedFile,
result: {
state: 'pass',
duration: 1.4422860145568848,
},
context: null as any,
})

const error: ErrorWithDiff = {
name: 'AssertionError',
message: 'expected 2.23606797749979 to equal 2',
Expand Down Expand Up @@ -176,5 +207,6 @@ file.tasks = [suite]
suite.tasks = tasks

const files = [file]
const passedFiles = [passedFile]

export { files }
export { files, passedFiles }
44 changes: 44 additions & 0 deletions test/reporters/tests/__snapshots__/reporters.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,39 @@ exports[`JUnit reporter 1`] = `
"
`;

exports[`JUnit reporter with custom function classnameTemplate 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="filename:basic.test.ts - filepath:/vitest/test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with custom string classname 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="my-custom-classname" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with custom string classnameTemplate 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="filename:basic.test.ts - filepath:/vitest/test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with outputFile 1`] = `
"JUNIT report written to <process-cwd>/report.xml
"
Expand Down Expand Up @@ -62,6 +95,17 @@ exports[`JUnit reporter with outputFile object in non-existing directory 2`] = `
"
`;

exports[`JUnit reporter without classname 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`json reporter (no outputFile entry) 1`] = `
{
"numFailedTestSuites": 1,
Expand Down
57 changes: 56 additions & 1 deletion test/reporters/tests/reporters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { JUnitReporter } from '../../../packages/vitest/src/node/reporters/junit
import { TapReporter } from '../../../packages/vitest/src/node/reporters/tap'
import { TapFlatReporter } from '../../../packages/vitest/src/node/reporters/tap-flat'
import { getContext } from '../src/context'
import { files } from '../src/data'
import { files, passedFiles } from '../src/data'

const beautify = (json: string) => JSON.parse(json)

Expand Down Expand Up @@ -60,6 +60,61 @@ test('JUnit reporter', async () => {
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter without classname', async () => {
// Arrange
const reporter = new JUnitReporter({})
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter with custom string classname', async () => {
// Arrange
const reporter = new JUnitReporter({ classname: 'my-custom-classname' })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter with custom function classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: task => `filename:${task.filename} - filepath:${task.filepath}` })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})
test('JUnit reporter with custom string classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: `filename:{filename} - filepath:{filepath}` })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter (no outputFile entry)', async () => {
// Arrange
const reporter = new JUnitReporter({})
Expand Down
Loading