This commit is contained in:
George Young 2025-12-04 23:44:21 +01:00 committed by GitHub
commit ce3cccd386
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 12565 additions and 1 deletions

View file

@ -342,6 +342,16 @@ You can use the following example configuration in `package.json`:
Configuration of `uniqueOutputName`, `suiteNameTemplate`, `classNameTemplate`, `titleTemplate` is important for proper visualization of test results.
</details>
<details>
<summary>karma-junit (Experimental)</summary>
Support for Karma test results in Junit format is experimental - should work but it was not extensively tested.
[Karma](https://karma-runner.github.io/latest/index.html) testing framework support requires the usage of [karma-junit-reporter](https://github.com/karma-runner/karma-junit-reporter).
It will create test results in Junit XML format which can be then processed by this action.
</details>
<details>
<summary>mocha-json</summary>

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/>
</testcase>
</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/)
})
})

115
dist/index.js generated vendored
View file

@ -282,6 +282,7 @@ const rspec_json_parser_1 = __nccwpck_require__(9768);
const swift_xunit_parser_1 = __nccwpck_require__(7330);
const path_utils_1 = __nccwpck_require__(9132);
const github_utils_1 = __nccwpck_require__(6667);
const karma_junit_parser_1 = __nccwpck_require__(3946);
async function main() {
try {
const testReporter = new TestReporter();
@ -492,6 +493,8 @@ class TestReporter {
return new java_junit_parser_1.JavaJunitParser(options);
case 'jest-junit':
return new jest_junit_parser_1.JestJunitParser(options);
case 'karma-junit':
return new karma_junit_parser_1.KarmaJunitParser(options);
case 'mocha-json':
return new mocha_json_parser_1.MochaJsonParser(options);
case 'python-xunit':
@ -1563,6 +1566,118 @@ class JestJunitParser {
exports.JestJunitParser = JestJunitParser;
/***/ }),
/***/ 3946:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.KarmaJunitParser = void 0;
const xml2js_1 = __nccwpck_require__(758);
const node_utils_1 = __nccwpck_require__(5384);
const path_utils_1 = __nccwpck_require__(9132);
const test_results_1 = __nccwpck_require__(613);
class KarmaJunitParser {
options;
assumedWorkDir;
constructor(options) {
this.options = options;
}
async parse(path, content) {
const ju = await this.getJunitReport(path, content);
return this.getTestRunResult(path, ju);
}
async getJunitReport(path, content) {
try {
return (await (0, xml2js_1.parseStringPromise)(content));
}
catch (e) {
throw new Error(`Invalid XML at ${path}\n\n${e}`);
}
}
getTestRunResult(path, karma) {
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 test_results_1.TestSuiteResult(name, this.getGroups(ts), time);
return sr;
});
const time = karma.testsuite?.$ && parseFloat(karma.testsuite.$.time) * 1000;
return new test_results_1.TestRunResult(path, suites, time);
}
getGroups(suite) {
if (!suite.testcase) {
return [];
}
const groups = [];
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 test_results_1.TestCaseResult(name, result, time, error);
});
return new test_results_1.TestGroupResult(grp.describe, tests);
});
}
getTestCaseResult(test) {
if (test.failure)
return 'failed';
if (test.skipped)
return 'skipped';
return 'success';
}
getTestCaseError(tc) {
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 = (0, node_utils_1.getExceptionSource)(details, this.options.trackedFiles, file => this.getRelativePath(file));
if (src) {
path = src.path;
line = src.line;
}
return {
path,
line,
details
};
}
getRelativePath(path) {
path = (0, path_utils_1.normalizeFilePath)(path);
const workDir = this.getWorkDir(path);
if (workDir !== undefined && path.startsWith(workDir)) {
path = path.substr(workDir.length);
}
return path;
}
getWorkDir(path) {
return (this.options.workDir ??
this.assumedWorkDir ??
(this.assumedWorkDir = (0, path_utils_1.getBasePath)(path, this.options.trackedFiles)));
}
escapeCharacters(s) {
return s.replace(/([<>])/g, '\\$1');
}
}
exports.KarmaJunitParser = KarmaJunitParser;
/***/ }),
/***/ 5402:

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 {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
import {getCheckRunContext} from './utils/github-utils'
import {KarmaJunitParser} from './parsers/karma-junit/karma-junit-parser'
async function main(): Promise<void> {
try {
@ -269,6 +270,8 @@ class TestReporter {
return new JavaJunitParser(options)
case 'jest-junit':
return new JestJunitParser(options)
case 'karma-junit':
return new KarmaJunitParser(options)
case 'mocha-json':
return new MochaJsonParser(options)
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[]
}