add support for karma-junit-reporter

This commit is contained in:
George Young 2025-11-18 16:51:07 +13:00
parent aef3d726a6
commit 08646e4d97
17 changed files with 12441 additions and 1 deletions

View file

@ -0,0 +1,17 @@
![Tests passed successfully](https://img.shields.io/badge/tests-1%20passed-success)
<details><summary>Expand for details</summary>
|Report|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
|[fixtures/karma-junit-eslint.xml](#user-content-r0)|1 ✅|||0ms|
## ✅ <a id="user-content-r0" href="#user-content-r0">fixtures/karma-junit-eslint.xml</a>
**1** tests were completed in **0ms** with **1** passed, **0** failed and **0** skipped.
|Test suite|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
|[test.jsx](#user-content-r0s0)|1 ✅|||0ms|
### ✅ <a id="user-content-r0s0" href="#user-content-r0s0">test.jsx</a>
```
test
✅ test.jsx
```
</details>

View file

@ -0,0 +1,22 @@
![Tests failed](https://img.shields.io/badge/tests-1%20passed%2C%203%20failed-critical)
|Report|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
|[fixtures/karma-junit.xml](#user-content-r0)|1 ✅|3 ❌||486ms|
## ❌ <a id="user-content-r0" href="#user-content-r0">fixtures/karma-junit.xml</a>
**4** tests were completed in **486ms** with **1** passed, **3** failed and **0** skipped.
|Test suite|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
|[__tests__\main.test.js](#user-content-r0s0)|1 ✅|3 ❌||486ms|
### ❌ <a id="user-content-r0s0" href="#user-content-r0s0">__tests__\main.test.js</a>
```
Test 1
✅ Passing test
Test 1 Test 1.1
❌ Failing test
Error: expect(received).toBeTruthy()
❌ Exception in target unit
Error: Some error
Test 2
❌ Exception in test
Error: Some error
```

View file

@ -0,0 +1,9 @@
![Tests passed successfully](https://img.shields.io/badge/tests-none-yellow)
<details><summary>Expand for details</summary>
|Report|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
## ✅ <a id="user-content-r0" href="#user-content-r0">fixtures/external/karma/karma-react-component-test-results.xml</a>
No tests found
</details>

View file

@ -0,0 +1,9 @@
![Tests passed successfully](https://img.shields.io/badge/tests-none-yellow)
<details><summary>Expand for details</summary>
|Report|Passed|Failed|Skipped|Time|
|:---|---:|---:|---:|---:|
## ✅ <a id="user-content-r0" href="#user-content-r0">fixtures/external/karma/karma-test-results.xml</a>
No tests found
</details>

View file

@ -0,0 +1,141 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`karma-junit tests parsing ESLint report without timing information works - PR #134 1`] = `
TestRunResult {
"path": "fixtures/karma-junit-eslint.xml",
"suites": [
TestSuiteResult {
"groups": [
TestGroupResult {
"name": "test",
"tests": [
TestCaseResult {
"error": undefined,
"name": "test.jsx",
"result": "success",
"time": 0,
},
],
},
],
"name": "test.jsx",
"totalTime": 0,
},
],
"totalTime": 0,
}
`;
exports[`karma-junit tests parsing junit report with message succeeds 1`] = `
TestRunResult {
"path": "fixtures/junit-with-message.xml",
"suites": [],
"totalTime": undefined,
}
`;
exports[`karma-junit tests report from #235 testing react components named <ComponentName /> 1`] = `
TestRunResult {
"path": "fixtures/external/karma/karma-react-component-test-results.xml",
"suites": [],
"totalTime": undefined,
}
`;
exports[`karma-junit tests report from ./reports/karma test results matches snapshot 1`] = `
TestRunResult {
"path": "fixtures/karma-junit.xml",
"suites": [
TestSuiteResult {
"groups": [
TestGroupResult {
"name": "Test 1",
"tests": [
TestCaseResult {
"error": undefined,
"name": "Passing test",
"result": "success",
"time": 1,
},
],
},
TestGroupResult {
"name": "Test 1 Test 1.1",
"tests": [
TestCaseResult {
"error": {
"details": "Error: expect(received).toBeTruthy()
Received: false
at Object.<anonymous> (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\__tests__\\main.test.js:10:21)
at Object.asyncJestTest (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\jasmineAsyncInstall.js:106:37)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:28:19)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
"line": 10,
"path": "__tests__/main.test.js",
},
"name": "Failing test",
"result": "failed",
"time": 2,
},
TestCaseResult {
"error": {
"details": "Error: Some error
at Object.throwError (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\lib\\main.js:2:9)
at Object.<anonymous> (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\__tests__\\main.test.js:14:11)
at Object.asyncJestTest (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\jasmineAsyncInstall.js:106:37)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:28:19)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
"line": 2,
"path": "lib/main.js",
},
"name": "Exception in target unit",
"result": "failed",
"time": 0,
},
],
},
TestGroupResult {
"name": "Test 2",
"tests": [
TestCaseResult {
"error": {
"details": "Error: Some error
at Object.<anonymous> (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\__tests__\\main.test.js:21:11)
at Object.asyncJestTest (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\jasmineAsyncInstall.js:106:37)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:28:19)
at C:\\Users\\Michal\\Workspace\\dorny\\test-check\\reports\\jest\\node_modules\\jest-jasmine2\\build\\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)",
"line": 21,
"path": "__tests__/main.test.js",
},
"name": "Exception in test",
"result": "failed",
"time": 0,
},
],
},
],
"name": "__tests__\\main.test.js",
"totalTime": 486,
},
],
"totalTime": 486,
}
`;
exports[`karma-junit tests report from facebook/karma test results matches snapshot 1`] = `
TestRunResult {
"path": "fixtures/external/karma/karma-test-results.xml",
"suites": [],
"totalTime": undefined,
}
`;

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="__tests__\main.test.js" errors="0" failures="0" skipped="0" timestamp="2020-10-27T21:39:41" time="0.486" tests="0">
</testsuite>

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="11.299">
</testsuites>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="React components test" tests="1" failures="0" errors="0" time="1.0">
<testsuite name="&lt;Component /&gt;" errors="0" failures="0" skipped="0" timestamp="2021-01-24T19:21:45" time="0.798" tests="1">
<testcase classname="" name="&lt;Component /&gt; should render properly" time="0.704">
</testcase>
</testsuite>
</testsuites>

File diff suppressed because it is too large Load diff

View file

@ -58,4 +58,4 @@ Received: false
<skipped/> <skipped/>
</testcase> </testcase>
</testsuite> </testsuite>
</testsuites> </testsuites>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<testsuite package="org.eslint" time="0" tests="1" errors="0" name="test.jsx">
<testcase time="0" name="test.jsx" classname="test" />
</testsuite>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="__tests__\main.test.js" errors="0" failures="3" skipped="0" timestamp="2020-10-27T21:39:41" time="0.486" tests="4">
<testcase classname="Test 1" name="Passing test" time="0.001">
</testcase>
<testcase classname="Test 1 Test 1.1" name="Failing test" time="0.002">
<failure>Error: expect(received).toBeTruthy()
Received: false
at Object.&lt;anonymous&gt; (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:10:21)
at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
at new Promise (&lt;anonymous&gt;)
at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)</failure>
</testcase>
<testcase classname="Test 1 Test 1.1" name="Exception in target unit" time="0">
<failure>Error: Some error
at Object.throwError (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\lib\main.js:2:9)
at Object.&lt;anonymous&gt; (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:14:11)
at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
at new Promise (&lt;anonymous&gt;)
at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)</failure>
</testcase>
<testcase classname="Test 2" name="Exception in test" time="0">
<failure>Error: Some error
at Object.&lt;anonymous&gt; (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\__tests__\main.test.js:21:11)
at Object.asyncJestTest (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:45:12
at new Promise (&lt;anonymous&gt;)
at mapper (C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
at C:\Users\Michal\Workspace\dorny\test-check\reports\jest\node_modules\jest-jasmine2\build\queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:97:5)</failure>
</testcase>
</testsuite>

View file

@ -0,0 +1,349 @@
import * as fs from 'fs'
import * as path from 'path'
import {KarmaJunitParser} from '../src/parsers/karma-junit/karma-junit-parser'
import {ParseOptions} from '../src/test-parser'
import {DEFAULT_OPTIONS, getReport} from '../src/report/get-report'
import {normalizeFilePath} from '../src/utils/path-utils'
describe('karma-junit tests', () => {
it('produces empty test run result when there are no test cases in the testsuites element', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result.tests).toBe(0)
expect(result.result).toBe('success')
})
it('produces empty test run result when there are no test cases in a nested testsuite element', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'empty', 'karma-junit-empty-testsuite.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result.tests).toBe(0)
expect(result.result).toBe('success')
})
it('report from ./reports/karma test results matches snapshot', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const outputPath = path.join(__dirname, '__outputs__', 'karma-junit.md')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js']
//workDir: 'C:/Users/Michal/Workspace/dorny/test-check/reports/karma/'
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result).toMatchSnapshot()
const report = getReport([result])
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, report)
})
it('report from facebook/karma test results matches snapshot', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'karma', 'karma-test-results.xml')
const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'karma', 'files.txt')
const outputPath = path.join(__dirname, '__outputs__', 'karma-test-results.md')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
const opts: ParseOptions = {
parseErrors: true,
trackedFiles
//workDir: '/home/dorny/dorny/karma/'
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result).toMatchSnapshot()
const report = getReport([result])
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, report)
})
it('report from #235 testing react components named <ComponentName />', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'karma', 'karma-react-component-test-results.xml')
const trackedFilesPath = path.join(__dirname, 'fixtures', 'external', 'karma', 'files.txt')
const outputPath = path.join(__dirname, '__outputs__', 'karma-react-component-test-results.md')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const trackedFiles = fs.readFileSync(trackedFilesPath, {encoding: 'utf8'}).split(/\n\r?/g)
const opts: ParseOptions = {
parseErrors: true,
trackedFiles
//workDir: '/home/dorny/dorny/karma/'
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result).toMatchSnapshot()
const report = getReport([result])
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, report)
})
it('parsing ESLint report without timing information works - PR #134', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit-eslint.xml')
const outputPath = path.join(__dirname, '__outputs__', 'karma-junit-eslint.md')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: ['test.js']
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result).toMatchSnapshot()
const report = getReport([result])
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, report)
})
it('parsing junit report with message succeeds', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'junit-with-message.xml')
const outputPath = path.join(__dirname, '__outputs__', 'junit-with-message.md')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: ['test.js']
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result).toMatchSnapshot()
const report = getReport([result])
fs.mkdirSync(path.dirname(outputPath), {recursive: true})
fs.writeFileSync(outputPath, report)
})
it('report does not include a title by default', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result])
// Report should have the badge as the first line
expect(report).toMatch(/^!\[Tests failed]/)
})
it.each([
['empty string', ''],
['space', ' '],
['tab', '\t'],
['newline', '\n']
])('report does not include a title when configured value is %s', async (_, reportTitle) => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
reportTitle
})
// Report should have the badge as the first line
expect(report).toMatch(/^!\[Tests failed]/)
})
it('report includes a custom report title', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
reportTitle: 'My Custom Title'
})
// Report should have the title as the first line
expect(report).toMatch(/^# My Custom Title\n/)
})
it('report can be collapsed when configured', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'always'
})
// Report should include collapsible details
expect(report).toContain('<details><summary>Expand for details</summary>')
expect(report).toContain('</details>')
})
it('report is not collapsed when configured to never', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'never'
})
// Report should not include collapsible details
expect(report).not.toContain('<details><summary>Expand for details</summary>')
expect(report).not.toContain('</details>')
})
it('report auto-collapses when all tests pass', async () => {
// Test with a fixture that has all passing tests (no failures)
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit-eslint.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
// Verify this fixture has no failures
expect(result.failed).toBe(0)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'auto'
})
// Should collapse when all tests pass
expect(report).toContain('<details><summary>Expand for details</summary>')
expect(report).toContain('</details>')
})
it('report does not auto-collapse when tests fail', async () => {
// Test with a fixture that has failing tests
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
// Verify this fixture has failures
expect(result.failed).toBeGreaterThan(0)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'auto'
})
// Should not collapse when there are failures
expect(report).not.toContain('<details><summary>Expand for details</summary>')
expect(report).not.toContain('</details>')
})
it('report includes the short summary', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const shortSummary = '1 passed, 4 failed and 1 skipped'
const report = getReport([result], DEFAULT_OPTIONS, shortSummary)
// Report should have the title as the first line
expect(report).toMatch(/^## 1 passed, 4 failed and 1 skipped\n/)
})
it('report includes a custom report title and short summary', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'karma-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new KarmaJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const shortSummary = '1 passed, 4 failed and 1 skipped'
const report = getReport(
[result],
{
...DEFAULT_OPTIONS,
reportTitle: 'My Custom Title'
},
shortSummary
)
// Report should have the title as the first line
expect(report).toMatch(/^# My Custom Title\n## 1 passed, 4 failed and 1 skipped\n/)
})
})

