mirror of
https://github.com/dorny/test-reporter.git
synced 2026-02-02 03:15:22 -08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d00bb14cb | ||
|
|
a585725c8b | ||
|
|
de0b4b9ece | ||
|
|
ad831af420 | ||
|
|
2ac8b4498f | ||
|
|
17e793242c | ||
|
|
e8f4fdfec7 | ||
|
|
d01ef000ba |
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## v1.5.0
|
||||
- [Add option to convert backslashes in path pattern to forward slashes](https://github.com/dorny/test-reporter/pull/128)
|
||||
- [Add option to generate only the summary from processed test results files](https://github.com/dorny/test-reporter/pull/123)
|
||||
|
||||
## v1.4.3
|
||||
- [Patch java-junit to handle missing time field](https://github.com/dorny/test-reporter/pull/115)
|
||||
- [Fix dart-json parsing broken by print message](https://github.com/dorny/test-reporter/pull/114)
|
||||
|
||||
10
README.md
10
README.md
@@ -119,6 +119,11 @@ jobs:
|
||||
# All matched result files must be of the same format
|
||||
path: ''
|
||||
|
||||
# The fast-glob library that is internally used interprets backslashes as escape characters.
|
||||
# If enabled, all backslashes in provided path will be replaced by forward slashes and act as directory separators.
|
||||
# It might be useful when path input variable is composed dynamically from existing directory paths on Windows.
|
||||
path-replace-backslashes: 'false'
|
||||
|
||||
# Format of test results. Supported options:
|
||||
# dart-json
|
||||
# dotnet-trx
|
||||
@@ -128,6 +133,11 @@ jobs:
|
||||
# mocha-json
|
||||
reporter: ''
|
||||
|
||||
# Allows you to generate only the summary.
|
||||
# If enabled, the report will contain a table listing each test results file and the number of passed, failed, and skipped tests.
|
||||
# Detailed listing of test suites and test cases will be skipped.
|
||||
only-summary: 'false'
|
||||
|
||||
# Limits which test suites are listed:
|
||||
# all
|
||||
# failed
|
||||
|
||||
14
action.yml
14
action.yml
@@ -15,6 +15,13 @@ inputs:
|
||||
Supports wildcards via [fast-glob](https://github.com/mrmlnc/fast-glob)
|
||||
All matched result files must be of same format
|
||||
required: true
|
||||
path-replace-backslashes:
|
||||
description: |
|
||||
The fast-glob library that is internally used interprets backslashes as escape characters.
|
||||
If enabled, all backslashes in provided path will be replaced by forward slashes and act as directory separators.
|
||||
It might be useful when path input variable is composed dynamically from existing directory paths on Windows.
|
||||
default: 'false'
|
||||
required: false
|
||||
reporter:
|
||||
description: |
|
||||
Format of test results. Supported options:
|
||||
@@ -53,6 +60,13 @@ inputs:
|
||||
working-directory:
|
||||
description: Relative path under $GITHUB_WORKSPACE where the repository was checked out
|
||||
required: false
|
||||
only-summary:
|
||||
description: |
|
||||
Allows you to generate only the summary.
|
||||
If enabled, the report will contain a table listing each test results file and the number of passed, failed, and skipped tests.
|
||||
Detailed listing of test suites and test cases will be skipped.
|
||||
default: 'false'
|
||||
required: false
|
||||
token:
|
||||
description: GitHub Access Token
|
||||
required: false
|
||||
|
||||
38
dist/index.js
generated
vendored
38
dist/index.js
generated
vendored
@@ -239,12 +239,14 @@ class TestReporter {
|
||||
this.artifact = core.getInput('artifact', { required: false });
|
||||
this.name = core.getInput('name', { required: true });
|
||||
this.path = core.getInput('path', { required: true });
|
||||
this.pathReplaceBackslashes = core.getInput('path-replace-backslashes', { required: false }) === 'true';
|
||||
this.reporter = core.getInput('reporter', { required: true });
|
||||
this.listSuites = core.getInput('list-suites', { required: true });
|
||||
this.listTests = core.getInput('list-tests', { required: true });
|
||||
this.maxAnnotations = parseInt(core.getInput('max-annotations', { required: true }));
|
||||
this.failOnError = core.getInput('fail-on-error', { required: true }) === 'true';
|
||||
this.workDirInput = core.getInput('working-directory', { required: false });
|
||||
this.onlySummary = core.getInput('only-summary', { required: false }) === 'true';
|
||||
this.token = core.getInput('token', { required: true });
|
||||
this.context = github_utils_1.getCheckRunContext();
|
||||
this.octokit = github.getOctokit(this.token);
|
||||
@@ -267,7 +269,10 @@ class TestReporter {
|
||||
process.chdir(this.workDirInput);
|
||||
}
|
||||
core.info(`Check runs will be created with SHA=${this.context.sha}`);
|
||||
const pattern = this.path.split(',');
|
||||
// Split path pattern by ',' and optionally convert all backslashes to forward slashes
|
||||
// fast-glob (micromatch) always interprets backslashes as escape characters instead of directory separators
|
||||
const pathsList = this.path.split(',');
|
||||
const pattern = this.pathReplaceBackslashes ? pathsList.map(path_utils_1.normalizeFilePath) : pathsList;
|
||||
const inputProvider = this.artifact
|
||||
? new artifact_provider_1.ArtifactProvider(this.octokit, this.artifact, this.name, pattern, this.context.sha, this.context.runId, this.token)
|
||||
: new local_file_provider_1.LocalFileProvider(this.name, pattern);
|
||||
@@ -337,9 +342,9 @@ class TestReporter {
|
||||
...github.context.repo
|
||||
});
|
||||
core.info('Creating report summary');
|
||||
const { listSuites, listTests } = this;
|
||||
const { listSuites, listTests, onlySummary } = this;
|
||||
const baseUrl = createResp.data.html_url;
|
||||
const summary = get_report_1.getReport(results, { listSuites, listTests, baseUrl });
|
||||
const summary = get_report_1.getReport(results, { listSuites, listTests, baseUrl, onlySummary });
|
||||
core.info('Creating annotations');
|
||||
const annotations = get_annotations_1.getAnnotations(results, this.maxAnnotations);
|
||||
const isFailed = results.some(tr => tr.result === 'failed');
|
||||
@@ -930,18 +935,24 @@ class JavaJunitParser {
|
||||
});
|
||||
}
|
||||
getTestCaseResult(test) {
|
||||
if (test.failure)
|
||||
if (test.failure || test.error)
|
||||
return 'failed';
|
||||
if (test.skipped)
|
||||
return 'skipped';
|
||||
return 'success';
|
||||
}
|
||||
getTestCaseError(tc) {
|
||||
if (!this.options.parseErrors || !tc.failure) {
|
||||
var _a;
|
||||
if (!this.options.parseErrors) {
|
||||
return undefined;
|
||||
}
|
||||
const failure = tc.failure[0];
|
||||
const details = failure._;
|
||||
// We process <error> and <failure> the same way
|
||||
const failures = (_a = tc.failure) !== null && _a !== void 0 ? _a : tc.error;
|
||||
if (!failures) {
|
||||
return undefined;
|
||||
}
|
||||
const failure = failures[0];
|
||||
const details = typeof failure === 'object' ? failure._ : failure;
|
||||
let filePath;
|
||||
let line;
|
||||
const src = this.exceptionThrowSource(details);
|
||||
@@ -953,7 +964,7 @@ class JavaJunitParser {
|
||||
path: filePath,
|
||||
line,
|
||||
details,
|
||||
message: failure.message
|
||||
message: typeof failure === 'object' ? failure.message : undefined
|
||||
};
|
||||
}
|
||||
exceptionThrowSource(stackTrace) {
|
||||
@@ -1344,7 +1355,8 @@ const MAX_REPORT_LENGTH = 65535;
|
||||
const defaultOptions = {
|
||||
listSuites: 'all',
|
||||
listTests: 'all',
|
||||
baseUrl: ''
|
||||
baseUrl: '',
|
||||
onlySummary: false
|
||||
};
|
||||
function getReport(results, options = defaultOptions) {
|
||||
core.info('Generating check run summary');
|
||||
@@ -1442,7 +1454,7 @@ function getBadge(passed, failed, skipped) {
|
||||
}
|
||||
function getTestRunsReport(testRuns, options) {
|
||||
const sections = [];
|
||||
if (testRuns.length > 1) {
|
||||
if (testRuns.length > 1 || options.onlySummary) {
|
||||
const tableData = testRuns.map((tr, runIndex) => {
|
||||
const time = markdown_utils_1.formatTime(tr.time);
|
||||
const name = tr.path;
|
||||
@@ -1456,8 +1468,10 @@ function getTestRunsReport(testRuns, options) {
|
||||
const resultsTable = markdown_utils_1.table(['Report', 'Passed', 'Failed', 'Skipped', 'Time'], [markdown_utils_1.Align.Left, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right, markdown_utils_1.Align.Right], ...tableData);
|
||||
sections.push(resultsTable);
|
||||
}
|
||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
|
||||
sections.push(...suitesReports);
|
||||
if (options.onlySummary === false) {
|
||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
|
||||
sections.push(...suitesReports);
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
function getSuitesReport(tr, runIndex, options) {
|
||||
|
||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
36
dist/licenses.txt
generated
vendored
36
dist/licenses.txt
generated
vendored
@@ -341,27 +341,27 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
|
||||
adm-zip
|
||||
MIT
|
||||
Copyright (c) 2012 Another-D-Mention Software and other contributors,
|
||||
http://www.another-d-mention.ro/
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
Copyright (c) 2012 Another-D-Mention Software and other contributors
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
before-after-hook
|
||||
|
||||
14
src/main.ts
14
src/main.ts
@@ -16,7 +16,7 @@ import {JavaJunitParser} from './parsers/java-junit/java-junit-parser'
|
||||
import {JestJunitParser} from './parsers/jest-junit/jest-junit-parser'
|
||||
import {MochaJsonParser} from './parsers/mocha-json/mocha-json-parser'
|
||||
|
||||
import {normalizeDirPath} from './utils/path-utils'
|
||||
import {normalizeDirPath, normalizeFilePath} from './utils/path-utils'
|
||||
import {getCheckRunContext} from './utils/github-utils'
|
||||
import {Icon} from './utils/markdown-utils'
|
||||
|
||||
@@ -33,12 +33,14 @@ class TestReporter {
|
||||
readonly artifact = core.getInput('artifact', {required: false})
|
||||
readonly name = core.getInput('name', {required: true})
|
||||
readonly path = core.getInput('path', {required: true})
|
||||
readonly pathReplaceBackslashes = core.getInput('path-replace-backslashes', {required: false}) === 'true'
|
||||
readonly reporter = core.getInput('reporter', {required: true})
|
||||
readonly listSuites = core.getInput('list-suites', {required: true}) as 'all' | 'failed'
|
||||
readonly listTests = core.getInput('list-tests', {required: true}) as 'all' | 'failed' | 'none'
|
||||
readonly maxAnnotations = parseInt(core.getInput('max-annotations', {required: true}))
|
||||
readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
|
||||
readonly workDirInput = core.getInput('working-directory', {required: false})
|
||||
readonly onlySummary = core.getInput('only-summary', {required: false}) === 'true'
|
||||
readonly token = core.getInput('token', {required: true})
|
||||
readonly octokit: InstanceType<typeof GitHub>
|
||||
readonly context = getCheckRunContext()
|
||||
@@ -70,7 +72,11 @@ class TestReporter {
|
||||
|
||||
core.info(`Check runs will be created with SHA=${this.context.sha}`)
|
||||
|
||||
const pattern = this.path.split(',')
|
||||
// Split path pattern by ',' and optionally convert all backslashes to forward slashes
|
||||
// fast-glob (micromatch) always interprets backslashes as escape characters instead of directory separators
|
||||
const pathsList = this.path.split(',')
|
||||
const pattern = this.pathReplaceBackslashes ? pathsList.map(normalizeFilePath) : pathsList
|
||||
|
||||
const inputProvider = this.artifact
|
||||
? new ArtifactProvider(
|
||||
this.octokit,
|
||||
@@ -160,9 +166,9 @@ class TestReporter {
|
||||
})
|
||||
|
||||
core.info('Creating report summary')
|
||||
const {listSuites, listTests} = this
|
||||
const {listSuites, listTests, onlySummary} = this
|
||||
const baseUrl = createResp.data.html_url
|
||||
const summary = getReport(results, {listSuites, listTests, baseUrl})
|
||||
const summary = getReport(results, {listSuites, listTests, baseUrl, onlySummary})
|
||||
|
||||
core.info('Creating annotations')
|
||||
const annotations = getAnnotations(results, this.maxAnnotations)
|
||||
|
||||
@@ -107,18 +107,24 @@ export class JavaJunitParser implements TestParser {
|
||||
}
|
||||
|
||||
private getTestCaseResult(test: TestCase): TestExecutionResult {
|
||||
if (test.failure) return 'failed'
|
||||
if (test.failure || test.error) return 'failed'
|
||||
if (test.skipped) return 'skipped'
|
||||
return 'success'
|
||||
}
|
||||
|
||||
private getTestCaseError(tc: TestCase): TestCaseError | undefined {
|
||||
if (!this.options.parseErrors || !tc.failure) {
|
||||
if (!this.options.parseErrors) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const failure = tc.failure[0]
|
||||
const details = failure._
|
||||
// We process <error> and <failure> the same way
|
||||
const failures = tc.failure ?? tc.error
|
||||
if (!failures) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const failure = failures[0]
|
||||
const details = typeof failure === 'object' ? failure._ : failure
|
||||
let filePath
|
||||
let line
|
||||
|
||||
@@ -132,7 +138,7 @@ export class JavaJunitParser implements TestParser {
|
||||
path: filePath,
|
||||
line,
|
||||
details,
|
||||
message: failure.message
|
||||
message: typeof failure === 'object' ? failure.message : undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ export interface TestCase {
|
||||
name: string
|
||||
time: string
|
||||
}
|
||||
failure?: Failure[]
|
||||
failure?: string | Failure[]
|
||||
error?: string | Failure[]
|
||||
skipped?: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,14 @@ export interface ReportOptions {
|
||||
listSuites: 'all' | 'failed'
|
||||
listTests: 'all' | 'failed' | 'none'
|
||||
baseUrl: string
|
||||
onlySummary: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: ReportOptions = {
|
||||
listSuites: 'all',
|
||||
listTests: 'all',
|
||||
baseUrl: ''
|
||||
baseUrl: '',
|
||||
onlySummary: false
|
||||
}
|
||||
|
||||
export function getReport(results: TestRunResult[], options: ReportOptions = defaultOptions): string {
|
||||
@@ -132,7 +134,7 @@ function getBadge(passed: number, failed: number, skipped: number): string {
|
||||
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
|
||||
const sections: string[] = []
|
||||
|
||||
if (testRuns.length > 1) {
|
||||
if (testRuns.length > 1 || options.onlySummary) {
|
||||
const tableData = testRuns.map((tr, runIndex) => {
|
||||
const time = formatTime(tr.time)
|
||||
const name = tr.path
|
||||
@@ -152,8 +154,10 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
|
||||
sections.push(resultsTable)
|
||||
}
|
||||
|
||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
|
||||
sections.push(...suitesReports)
|
||||
if (options.onlySummary === false) {
|
||||
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat()
|
||||
sections.push(...suitesReports)
|
||||
}
|
||||
return sections
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user