├── .gitignore ├── LICENSE ├── README.md ├── dist └── index.js ├── img └── annotations.png ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Christopher Schleiden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update 2022-05-17: 2 | 3 | With version [28](https://jestjs.io/blog/2022/04/25/jest-28) Jest now includes a native GitHub Actions reporter. This repository and package won't go away, but I recommend switching over to the official reporter. The [blog post](https://jestjs.io/blog/2022/04/25/jest-28#github-actions-reporter) has some pointers on how to set this up. 4 | 5 | # Jest Reporter for GitHub Actions 6 | 7 | A custom Jest reporter to create annotations when run via GitHub Actions. 8 | 9 | ![](https://github.com/cschleiden/jest-github-actions-reporter/blob/master/img/annotations.png) 10 | 11 | 12 | ## Usage 13 | 14 | All you have to do to get annotations in your GitHub Actions runs is to add the reporter your Jest configuration. 15 | 16 | 1. Install `npm install -D jest-github-actions-reporter` 17 | 2. Add to your `jest.config.js`: 18 | ```js 19 | module.exports = { 20 | reporters: [ 21 | "default", 22 | "jest-github-actions-reporter" 23 | ], 24 | testLocationInResults: true 25 | }; 26 | ``` 27 | alternatively you can only add it during your CI build, for example in `package.json`: 28 | ```jsonc 29 | { 30 | // ... 31 | "scripts": { 32 | "citest": "CI=true jest --reporters=default --reporters=jest-github-actions-reporter" 33 | } 34 | } 35 | ``` 36 | 37 | nothing else is required, no token sharing, no REST API calls etc. 38 | 39 | ## Example 40 | 41 | `.github/workflows/CI.yaml` 42 | 43 | ```yaml 44 | name: CI 45 | 46 | on: [push] 47 | 48 | jobs: 49 | build: 50 | runs-on: ubuntu-latest 51 | 52 | strategy: 53 | matrix: 54 | node-version: [10.x, 12.x] 55 | 56 | steps: 57 | - uses: actions/checkout@v1 58 | - name: Use Node.js ${{ matrix.node-version }} 59 | uses: actions/setup-node@v1 60 | with: 61 | node-version: ${{ matrix.node-version }} 62 | - run: npm ci 63 | - run: npm run build --if-present 64 | - run: npm citest 65 | env: 66 | CI: true 67 | ``` 68 | 69 | ## How does this work? 70 | 71 | GitHub Actions supports a number of commands that allow you to provide rich experiences without custom REST API calls etc. See the [docs](https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message) for more information. 72 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const command_1 = require("@actions/core/lib/command"); 4 | class GitHubActionsReporter { 5 | constructor(globalConfig, options) { 6 | this.globalConfig = globalConfig; 7 | this.options = options; 8 | } 9 | onTestStart(test) { } 10 | onTestResult(test, testResult, results) { } 11 | onRunComplete(contexts, results) { 12 | command_1.issue("group", "Jest Annotations"); 13 | if (results.numFailedTests > 0) { 14 | for (const testResults of results.testResults.filter(x => x.numFailingTests > 0)) { 15 | for (const testResult of testResults.testResults) { 16 | for (const failureMessage of testResult.failureMessages) { 17 | const x = /\((.+?):(\d+):(\d+)\)/; 18 | const match = x.exec(failureMessage); 19 | if (match && match.length > 2) { 20 | command_1.issueCommand("error", { 21 | file: match[1], 22 | line: match[2], 23 | col: match[3] 24 | }, failureMessage); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | command_1.issue("endgroup"); 31 | } 32 | } 33 | module.exports = GitHubActionsReporter; 34 | -------------------------------------------------------------------------------- /img/annotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cschleiden/jest-github-actions-reporter/52600f38f69315302020e9e67bf30ead5706aca0/img/annotations.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-github-actions-reporter", 3 | "version": "1.0.2", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@actions/core": { 8 | "version": "1.2.6", 9 | "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", 10 | "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" 11 | }, 12 | "@jest/types": { 13 | "version": "24.9.0", 14 | "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", 15 | "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", 16 | "dev": true, 17 | "requires": { 18 | "@types/istanbul-lib-coverage": "^2.0.0", 19 | "@types/istanbul-reports": "^1.1.1", 20 | "@types/yargs": "^13.0.0" 21 | } 22 | }, 23 | "@types/istanbul-lib-coverage": { 24 | "version": "2.0.1", 25 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", 26 | "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", 27 | "dev": true 28 | }, 29 | "@types/istanbul-lib-report": { 30 | "version": "1.1.1", 31 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", 32 | "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", 33 | "dev": true, 34 | "requires": { 35 | "@types/istanbul-lib-coverage": "*" 36 | } 37 | }, 38 | "@types/istanbul-reports": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", 41 | "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", 42 | "dev": true, 43 | "requires": { 44 | "@types/istanbul-lib-coverage": "*", 45 | "@types/istanbul-lib-report": "*" 46 | } 47 | }, 48 | "@types/jest": { 49 | "version": "24.0.23", 50 | "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz", 51 | "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==", 52 | "dev": true, 53 | "requires": { 54 | "jest-diff": "^24.3.0" 55 | } 56 | }, 57 | "@types/node": { 58 | "version": "12.12.8", 59 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.8.tgz", 60 | "integrity": "sha512-XLla8N+iyfjvsa0KKV+BP/iGSoTmwxsu5Ci5sM33z9TjohF72DEz95iNvD6pPmemvbQgxAv/909G73gUn8QR7w==", 61 | "dev": true 62 | }, 63 | "@types/yargs": { 64 | "version": "13.0.3", 65 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", 66 | "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", 67 | "dev": true, 68 | "requires": { 69 | "@types/yargs-parser": "*" 70 | } 71 | }, 72 | "@types/yargs-parser": { 73 | "version": "13.1.0", 74 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", 75 | "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", 76 | "dev": true 77 | }, 78 | "ansi-regex": { 79 | "version": "4.1.0", 80 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 81 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 82 | "dev": true 83 | }, 84 | "ansi-styles": { 85 | "version": "3.2.1", 86 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 87 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 88 | "dev": true, 89 | "requires": { 90 | "color-convert": "^1.9.0" 91 | } 92 | }, 93 | "chalk": { 94 | "version": "2.4.2", 95 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 96 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 97 | "dev": true, 98 | "requires": { 99 | "ansi-styles": "^3.2.1", 100 | "escape-string-regexp": "^1.0.5", 101 | "supports-color": "^5.3.0" 102 | } 103 | }, 104 | "color-convert": { 105 | "version": "1.9.3", 106 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 107 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 108 | "dev": true, 109 | "requires": { 110 | "color-name": "1.1.3" 111 | } 112 | }, 113 | "color-name": { 114 | "version": "1.1.3", 115 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 116 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 117 | "dev": true 118 | }, 119 | "diff-sequences": { 120 | "version": "24.9.0", 121 | "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", 122 | "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", 123 | "dev": true 124 | }, 125 | "escape-string-regexp": { 126 | "version": "1.0.5", 127 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 128 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 129 | "dev": true 130 | }, 131 | "has-flag": { 132 | "version": "3.0.0", 133 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 134 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 135 | "dev": true 136 | }, 137 | "jest-diff": { 138 | "version": "24.9.0", 139 | "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", 140 | "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", 141 | "dev": true, 142 | "requires": { 143 | "chalk": "^2.0.1", 144 | "diff-sequences": "^24.9.0", 145 | "jest-get-type": "^24.9.0", 146 | "pretty-format": "^24.9.0" 147 | } 148 | }, 149 | "jest-get-type": { 150 | "version": "24.9.0", 151 | "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", 152 | "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", 153 | "dev": true 154 | }, 155 | "pretty-format": { 156 | "version": "24.9.0", 157 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", 158 | "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", 159 | "dev": true, 160 | "requires": { 161 | "@jest/types": "^24.9.0", 162 | "ansi-regex": "^4.0.0", 163 | "ansi-styles": "^3.2.0", 164 | "react-is": "^16.8.4" 165 | } 166 | }, 167 | "react-is": { 168 | "version": "16.12.0", 169 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", 170 | "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", 171 | "dev": true 172 | }, 173 | "supports-color": { 174 | "version": "5.5.0", 175 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 176 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 177 | "dev": true, 178 | "requires": { 179 | "has-flag": "^3.0.0" 180 | } 181 | }, 182 | "typescript": { 183 | "version": "3.7.2", 184 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", 185 | "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", 186 | "dev": true 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jest-github-actions-reporter", 3 | "version": "1.0.3", 4 | "description": "Reporter for Jest that outputs GitHub Actions annotations", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "watch": "tsc --watch", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "tsc", 10 | "prepublish": "npm run build" 11 | }, 12 | "author": "cschleiden", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@actions/core": "^1.2.0" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/cschleiden/jest-github-actions-reporter" 20 | }, 21 | "devDependencies": { 22 | "@types/jest": "^24.0.23", 23 | "@types/node": "^12.12.8", 24 | "typescript": "^3.7.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { issueCommand, issue } from "@actions/core/lib/command"; 2 | 3 | class GitHubActionsReporter implements jest.Reporter { 4 | constructor(public globalConfig: jest.GlobalConfig, private options: any) {} 5 | 6 | public onTestStart(test: jest.Test) {} 7 | 8 | public onTestResult( 9 | test: jest.Test, 10 | testResult: jest.TestResult, 11 | results: jest.AggregatedResult 12 | ) {} 13 | 14 | public onRunComplete( 15 | contexts: Set, 16 | results: jest.AggregatedResult 17 | ) { 18 | issue("group", "Jest Annotations"); 19 | 20 | if (results.numFailedTests > 0) { 21 | for (const testResults of results.testResults.filter( 22 | x => x.numFailingTests > 0 23 | )) { 24 | for (const testResult of testResults.testResults) { 25 | for (const failureMessage of testResult.failureMessages) { 26 | const x = /\((.+?):(\d+):(\d+)\)/; 27 | const match = x.exec(failureMessage); 28 | if (match && match.length > 2) { 29 | issueCommand( 30 | "error", 31 | { 32 | file: match[1], 33 | line: match[2], 34 | col: match[3] 35 | }, 36 | failureMessage 37 | ); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | issue("endgroup"); 45 | } 46 | } 47 | 48 | module.exports = GitHubActionsReporter; 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "outDir": "dist" 9 | }, 10 | "include": ["src/**/*.ts"] 11 | } 12 | --------------------------------------------------------------------------------