@testream/pytest-reporter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +30 -0
- package/README.md +29 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Proprietary License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Testream Team. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are licensed,
|
|
6
|
+
not sold, to you for use only under the terms of this license.
|
|
7
|
+
|
|
8
|
+
GRANT OF LICENSE:
|
|
9
|
+
You are granted a non-exclusive, non-transferable license to use the Software
|
|
10
|
+
solely for the purpose of integrating test result reporting with the Testream service.
|
|
11
|
+
|
|
12
|
+
RESTRICTIONS:
|
|
13
|
+
- You may not modify, adapt, or create derivative works of the Software
|
|
14
|
+
- You may not distribute, sublicense, lease, or rent the Software
|
|
15
|
+
- You may not reverse engineer, decompile, or disassemble the Software
|
|
16
|
+
- The Software may only be used in conjunction with the Testream service
|
|
17
|
+
|
|
18
|
+
OWNERSHIP:
|
|
19
|
+
The Software is licensed, not sold. This license does not grant you any rights
|
|
20
|
+
to trademarks or service marks.
|
|
21
|
+
|
|
22
|
+
TERMINATION:
|
|
23
|
+
This license is effective until terminated. Your rights under this license will
|
|
24
|
+
terminate automatically without notice if you fail to comply with any term of
|
|
25
|
+
this license.
|
|
26
|
+
|
|
27
|
+
DISCLAIMER:
|
|
28
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
29
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @testream/pytest-reporter
|
|
2
|
+
|
|
3
|
+
Run pytest and send the results from your codebase to Jira via Testream.
|
|
4
|
+
|
|
5
|
+
## CI Example (GitHub Actions)
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
- uses: actions/setup-python@v5
|
|
9
|
+
with:
|
|
10
|
+
python-version: '3.12'
|
|
11
|
+
|
|
12
|
+
- uses: actions/setup-node@v4
|
|
13
|
+
with:
|
|
14
|
+
node-version: '20'
|
|
15
|
+
|
|
16
|
+
- run: pip install pytest
|
|
17
|
+
|
|
18
|
+
- run: npm install -g @testream/pytest-reporter
|
|
19
|
+
|
|
20
|
+
- run: |
|
|
21
|
+
testream-pytest \
|
|
22
|
+
--api-key "${{ secrets.TESTREAM_API_KEY }}" \
|
|
23
|
+
--fail-on-error
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
By default, `testream-pytest` runs `pytest` and writes JUnit XML to `junit/pytest-junit.xml`.
|
|
27
|
+
Use `--junit-path` only if you want to skip test execution and ingest existing JUnit XML.
|
|
28
|
+
|
|
29
|
+
For CLI options and CI examples, see https://docs.testream.app/reporters/pytest.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export type { PytestReporterOptions } from "./types";
|
|
3
|
+
export type { CTRFReport, CTRFTest, CTRFSummary, CTRFResults, CTRFAttachment, CTRFStep, UploadResult, UploadTestRunOptions, CIContext, } from "@jira-test-manager/shared-types";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuXA,YAAY,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,YAAY,EACV,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,oBAAoB,EACpB,SAAS,GACV,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
(()=>{"use strict";var e={18:function(e,t,r){var n=this&&this.__createBinding||(Object.create?function(e,t,r,n){if(n===undefined)n=r;var o=Object.getOwnPropertyDescriptor(t,r);if(!o||("get"in o?!t.__esModule:o.writable||o.configurable)){o={enumerable:true,get:function(){return t[r]}}}Object.defineProperty(e,n,o)}:function(e,t,r,n){if(n===undefined)n=r;e[n]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var i=this&&this.__importStar||function(){var ownKeys=function(e){ownKeys=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))t[t.length]=r;return t};return ownKeys(e)};return function(e){if(e&&e.__esModule)return e;var t={};if(e!=null)for(var r=ownKeys(e),i=0;i<r.length;i++)if(r[i]!=="default")n(t,e,r[i]);o(t,e);return t}}();Object.defineProperty(t,"__esModule",{value:true});const s=r(317);const a=i(r(928));const p=i(r(943));const l=r(617);const u="junit/pytest-junit.xml";function printUsage(){console.log(`\n@testream/pytest-reporter - Run pytest and upload results to Testream\n\nUsage:\n npx @testream/pytest-reporter --api-key <key> [options]\n\nRequired arguments (unless --no-upload):\n --api-key, -k API key for authentication\n\nOptional arguments:\n --project <path> Path to pytest project (defaults to current directory)\n --python <path> Python executable (default: python, fallback: python3)\n --junit-path <path> Skip running pytest, ingest existing JUnit XML path/glob\n\nGit context (auto-detected in CI):\n --branch Git branch name\n --commit-sha Git commit SHA\n --repository-url Git repository URL\n\nBuild metadata (auto-detected in CI):\n --build-name Build name/identifier\n --build-number Build number\n --build-url Build URL (CI pipeline URL)\n\nEnvironment metadata:\n --test-environment Test environment (e.g., staging, production, ci)\n --app-name Application name under test\n --app-version Application version under test\n --test-type Test type (e.g., unit, integration, e2e)\n\nUpload options:\n --no-upload Skip uploading CTRF results\n --fail-on-error Fail process if upload fails\n\nOther:\n --help, -h Show help message\n -- <args> Additional arguments to pass to pytest\n\nExamples:\n # Run pytest and upload\n npx @testream/pytest-reporter -k your-api-key\n\n # Ingest existing JUnit XML without running pytest\n npx @testream/pytest-reporter -k your-api-key --junit-path ./reports/junit.xml\n`)}function parseArgs(e){const t={uploadEnabled:true,failOnUploadError:false,continueOnTestFailure:true};const r=[];let n=false;for(let o=0;o<e.length;o++){const i=e[o];const s=e[o+1];if(i==="--"){n=true;continue}if(n){r.push(i);continue}switch(i){case"--help":case"-h":printUsage();process.exit(0);break;case"--api-key":case"-k":t.apiKey=s;o++;break;case"--project":t.projectPath=s;o++;break;case"--python":t.pythonExecutable=s;o++;break;case"--junit-path":t.junitPath=s;o++;break;case"--branch":t.branch=s;o++;break;case"--commit-sha":t.commitSha=s;o++;break;case"--repository-url":t.repositoryUrl=s;o++;break;case"--build-name":t.buildName=s;o++;break;case"--build-number":t.buildNumber=s;o++;break;case"--build-url":t.buildUrl=s;o++;break;case"--test-environment":t.testEnvironment=s;o++;break;case"--app-name":t.appName=s;o++;break;case"--app-version":t.appVersion=s;o++;break;case"--test-type":t.testType=s;o++;break;case"--no-upload":t.uploadEnabled=false;break;case"--fail-on-error":t.failOnUploadError=true;break;default:if(i.startsWith("-")){console.error(`Unknown argument: ${i}`);return null}break}}if(r.length>0){t.pytestArgs=r}if(!t.apiKey&&t.uploadEnabled){console.error("Error: Missing required argument: --api-key (unless --no-upload is set)");return null}return t}function isPytestTestFailureCode(e){return e===1}async function runPytest(e){const t=e.projectPath?a.resolve(e.projectPath):process.cwd();const r=a.resolve(t,u);await p.mkdir(a.dirname(r),{recursive:true});const n=["-m","pytest","--junitxml",r,...e.pytestArgs??[]];const o=e.pythonExecutable?[e.pythonExecutable]:process.platform==="win32"?["python","py"]:["python","python3"];let i=null;for(const a of o){console.info(`Running pytest with ${a}...`);console.info(` Command: ${a} -m pytest --junitxml ${r}`);if((e.pytestArgs??[]).length>0){console.info(` Additional pytest args: ${(e.pytestArgs??[]).length} provided`)}console.info(` Working directory: ${t}`);const o=await new Promise(((e,r)=>{const o=(0,s.spawn)(a,n,{cwd:t,stdio:["inherit","inherit","inherit"],shell:false});o.on("error",(e=>{r(e)}));o.on("close",(t=>{e(t)}))})).catch((e=>{i=e;return null}));if(i&&o===null){continue}try{await p.access(r)}catch{throw new Error(`Pytest run completed but no JUnit XML was found at: ${r}`)}return{junitPath:r,exitCode:o}}const l=i instanceof Error?i.message:"Failed to start python executable for pytest";throw new Error(l)}async function main(){const e=process.argv.slice(2);if(e.length===0){printUsage();process.exit(1)}const t=parseArgs(e);if(!t){process.exit(1)}let r=false;let n=0;try{let e=t.junitPath;if(e){console.info("Using existing JUnit XML file(s), tests will not be re-run...")}else{const o=await runPytest(t);e=o.junitPath;n=o.exitCode;if(o.exitCode&&o.exitCode!==0){r=isPytestTestFailureCode(o.exitCode);if(!r){throw new Error(`pytest exited with code ${o.exitCode}`)}}}const o=await(0,l.convertJUnitToCtrf)({junitPath:e});o.report.results.tool.name="pytest";o.report.generatedBy="@testream/pytest-reporter";await p.writeFile(o.reportPath,JSON.stringify(o.report,null,2));let i;const s=t.uploadEnabled!==false;if(s&&t.apiKey){i=await(0,l.uploadCtrfReport)({report:o.report,apiKey:t.apiKey,apiUrl:t.apiUrl,branch:t.branch,commitSha:t.commitSha,repositoryUrl:t.repositoryUrl,buildName:t.buildName,buildNumber:t.buildNumber,buildUrl:t.buildUrl,testEnvironment:t.testEnvironment,appName:t.appName,appVersion:t.appVersion,testType:t.testType});if(!i.success&&t.failOnUploadError){throw new Error(i.error||"Upload failed")}}console.info("Test run ingestion completed");console.info(` JUnit source files: ${o.sourceFiles.length}`);console.info(` CTRF output: ${o.reportPath}`);console.info(` Tests: ${o.report.results.summary.tests}`);console.info(` Passed: ${o.report.results.summary.passed}`);console.info(` Failed: ${o.report.results.summary.failed}`);console.info(` Skipped: ${o.report.results.summary.skipped}`);if(!i){console.info("Upload disabled, skipping upload")}if(r){process.exit(n||1)}}catch(e){const t=e instanceof Error?e.message:String(e);console.error(`Error: ${t}`);process.exit(1)}}main()},617:e=>{e.exports=require("@testream/junit-reporter")},317:e=>{e.exports=require("child_process")},943:e=>{e.exports=require("fs/promises")},928:e=>{e.exports=require("path")}};var t={};function __nccwpck_require__(r){var n=t[r];if(n!==undefined){return n.exports}var o=t[r]={exports:{}};var i=true;try{e[r].call(o.exports,o,o.exports,__nccwpck_require__);i=false}finally{if(i)delete t[r]}return o.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r=__nccwpck_require__(18);module.exports=r})();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JUnitReporterOptions } from '@testream/junit-reporter';
|
|
2
|
+
export interface PytestReporterOptions extends JUnitReporterOptions {
|
|
3
|
+
/** Path to the pytest project directory. Defaults to current directory */
|
|
4
|
+
projectPath?: string;
|
|
5
|
+
/** Explicit python executable. Defaults to python, then python3 */
|
|
6
|
+
pythonExecutable?: string;
|
|
7
|
+
/** Additional args passed to pytest */
|
|
8
|
+
pytestArgs?: string[];
|
|
9
|
+
/** Skip running pytest and ingest an existing JUnit XML path/glob */
|
|
10
|
+
junitPath?: string;
|
|
11
|
+
/** Continue ingest/upload even if pytest exits with failing tests */
|
|
12
|
+
continueOnTestFailure?: boolean;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAErE,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IACjE,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testream/pytest-reporter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to run pytest with JUnit XML output and upload results to Testream",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "npm run build:bundle && npm run build:types",
|
|
9
|
+
"build:bundle": "ncc build src/index.ts -o dist --minify -e @testream/junit-reporter",
|
|
10
|
+
"build:types": "tsc --declaration --emitDeclarationOnly --outDir dist",
|
|
11
|
+
"format": "prettier --write '**/*.ts'",
|
|
12
|
+
"lint": "eslint src/**/*.ts",
|
|
13
|
+
"test": "echo \"No tests yet\" && exit 0",
|
|
14
|
+
"all": "npm run format && npm run lint && npm run build",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/testream/docs.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"pytest",
|
|
23
|
+
"junit",
|
|
24
|
+
"reporter",
|
|
25
|
+
"testing",
|
|
26
|
+
"test-management",
|
|
27
|
+
"test-results",
|
|
28
|
+
"jira",
|
|
29
|
+
"ctrf",
|
|
30
|
+
"testream"
|
|
31
|
+
],
|
|
32
|
+
"author": "Testream",
|
|
33
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
34
|
+
"bin": {
|
|
35
|
+
"testream-pytest": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@testream/junit-reporter": "*"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@jira-test-manager/shared-types": "*",
|
|
50
|
+
"@types/node": "^20.10.6",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
52
|
+
"@typescript-eslint/parser": "^6.17.0",
|
|
53
|
+
"@vercel/ncc": "^0.38.1",
|
|
54
|
+
"eslint": "^8.56.0",
|
|
55
|
+
"prettier": "^3.1.1",
|
|
56
|
+
"typescript": "^5.3.3"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|