View file

@ -22,6 +22,7 @@ import {RspecJsonParser} from './parsers/rspec-json/rspec-json-parser'
import {SwiftXunitParser} from './parsers/swift-xunit/swift-xunit-parser' import {SwiftXunitParser} from './parsers/swift-xunit/swift-xunit-parser'
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils' import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
import {getCheckRunContext} from './utils/github-utils' import {getCheckRunContext} from './utils/github-utils'
import {KarmaJunitParser} from './parsers/karma-junit/karma-junit-parser'
async function main(): Promise<void> { async function main(): Promise<void> {
try { try {
@ -269,6 +270,9 @@ class TestReporter {
return new JavaJunitParser(options) return new JavaJunitParser(options)
case 'jest-junit': case 'jest-junit':
return new JestJunitParser(options) return new JestJunitParser(options)
case 'karma-junit':
return new KarmaJunitParser(options)
case 'mocha-json': case 'mocha-json':
return new MochaJsonParser(options) return new MochaJsonParser(options)
case 'python-xunit': case 'python-xunit':

View file

@ -0,0 +1,125 @@
import {ParseOptions, TestParser} from '../../test-parser'
import {parseStringPromise} from 'xml2js'
import {KarmaReport, TestCase, TestSuite} from './karma-junit-types'
import {getExceptionSource} from '../../utils/node-utils'
import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
import {
TestExecutionResult,
TestRunResult,
TestSuiteResult,
TestGroupResult,
TestCaseResult,
TestCaseError
} from '../../test-results'
export class KarmaJunitParser implements TestParser {
assumedWorkDir: string | undefined
constructor(readonly options: ParseOptions) {}
async parse(path: string, content: string): Promise<TestRunResult> {
const ju = await this.getJunitReport(path, content)
return this.getTestRunResult(path, ju)
}
private async getJunitReport(path: string, content: string): Promise<KarmaReport> {
try {
return (await parseStringPromise(content)) as KarmaReport
} catch (e) {
throw new Error(`Invalid XML at ${path}\n\n${e}`)
}
}
private getTestRunResult(path: string, karma: KarmaReport): TestRunResult {
const suites =
karma.testsuite === undefined
? []
: [karma.testsuite].map(ts => {
const name = this.escapeCharacters(ts.$.name.trim())
const time = parseFloat(ts.$.time) * 1000
const sr = new TestSuiteResult(name, this.getGroups(ts), time)
return sr
})
const time = karma.testsuite?.$ && parseFloat(karma.testsuite.$.time) * 1000
return new TestRunResult(path, suites, time)
}
protected getGroups(suite: TestSuite): TestGroupResult[] {
if (!suite.testcase) {
return []
}
const groups: {describe: string; tests: TestCase[]}[] = []
for (const tc of suite.testcase) {
let grp = groups.find(g => g.describe === tc.$.classname)
if (grp === undefined) {
grp = {describe: tc.$.classname, tests: []}
groups.push(grp)
}
grp.tests.push(tc)
}
return groups.map(grp => {
const tests = grp.tests.map(tc => {
const name = tc.$.name.trim()
const result = this.getTestCaseResult(tc)
const time = parseFloat(tc.$.time) * 1000
const error = this.getTestCaseError(tc)
return new TestCaseResult(name, result, time, error)
})
return new TestGroupResult(grp.describe, tests)
})
}
private getTestCaseResult(test: TestCase): TestExecutionResult {
if (test.failure) return 'failed'
if (test.skipped) return 'skipped'
return 'success'
}
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
if (!this.options.parseErrors || !tc.failure) {
return undefined
}
const details = typeof tc.failure[0] === 'string' ? tc.failure[0] : tc.failure[0]['_']
let path
let line
const src = getExceptionSource(details, this.options.trackedFiles, file => this.getRelativePath(file))
if (src) {
path = src.path
line = src.line
}
return {
path,
line,
details
}
}
private getRelativePath(path: string): string {
path = normalizeFilePath(path)
const workDir = this.getWorkDir(path)
if (workDir !== undefined && path.startsWith(workDir)) {
path = path.substr(workDir.length)
}
return path
}
private getWorkDir(path: string): string | undefined {
return (
this.options.workDir ??
this.assumedWorkDir ??
(this.assumedWorkDir = getBasePath(path, this.options.trackedFiles))
)
}
protected escapeCharacters(s: string): string {
return s.replace(/([<>])/g, '\\$1')
}
}

View file

@ -0,0 +1,34 @@
export interface KarmaReport {
testsuite: TestSuite
}
export interface TestSuites {
$: {
time: string
}
testsuite?: TestSuite[]
}
export interface TestSuite {
$: {
name: string
tests: string
errors: string
failures: string
skipped: string
time: string
timestamp?: Date
}
testcase?: TestCase[]
}
export interface TestCase {
$: {
classname: string
file?: string
name: string
time: string
}
failure?: string[]
skipped?: string[]
}