@testream/jest-reporter 0.1.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 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,157 @@
1
+ # @testream/jest-reporter
2
+
3
+ Jest reporter that generates CTRF test reports and uploads them to Testream.
4
+
5
+ - **Docs:** https://testream.github.io/docs/reporters/jest
6
+ - **Docs repository:** https://github.com/testream/docs
7
+
8
+ ## Quick Start
9
+
10
+ Install the reporter:
11
+
12
+ ```bash
13
+ npm install --save-dev @testream/jest-reporter
14
+ ```
15
+
16
+ Configure in your `jest.config.js`:
17
+
18
+ ```javascript
19
+ module.exports = {
20
+ reporters: [
21
+ 'default',
22
+ [
23
+ '@testream/jest-reporter',
24
+ {
25
+ apiKey: process.env.TESTREAM_API_KEY,
26
+ projectKey: 'PROJ',
27
+ uploadEnabled: true,
28
+ },
29
+ ],
30
+ ],
31
+ };
32
+ ```
33
+
34
+ Run your tests:
35
+
36
+ ```bash
37
+ npm test
38
+ ```
39
+
40
+ The reporter will automatically:
41
+ 1. Generate a CTRF report in `ctrf/ctrf-report.json`
42
+ 2. Upload test results to Testream
43
+ 3. Display results in Jira
44
+
45
+ ## Configuration Options
46
+
47
+ ```typescript
48
+ {
49
+ // Required
50
+ apiKey: string; // Testream API key
51
+ projectKey: string; // Jira project key
52
+
53
+ // Optional - Git context (auto-detected in CI)
54
+ branch?: string; // Git branch name
55
+ commitSha?: string; // Git commit SHA
56
+ repositoryUrl?: string; // Git repository URL
57
+
58
+ // Optional - Build metadata (auto-detected in CI)
59
+ buildName?: string; // Build name/identifier
60
+ buildNumber?: string; // Build number
61
+ buildUrl?: string; // CI pipeline URL
62
+
63
+ // Optional - Environment metadata
64
+ testEnvironment?: string; // e.g., 'staging', 'production', 'ci'
65
+ appName?: string; // Application name under test
66
+ appVersion?: string; // Application version
67
+ testType?: string; // e.g., 'unit', 'integration', 'e2e'
68
+
69
+ // Optional - Upload options
70
+ uploadEnabled?: boolean; // Enable/disable upload (default: true)
71
+ failOnUploadError?: boolean; // Fail tests if upload fails (default: false)
72
+ }
73
+ ```
74
+
75
+ ## Examples
76
+
77
+ ### Basic Usage
78
+
79
+ ```javascript
80
+ // jest.config.js
81
+ module.exports = {
82
+ reporters: [
83
+ 'default',
84
+ [
85
+ '@testream/jest-reporter',
86
+ {
87
+ apiKey: process.env.TESTREAM_API_KEY,
88
+ projectKey: 'MYPROJ',
89
+ },
90
+ ],
91
+ ],
92
+ };
93
+ ```
94
+
95
+ ### With Environment Metadata
96
+
97
+ ```javascript
98
+ // jest.config.js
99
+ module.exports = {
100
+ reporters: [
101
+ 'default',
102
+ [
103
+ '@testream/jest-reporter',
104
+ {
105
+ apiKey: process.env.TESTREAM_API_KEY,
106
+ projectKey: 'MYPROJ',
107
+ testEnvironment: 'ci',
108
+ appName: 'MyApp',
109
+ appVersion: '1.0.0',
110
+ testType: 'unit',
111
+ },
112
+ ],
113
+ ],
114
+ };
115
+ ```
116
+
117
+ ### Generate CTRF Only (No Upload)
118
+
119
+ ```javascript
120
+ // jest.config.js
121
+ module.exports = {
122
+ reporters: [
123
+ 'default',
124
+ [
125
+ '@testream/jest-reporter',
126
+ {
127
+ apiKey: '', // Not needed when upload is disabled
128
+ projectKey: '',
129
+ uploadEnabled: false,
130
+ },
131
+ ],
132
+ ],
133
+ };
134
+ ```
135
+
136
+ ## How It Works
137
+
138
+ 1. The reporter uses `jest-ctrf-json-reporter` internally to generate CTRF reports
139
+ 2. Hooks into Jest's test lifecycle to collect results
140
+ 3. Converts Jest results to CTRF (Common Test Report Format)
141
+ 4. Writes CTRF report to `ctrf/ctrf-report.json`
142
+ 5. Optionally uploads results to Testream backend
143
+ 6. Test results appear in Jira automatically
144
+
145
+ ## CI/CD Integration
146
+
147
+ The reporter auto-detects CI context from:
148
+ - GitHub Actions
149
+ - GitLab CI
150
+ - Azure Pipelines
151
+ - CircleCI
152
+ - Jenkins
153
+ - Bitbucket Pipelines
154
+
155
+ You can also manually specify git/build context via configuration options.
156
+
157
+ For full documentation and examples, see https://testream.github.io/docs/reporters/jest.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @testream/jest-reporter
3
+ *
4
+ * Jest reporter that generates CTRF-format test reports and
5
+ * automatically uploads them to the Testream backend.
6
+ */
7
+ export { TestreamJestReporter as default } from './reporter';
8
+ export { TestreamJestReporter } from './reporter';
9
+ export type { JestReporterConfig } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * @testream/jest-reporter
4
+ *
5
+ * Jest reporter that generates CTRF-format test reports and
6
+ * automatically uploads them to the Testream backend.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.TestreamJestReporter = exports.default = void 0;
10
+ var reporter_1 = require("./reporter");
11
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return reporter_1.TestreamJestReporter; } });
12
+ var reporter_2 = require("./reporter");
13
+ Object.defineProperty(exports, "TestreamJestReporter", { enumerable: true, get: function () { return reporter_2.TestreamJestReporter; } });
@@ -0,0 +1,39 @@
1
+ import type { Reporter, Test, TestContext } from '@jest/reporters';
2
+ import type { AggregatedResult, TestResult } from '@jest/test-result';
3
+ import { JestReporterConfig } from './types';
4
+ /**
5
+ * Testream Jest Reporter
6
+ * Wraps jest-ctrf-json-reporter to generate CTRF reports and upload them to Testream
7
+ */
8
+ export declare class TestreamJestReporter implements Reporter {
9
+ private config;
10
+ private ctrfReporter;
11
+ private readonly outputDir;
12
+ private readonly outputFile;
13
+ constructor(globalConfig: unknown, options?: JestReporterConfig, context?: unknown);
14
+ /**
15
+ * Called when test run starts
16
+ */
17
+ onRunStart(_aggregatedResult: AggregatedResult, _options: unknown): void | Promise<void>;
18
+ /**
19
+ * Called when an individual test starts
20
+ */
21
+ onTestStart(_test: Test): void | Promise<void>;
22
+ /**
23
+ * Called when a test file completes
24
+ */
25
+ onTestResult(test: Test, testResult: TestResult, _aggregatedResult: AggregatedResult): void | Promise<void>;
26
+ /**
27
+ * Called when entire test run completes
28
+ */
29
+ onRunComplete(_testContexts: Set<TestContext>, _results: AggregatedResult): Promise<void>;
30
+ /**
31
+ * Read CTRF report from file
32
+ */
33
+ private readCTRFReport;
34
+ /**
35
+ * Upload report to Testream API
36
+ */
37
+ private uploadReport;
38
+ }
39
+ export default TestreamJestReporter;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.TestreamJestReporter = void 0;
40
+ const jest_ctrf_json_reporter_1 = __importDefault(require("jest-ctrf-json-reporter"));
41
+ const fs = __importStar(require("fs/promises"));
42
+ const path = __importStar(require("path"));
43
+ const uploader_1 = require("./uploader");
44
+ /**
45
+ * Testream Jest Reporter
46
+ * Wraps jest-ctrf-json-reporter to generate CTRF reports and upload them to Testream
47
+ */
48
+ class TestreamJestReporter {
49
+ constructor(globalConfig, options = {
50
+ apiKey: '',
51
+ projectKey: '',
52
+ uploadEnabled: true,
53
+ }, context = {}) {
54
+ this.outputDir = 'ctrf';
55
+ this.outputFile = 'ctrf-report.json';
56
+ this.config = {
57
+ uploadEnabled: true,
58
+ failOnUploadError: false,
59
+ ...options,
60
+ };
61
+ // Initialize jest-ctrf-json-reporter with fixed output path and metadata
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ const ctrfConfig = {
64
+ outputDir: this.outputDir,
65
+ outputFile: this.outputFile,
66
+ };
67
+ // Add environment metadata (same pattern as playwright-reporter)
68
+ if (this.config.testEnvironment)
69
+ ctrfConfig.testEnvironment = this.config.testEnvironment;
70
+ if (this.config.appName)
71
+ ctrfConfig.appName = this.config.appName;
72
+ if (this.config.appVersion)
73
+ ctrfConfig.appVersion = this.config.appVersion;
74
+ if (this.config.buildName)
75
+ ctrfConfig.buildName = this.config.buildName;
76
+ if (this.config.buildNumber)
77
+ ctrfConfig.buildNumber = this.config.buildNumber;
78
+ if (this.config.buildUrl)
79
+ ctrfConfig.buildUrl = this.config.buildUrl;
80
+ if (this.config.testType)
81
+ ctrfConfig.testType = this.config.testType;
82
+ if (this.config.branch)
83
+ ctrfConfig.branchName = this.config.branch;
84
+ if (this.config.commitSha)
85
+ ctrfConfig.commit = this.config.commitSha;
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ this.ctrfReporter = new jest_ctrf_json_reporter_1.default(globalConfig, ctrfConfig, context);
88
+ }
89
+ /**
90
+ * Called when test run starts
91
+ */
92
+ onRunStart(_aggregatedResult, _options) {
93
+ if (this.ctrfReporter.onRunStart) {
94
+ return this.ctrfReporter.onRunStart();
95
+ }
96
+ }
97
+ /**
98
+ * Called when an individual test starts
99
+ */
100
+ onTestStart(_test) {
101
+ if (this.ctrfReporter.onTestStart) {
102
+ return this.ctrfReporter.onTestStart();
103
+ }
104
+ }
105
+ /**
106
+ * Called when a test file completes
107
+ */
108
+ onTestResult(test, testResult, _aggregatedResult) {
109
+ if (this.ctrfReporter.onTestResult) {
110
+ return this.ctrfReporter.onTestResult(test, testResult);
111
+ }
112
+ }
113
+ /**
114
+ * Called when entire test run completes
115
+ */
116
+ async onRunComplete(_testContexts, _results) {
117
+ // Let jest-ctrf-json-reporter complete and write the CTRF file
118
+ if (this.ctrfReporter.onRunComplete) {
119
+ await this.ctrfReporter.onRunComplete();
120
+ }
121
+ // Read the generated CTRF report
122
+ const report = await this.readCTRFReport();
123
+ if (!report) {
124
+ uploader_1.defaultLogger.error('Failed to read CTRF report, skipping upload');
125
+ return;
126
+ }
127
+ uploader_1.defaultLogger.info(`CTRF report written to: ${path.join(this.outputDir, this.outputFile)}`);
128
+ // Upload to API if enabled
129
+ if (this.config.uploadEnabled && this.config.apiKey && this.config.projectKey) {
130
+ await this.uploadReport(report);
131
+ }
132
+ }
133
+ /**
134
+ * Read CTRF report from file
135
+ */
136
+ async readCTRFReport() {
137
+ const reportPath = path.join(this.outputDir, this.outputFile);
138
+ try {
139
+ const content = await fs.readFile(reportPath, 'utf-8');
140
+ return JSON.parse(content);
141
+ }
142
+ catch (error) {
143
+ uploader_1.defaultLogger.error(`Failed to read CTRF report from ${reportPath}: ${error instanceof Error ? error.message : String(error)}`);
144
+ return null;
145
+ }
146
+ }
147
+ /**
148
+ * Upload report to Testream API
149
+ */
150
+ async uploadReport(report) {
151
+ const ciContext = (0, uploader_1.detectCIContext)();
152
+ // Merge CLI options with auto-detected CI context (config takes precedence)
153
+ const branch = this.config.branch || ciContext.branch;
154
+ const commitSha = this.config.commitSha || ciContext.commitSha;
155
+ const repositoryUrl = this.config.repositoryUrl || ciContext.repositoryUrl;
156
+ const buildNumber = this.config.buildNumber || ciContext.buildNumber;
157
+ const buildUrl = this.config.buildUrl || ciContext.buildUrl;
158
+ const result = await (0, uploader_1.uploadToApi)({
159
+ report,
160
+ projectKey: this.config.projectKey,
161
+ apiKey: this.config.apiKey,
162
+ // Git context
163
+ branch,
164
+ commitSha,
165
+ repositoryUrl,
166
+ // Build metadata
167
+ buildName: this.config.buildName,
168
+ buildNumber,
169
+ buildUrl,
170
+ // Environment metadata
171
+ testEnvironment: this.config.testEnvironment,
172
+ appName: this.config.appName,
173
+ appVersion: this.config.appVersion,
174
+ testType: this.config.testType,
175
+ logger: uploader_1.defaultLogger,
176
+ });
177
+ if (!result.success && this.config.failOnUploadError) {
178
+ throw new Error(`Upload failed: ${result.error}`);
179
+ }
180
+ }
181
+ }
182
+ exports.TestreamJestReporter = TestreamJestReporter;
183
+ exports.default = TestreamJestReporter;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Re-export CTRF types from shared-types package
3
+ */
4
+ export type { CTRFReport, CTRFTest, CTRFSummary, CTRFResults, CTRFAttachment, CTRFStep, } from '@jira-test-manager/shared-types';
5
+ /**
6
+ * Re-export API types from shared-types package
7
+ */
8
+ export type { IngestResponse, ApiErrorResponse, UploadResult, } from '@jira-test-manager/shared-types';
9
+ /**
10
+ * Reporter Configuration Options
11
+ */
12
+ export interface JestReporterConfig {
13
+ /**
14
+ * API key for authentication
15
+ * @required
16
+ */
17
+ apiKey: string;
18
+ /**
19
+ * Project key to associate test results with
20
+ * @required
21
+ */
22
+ projectKey: string;
23
+ /**
24
+ * Git branch name (auto-detected from CI environment if not provided)
25
+ */
26
+ branch?: string;
27
+ /**
28
+ * Git commit SHA (auto-detected from CI environment if not provided)
29
+ */
30
+ commitSha?: string;
31
+ /**
32
+ * Git repository URL (auto-detected from CI environment if not provided)
33
+ */
34
+ repositoryUrl?: string;
35
+ /**
36
+ * Enable/disable automatic upload to backend
37
+ * @default true
38
+ */
39
+ uploadEnabled?: boolean;
40
+ /**
41
+ * Fail the process if upload fails
42
+ * @default false
43
+ */
44
+ failOnUploadError?: boolean;
45
+ /**
46
+ * Test type identifier (e.g., 'unit', 'integration', 'e2e')
47
+ */
48
+ testType?: string;
49
+ /**
50
+ * Application name under test
51
+ */
52
+ appName?: string;
53
+ /**
54
+ * Application version under test
55
+ */
56
+ appVersion?: string;
57
+ /**
58
+ * Build name/identifier
59
+ */
60
+ buildName?: string;
61
+ /**
62
+ * Build number
63
+ */
64
+ buildNumber?: string;
65
+ /**
66
+ * Build URL (CI pipeline URL)
67
+ */
68
+ buildUrl?: string;
69
+ /**
70
+ * Test environment (e.g., 'staging', 'production', 'ci')
71
+ */
72
+ testEnvironment?: string;
73
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,45 @@
1
+ import { CTRFReport, UploadResult } from './types';
2
+ /**
3
+ * Logger interface for output
4
+ */
5
+ export interface Logger {
6
+ info(message: string): void;
7
+ warning(message: string): void;
8
+ error(message: string): void;
9
+ }
10
+ /**
11
+ * Default console logger
12
+ */
13
+ export declare const defaultLogger: Logger;
14
+ export interface UploadToApiOptions {
15
+ report: CTRFReport;
16
+ projectKey: string;
17
+ apiKey: string;
18
+ commitSha?: string;
19
+ branch?: string;
20
+ repositoryUrl?: string;
21
+ buildName?: string;
22
+ buildNumber?: string;
23
+ buildUrl?: string;
24
+ testEnvironment?: string;
25
+ appName?: string;
26
+ appVersion?: string;
27
+ testType?: string;
28
+ logger?: Logger;
29
+ }
30
+ /**
31
+ * Upload CTRF report to Testream API
32
+ */
33
+ export declare function uploadToApi(options: UploadToApiOptions): Promise<UploadResult>;
34
+ /**
35
+ * Detect CI environment and extract context
36
+ */
37
+ export interface CIContext {
38
+ branch?: string;
39
+ commitSha?: string;
40
+ repositoryUrl?: string;
41
+ buildNumber?: string;
42
+ buildUrl?: string;
43
+ }
44
+ export declare function detectCIContext(): CIContext;
45
+ export declare const detectGitContext: typeof detectCIContext;
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectGitContext = exports.defaultLogger = void 0;
4
+ exports.uploadToApi = uploadToApi;
5
+ exports.detectCIContext = detectCIContext;
6
+ const crypto_1 = require("crypto");
7
+ /**
8
+ * Hardcoded API URL for Testream backend
9
+ */
10
+ const API_URL = 'https://test-manager-backend.fly.dev';
11
+ /**
12
+ * Default console logger
13
+ */
14
+ exports.defaultLogger = {
15
+ info: (msg) => console.log(msg),
16
+ warning: (msg) => console.warn(msg),
17
+ error: (msg) => console.error(msg),
18
+ };
19
+ /**
20
+ * Upload CTRF report to Testream API
21
+ */
22
+ async function uploadToApi(options) {
23
+ const { report, projectKey, apiKey, commitSha = '', branch = '', repositoryUrl = '', buildName, buildNumber, buildUrl, testEnvironment, appName, appVersion, testType, logger = exports.defaultLogger, } = options;
24
+ // Ensure reportId exists
25
+ if (!report.reportId) {
26
+ report.reportId = (0, crypto_1.randomUUID)();
27
+ }
28
+ try {
29
+ logger.info('='.repeat(60));
30
+ logger.info('Testream - Jest Test Results Upload');
31
+ logger.info('='.repeat(60));
32
+ logger.info(`Test Summary:`);
33
+ logger.info(` Total: ${report.results.summary.tests}`);
34
+ logger.info(` Passed: ${report.results.summary.passed}`);
35
+ logger.info(` Failed: ${report.results.summary.failed}`);
36
+ logger.info(` Skipped: ${report.results.summary.skipped}`);
37
+ logger.info('');
38
+ // Log git context if provided
39
+ if (branch || commitSha || repositoryUrl) {
40
+ logger.info(`Git Context:`);
41
+ if (branch)
42
+ logger.info(` Branch: ${branch}`);
43
+ if (commitSha)
44
+ logger.info(` Commit: ${commitSha.substring(0, 7)}`);
45
+ if (repositoryUrl)
46
+ logger.info(` Repository: ${repositoryUrl}`);
47
+ logger.info('');
48
+ }
49
+ // Log build info if provided
50
+ if (buildName || buildNumber || buildUrl) {
51
+ logger.info(`Build Info:`);
52
+ if (buildName)
53
+ logger.info(` Name: ${buildName}`);
54
+ if (buildNumber)
55
+ logger.info(` Number: ${buildNumber}`);
56
+ if (buildUrl)
57
+ logger.info(` URL: ${buildUrl}`);
58
+ logger.info('');
59
+ }
60
+ // Log environment info if provided
61
+ if (testEnvironment || appName || appVersion || testType) {
62
+ logger.info(`Environment:`);
63
+ if (testEnvironment)
64
+ logger.info(` Environment: ${testEnvironment}`);
65
+ if (appName)
66
+ logger.info(` App: ${appName}`);
67
+ if (appVersion)
68
+ logger.info(` Version: ${appVersion}`);
69
+ if (testType)
70
+ logger.info(` Test Type: ${testType}`);
71
+ logger.info('');
72
+ }
73
+ // Prepare payload with all metadata
74
+ const ingestPayload = {
75
+ report,
76
+ reportId: report.reportId,
77
+ projectKey,
78
+ // Git context
79
+ commitSha,
80
+ branch,
81
+ repositoryUrl,
82
+ // Build metadata
83
+ buildName,
84
+ buildNumber,
85
+ buildUrl,
86
+ // Environment metadata
87
+ testEnvironment,
88
+ appName,
89
+ appVersion,
90
+ testType,
91
+ };
92
+ // Upload to API
93
+ logger.info(`Uploading test results...`);
94
+ let response;
95
+ try {
96
+ response = await fetch(`${API_URL}/api/v1/ingest`, {
97
+ method: 'POST',
98
+ headers: {
99
+ 'X-API-KEY': apiKey,
100
+ 'Content-Type': 'application/json',
101
+ },
102
+ body: JSON.stringify(ingestPayload),
103
+ });
104
+ }
105
+ catch (error) {
106
+ const errorMessage = error instanceof Error ? error.message : String(error);
107
+ throw new Error(`Failed to connect to API: ${errorMessage}`);
108
+ }
109
+ // Handle errors
110
+ if (!response.ok) {
111
+ const responseText = await response.text();
112
+ let errorData = null;
113
+ try {
114
+ errorData = JSON.parse(responseText);
115
+ }
116
+ catch {
117
+ // Not JSON
118
+ }
119
+ // Handle 409 (already exists)
120
+ if (response.status === 409) {
121
+ logger.warning(`Report already exists: ${errorData?.reportId || 'unknown'}`);
122
+ logger.info('This is expected if the workflow was re-run');
123
+ return {
124
+ success: true,
125
+ reportId: errorData?.reportId || report.reportId,
126
+ summary: {
127
+ passed: report.results.summary.passed,
128
+ failed: report.results.summary.failed,
129
+ skipped: report.results.summary.skipped,
130
+ total: report.results.summary.tests,
131
+ },
132
+ };
133
+ }
134
+ // Other errors
135
+ const errorMessage = errorData?.error
136
+ ? `${errorData.error}${errorData.details ? `: ${errorData.details}` : ''}`
137
+ : responseText || `HTTP ${response.status}`;
138
+ throw new Error(`API ingest failed (${response.status}): ${errorMessage}`);
139
+ }
140
+ const result = (await response.json());
141
+ logger.info('');
142
+ logger.info('='.repeat(60));
143
+ logger.info('Upload completed successfully');
144
+ logger.info(` Report ID: ${result.reportId}`);
145
+ logger.info(` Test Run ID: ${result.testRunId}`);
146
+ logger.info('='.repeat(60));
147
+ return {
148
+ success: true,
149
+ reportId: result.reportId,
150
+ testRunId: result.testRunId,
151
+ summary: result.summary,
152
+ };
153
+ }
154
+ catch (error) {
155
+ const errorMessage = error instanceof Error ? error.message : String(error);
156
+ logger.error(`Upload failed: ${errorMessage}`);
157
+ return {
158
+ success: false,
159
+ reportId: '',
160
+ error: errorMessage,
161
+ };
162
+ }
163
+ }
164
+ function detectCIContext() {
165
+ const env = process.env;
166
+ // GitHub Actions
167
+ if (env.GITHUB_ACTIONS === 'true') {
168
+ return {
169
+ branch: env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME || env.GITHUB_REF?.replace('refs/heads/', ''),
170
+ commitSha: env.GITHUB_SHA,
171
+ repositoryUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY
172
+ ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`
173
+ : undefined,
174
+ buildNumber: env.GITHUB_RUN_NUMBER,
175
+ buildUrl: env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID
176
+ ? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`
177
+ : undefined,
178
+ };
179
+ }
180
+ // GitLab CI
181
+ if (env.GITLAB_CI === 'true') {
182
+ return {
183
+ branch: env.CI_COMMIT_BRANCH || env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME,
184
+ commitSha: env.CI_COMMIT_SHA,
185
+ repositoryUrl: env.CI_PROJECT_URL,
186
+ buildNumber: env.CI_PIPELINE_IID,
187
+ buildUrl: env.CI_PIPELINE_URL,
188
+ };
189
+ }
190
+ // Azure Pipelines
191
+ if (env.TF_BUILD === 'True') {
192
+ return {
193
+ branch: env.BUILD_SOURCEBRANCH?.replace('refs/heads/', ''),
194
+ commitSha: env.BUILD_SOURCEVERSION,
195
+ repositoryUrl: env.BUILD_REPOSITORY_URI,
196
+ buildNumber: env.BUILD_BUILDNUMBER,
197
+ buildUrl: env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI && env.SYSTEM_TEAMPROJECT && env.BUILD_BUILDID
198
+ ? `${env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}${env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${env.BUILD_BUILDID}`
199
+ : undefined,
200
+ };
201
+ }
202
+ // CircleCI
203
+ if (env.CIRCLECI === 'true') {
204
+ return {
205
+ branch: env.CIRCLE_BRANCH,
206
+ commitSha: env.CIRCLE_SHA1,
207
+ repositoryUrl: env.CIRCLE_REPOSITORY_URL,
208
+ buildNumber: env.CIRCLE_BUILD_NUM,
209
+ buildUrl: env.CIRCLE_BUILD_URL,
210
+ };
211
+ }
212
+ // Jenkins
213
+ if (env.JENKINS_URL) {
214
+ return {
215
+ branch: env.GIT_BRANCH?.replace('origin/', ''),
216
+ commitSha: env.GIT_COMMIT,
217
+ repositoryUrl: env.GIT_URL,
218
+ buildNumber: env.BUILD_NUMBER,
219
+ buildUrl: env.BUILD_URL,
220
+ };
221
+ }
222
+ // Bitbucket Pipelines
223
+ if (env.BITBUCKET_BUILD_NUMBER) {
224
+ return {
225
+ branch: env.BITBUCKET_BRANCH,
226
+ commitSha: env.BITBUCKET_COMMIT,
227
+ repositoryUrl: env.BITBUCKET_GIT_HTTP_ORIGIN,
228
+ buildNumber: env.BITBUCKET_BUILD_NUMBER,
229
+ buildUrl: env.BITBUCKET_REPO_FULL_NAME && env.BITBUCKET_BUILD_NUMBER
230
+ ? `https://bitbucket.org/${env.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${env.BITBUCKET_BUILD_NUMBER}`
231
+ : undefined,
232
+ };
233
+ }
234
+ return {};
235
+ }
236
+ // Keep old function name for backwards compatibility
237
+ exports.detectGitContext = detectCIContext;
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@testream/jest-reporter",
3
+ "version": "0.1.0",
4
+ "description": "Jest CTRF reporter with automatic upload to Testream",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "format": "prettier --write '**/*.ts'",
10
+ "lint": "eslint src/**/*.ts",
11
+ "prepublishOnly": "npm run build",
12
+ "test": "echo \"No tests yet\" && exit 0",
13
+ "all": "npm run format && npm run lint && npm run build"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/testream/docs.git"
18
+ },
19
+ "keywords": [
20
+ "jest",
21
+ "reporter",
22
+ "testing",
23
+ "test-management",
24
+ "ctrf",
25
+ "test-results",
26
+ "jira"
27
+ ],
28
+ "author": "Testream",
29
+ "license": "SEE LICENSE IN LICENSE",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@jira-test-manager/shared-types": "*",
35
+ "jest-ctrf-json-reporter": "^0.0.10"
36
+ },
37
+ "peerDependencies": {
38
+ "@jest/reporters": ">=27.0.0",
39
+ "@jest/test-result": ">=27.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@jest/reporters": "^29.0.0",
43
+ "@jest/test-result": "^29.0.0",
44
+ "@types/node": "^20.10.6",
45
+ "@typescript-eslint/eslint-plugin": "^6.17.0",
46
+ "@typescript-eslint/parser": "^6.17.0",
47
+ "eslint": "^8.56.0",
48
+ "prettier": "^3.1.1",
49
+ "typescript": "^5.3.3"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "files": [
55
+ "dist",
56
+ "README.md",
57
+ "LICENSE"
58
+ ]
59
+ }