Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

ctrf

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ctrf - npm Package Compare versions

Comparing version
0.0.13-next.0
to
0.0.13-next.1
+2
-1
dist/index.d.ts

@@ -5,2 +5,3 @@ export { mergeReports } from './methods/merge-reports.js';

export { enrichReportWithInsights, } from './methods/run-insights.js';
export type { Report, Results, Summary, Test, Environment, Tool, Step, Attachment, RetryAttempts, Insights, TestInsights, InsightsMetric, TestState } from '../types/ctrf.js';
export { storePreviousResults } from './methods/store-previous-results.js';
export type { Report, Results, Summary, Test, Environment, Tool, Step, Attachment, RetryAttempt, Insights, TestInsights, InsightsMetric, TestState } from '../types/ctrf.js';

@@ -5,1 +5,2 @@ export { mergeReports } from './methods/merge-reports.js';

export { enrichReportWithInsights, } from './methods/run-insights.js';
export { storePreviousResults } from './methods/store-previous-results.js';

@@ -13,3 +13,3 @@ import { Report } from '../../types/ctrf.js';

*
* @param directory Path to the directory containing JSON files.
* @param directoryPath Path to the directory containing JSON files.
* @returns An array of parsed `CtrfReport` objects.

@@ -16,0 +16,0 @@ * @throws If the directory does not exist or no valid CTRF reports are found.

@@ -35,3 +35,3 @@ import fs from 'fs';

*
* @param directory Path to the directory containing JSON files.
* @param directoryPath Path to the directory containing JSON files.
* @returns An array of parsed `CtrfReport` objects.

@@ -38,0 +38,0 @@ * @throws If the directory does not exist or no valid CTRF reports are found.

@@ -332,7 +332,2 @@ // ========================================

},
skippedRate: {
current: calculateSkippedRateFromMetrics(runMetrics),
previous: 0,
change: 0
},
averageTestDuration: {

@@ -348,3 +343,3 @@ current: calculateAverageTestDurationFromMetrics(runMetrics),

},
reportsAnalyzed: allReportsUpToThisPoint.length,
runsAnalyzed: allReportsUpToThisPoint.length,
extra: relevantMetrics

@@ -404,6 +399,5 @@ };

failRate: calculateTestFailRate(testName, testMetrics),
skippedRate: calculateTestSkippedRate(testName, testMetrics),
averageTestDuration: calculateTestAverageDuration(testName, testMetrics),
p95Duration: calculateTestP95Duration(testName, testMetrics),
appearsInRuns: testMetrics.appearsInRuns,
p95TestDuration: calculateTestP95Duration(testName, testMetrics),
executedInRuns: testMetrics.appearsInRuns,
extra: relevantMetrics

@@ -494,7 +488,2 @@ };

},
skippedRate: {
current: currentSkippedRate,
previous: baselineSkippedRate,
change: Number((currentSkippedRate - baselineSkippedRate).toFixed(4))
},
averageTestDuration: {

@@ -505,3 +494,3 @@ current: currentAverageDuration,

},
p95Duration: {
p95TestDuration: {
current: currentP95Duration,

@@ -511,3 +500,3 @@ previous: baselineP95Duration,

},
appearsInRuns: currentTestMetrics.appearsInRuns,
executedInRuns: currentTestMetrics.appearsInRuns,
extra: relevantMetrics

@@ -576,27 +565,22 @@ };

flakyRate: {
current: currentInsights.flakyRate.current,
previous: previousInsights.flakyRate.current,
change: Number((currentInsights.flakyRate.current - previousInsights.flakyRate.current).toFixed(4))
current: currentInsights?.flakyRate?.current ?? 0,
previous: previousInsights?.flakyRate?.current ?? 0,
change: Number(((currentInsights?.flakyRate?.current ?? 0) - (previousInsights?.flakyRate?.current ?? 0)).toFixed(4))
},
failRate: {
current: currentInsights.failRate.current,
previous: previousInsights.failRate.current,
change: Number((currentInsights.failRate.current - previousInsights.failRate.current).toFixed(4))
current: currentInsights?.failRate?.current ?? 0,
previous: previousInsights?.failRate?.current ?? 0,
change: Number(((currentInsights?.failRate?.current ?? 0) - (previousInsights?.failRate?.current ?? 0)).toFixed(4))
},
skippedRate: {
current: currentInsights.skippedRate.current,
previous: previousInsights.skippedRate.current,
change: Number((currentInsights.skippedRate.current - previousInsights.skippedRate.current).toFixed(4))
},
averageTestDuration: {
current: currentInsights.averageTestDuration.current,
previous: previousInsights.averageTestDuration.current,
change: Number((currentInsights.averageTestDuration.current - previousInsights.averageTestDuration.current).toFixed(2))
current: currentInsights?.averageTestDuration?.current ?? 0,
previous: previousInsights?.averageTestDuration?.current ?? 0,
change: Number(((currentInsights?.averageTestDuration?.current ?? 0) - (previousInsights?.averageTestDuration?.current ?? 0)).toFixed(2))
},
averageRunDuration: {
current: currentInsights.averageRunDuration.current,
previous: previousInsights.averageRunDuration.current,
change: Number((currentInsights.averageRunDuration.current - previousInsights.averageRunDuration.current).toFixed(2))
current: currentInsights?.averageRunDuration?.current ?? 0,
previous: previousInsights?.averageRunDuration?.current ?? 0,
change: Number(((currentInsights?.averageRunDuration?.current ?? 0) - (previousInsights?.averageRunDuration?.current ?? 0)).toFixed(2))
},
reportsAnalyzed: currentInsights.reportsAnalyzed,
runsAnalyzed: currentInsights?.runsAnalyzed ?? 0,
extra: currentInsights.extra

@@ -603,0 +587,0 @@ };

@@ -0,1 +1,2 @@

// This should be added to library, as not standard CTRF thing
/**

@@ -13,7 +14,5 @@ * Stores previous results in the current report's previousResults array.

}
// Initialize previousResults if it doesn't exist
if (!currentReport.extra) {
currentReport.extra = {};
}
// Extract metrics from each previous report
const previousResults = previousReports.map((report) => {

@@ -25,7 +24,4 @@ if (!report.results || !report.results.summary) {

const tests = report.results.tests || [];
// Calculate flaky tests count
const flakyCount = tests.filter(test => test.flaky === true).length;
// Calculate duration (stop - start)
const duration = summary.stop - summary.start;
// Determine overall result status
let result = 'passed';

@@ -32,0 +28,0 @@ if (summary.failed > 0) {

{
"name": "ctrf",
"version": "0.0.13-next.0",
"description": "",
"version": "0.0.13-next.1",
"description": "Common library for working with CTRF reports",
"type": "module",

@@ -16,2 +16,4 @@ "main": "dist/index.js",

"build": "tsc",
"lint": "biome check .",
"lint:fix": "biome check . --write src/helpers/string.ts",
"test": "jest",

@@ -21,3 +23,5 @@ "test:watch": "jest --watch",

"test:e2e": "jest --testPathPattern=e2e",
"enrich-reports": "tsx scripts/enrich-reports.ts"
"enrich-reports": "tsx scripts/enrich-reports.ts",
"docs": "typedoc",
"docs:watch": "typedoc --watch"
},

@@ -42,2 +46,3 @@ "files": [

"devDependencies": {
"@biomejs/biome": "2.1.1",
"@types/jest": "^30.0.0",

@@ -51,4 +56,5 @@ "@types/node": "^20.12.7",

"tsx": "^4.19.2",
"typedoc": "^0.28.8",
"typescript": "^5.8.3"
}
}
+145
-103

@@ -1,4 +0,4 @@

# CTRF CLI
# CTRF Common JavaScript Library
Various CTRF utilities available programatically and by command line
Common JavaScript library for working with CTRF reports, including type definitions and utility functions.

@@ -12,4 +12,4 @@ <div align="center">

<div style="margin-top: 1.5rem;">
<a href="https://github.com/ctrf-io/ctrf-cli">
<img src="https://img.shields.io/github/stars/ctrf-io/ctrf-cli?style=for-the-badge&color=2ea043" alt="GitHub stars">
<a href="https://github.com/ctrf-io/ctrf-core-js">
<img src="https://img.shields.io/github/stars/ctrf-io/ctrf-core-js?style=for-the-badge&color=2ea043" alt="GitHub stars">
</a>

@@ -23,3 +23,2 @@ <a href="https://github.com/ctrf-io">

<p style="font-size: 14px; margin: 1rem 0;">
Maintained by <a href="https://github.com/ma11hewthomas">Matthew Thomas</a><br/>
Contributions are very welcome! <br/>

@@ -30,144 +29,187 @@ Explore more <a href="https://www.ctrf.io/integrations">integrations</a>

## Command Line Utilities
## Installation
| Name |Details |
| ------------ | ----------------------------------------------------------------------------------- |
| `merge` | Merge multiple CTRF reports into a single report. |
| `flaky` | Output flaky test name and retries. |
```sh
npm install ctrf@0.0.13-next.1
```
## Merge
## TypeScript Types
This might be useful if you need a single report, but your chosen reporter generates multiple reports through design, parallelisation or otherwise.
The library exports comprehensive TypeScript types for working with CTRF reports:
To merge CTRF reports in a specified directory, use the following command:
```typescript
import type { Report, Test, Insights } from 'ctrf';
```sh
npx ctrf merge <directory>
function analyzeReport(report: Report): void {
const flakyTests = report.results.tests.filter((test: Test) => test.flaky);
const insights = report.insights as Insights;
console.log(`Flaky rate: ${insights?.flakyRate.current}`);
}
```
Replace `directory` with the path to the directory containing the CTRF reports you want to merge.
## API Reference
### Options
### Reading Reports
-o, --output `filename`: Output file name for the merged report. Default is ctrf-report.json.
#### `readSingleReport(filePath: string): Report`
```sh
npx ctrf merge <directory> --output my-merged-report.json
Reads and parses a single CTRF report file from a specified file path.
**Parameters:**
- `filePath` - Path to the JSON file containing the CTRF report
**Returns:** The parsed `Report` object
**Throws:** Error if the file does not exist, is not valid JSON, or does not conform to the CTRF report structure
**Example:**
```typescript
import { readSingleReport } from 'ctrf';
const report = readSingleReport('./test-results.json');
console.log(`Found ${report.results.summary.tests} tests`);
```
-d, --output-dir `directory`: Output directory for the merged report. Default is the same directory as the input reports.
#### `readReportsFromDirectory(directoryPath: string): Report[]`
```sh
npx ctrf merge <directory> --output-dir /path/to/output
Reads all CTRF report files from a given directory.
**Parameters:**
- `directoryPath` - Path to the directory containing JSON files
**Returns:** Array of parsed `Report` objects
**Throws:** Error if the directory does not exist or no valid CTRF reports are found
**Example:**
```typescript
import { readReportsFromDirectory } from 'ctrf';
const reports = readReportsFromDirectory('./test-reports/');
console.log(`Loaded ${reports.length} reports`);
```
-k, --keep-reports: Keep existing reports after merging. By default, the original reports will be deleted after merging.
#### `readReportsFromGlobPattern(pattern: string): Report[]`
```sh
npx ctrf merge <directory> --keep-reports
Reads all CTRF report files matching a glob pattern.
**Parameters:**
- `pattern` - The glob pattern to match files (e.g., `ctrf/*.json`)
**Returns:** Array of parsed `Report` objects
**Throws:** Error if no valid CTRF reports are found
**Example:**
```typescript
import { readReportsFromGlobPattern } from 'ctrf';
const reports = readReportsFromGlobPattern('reports/**/*.json');
console.log(`Found ${reports.length} reports matching pattern`);
```
## Flaky
### Merging Reports
The flaky command is useful for identifying tests marked as flaky in your CTRF report. Flaky tests are tests that pass or fail inconsistently and may require special attention or retries to determine their reliability.
#### `mergeReports(reports: Report[]): Report`
Usage
To output flaky tests, use the following command:
Merges multiple CTRF reports into a single report. Combines test results, summaries, and metadata from all input reports.
```sh
npx ctrf flaky <file-path>
**Parameters:**
- `reports` - Array of CTRF report objects to be merged
**Returns:** The merged CTRF report object
**Throws:** Error if no reports are provided for merging
**Example:**
```typescript
import { mergeReports, readReportsFromDirectory } from 'ctrf';
const reports = readReportsFromDirectory('./test-reports/');
const mergedReport = mergeReports(reports);
console.log(`Merged ${reports.length} reports into one`);
```
Replace <file-path> with the path to the CTRF report file you want to analyze.
### Report Enrichment & Insights
### Output
#### `enrichReportWithInsights(currentReport: Report, previousReports?: Report[], baseline?: number | string): Report`
The command will output the names of the flaky tests and the number of retries each test has undergone. For example:
Enriches a CTRF report with comprehensive insights by analyzing current and historical test data. Calculates metrics like flaky rates, failure rates, and performance trends.
```zsh
Processing report: reports/sample-report.json
Found 1 flaky test(s) in reports/sample-report.json:
- Test Name: Test 1, Retries: 2
**Parameters:**
- `currentReport` - The current CTRF report to enrich
- `previousReports` - Array of historical CTRF reports (ordered newest to oldest, optional)
- `baseline` - Optional baseline specification:
- `undefined`: Use most recent previous report (default)
- `number`: Use report at this index in previousReports array (0 = most recent)
- `string`: Use report with specific timestamp ID
**Returns:** The current report enriched with insights
**Example:**
```typescript
import { enrichReportWithInsights, readSingleReport } from 'ctrf';
const currentReport = readSingleReport('./current-report.json');
const previousReports = readReportsFromDirectory('./historical-reports/');
const enrichedReport = enrichReportWithInsights(currentReport, previousReports);
console.log(`Flaky rate: ${enrichedReport.insights?.flakyRate.current}`);
```
## Programmatic Methods
### Storing Previous Results
```sh
npm install ctrf
#### `storePreviousResults(currentReport: Report, previousReports: Report[]): Report`
Stores previous test run results in the current report's metadata. Extracts key metrics from historical reports for trend analysis.
**Parameters:**
- `currentReport` - The current CTRF report to enrich with previous results
- `previousReports` - Array of previous CTRF reports to extract metrics from
**Returns:** The current report with previousResults populated in the `extra` field
**Example:**
```typescript
import { storePreviousResults, readSingleReport } from 'ctrf';
const currentReport = readSingleReport('./current-report.json');
const previousReports = readReportsFromDirectory('./historical-reports/');
const reportWithHistory = storePreviousResults(currentReport, previousReports);
console.log(`Stored ${reportWithHistory.extra?.previousResults?.length} previous results`);
```
The following programmatic methods are available:
### Utility Functions
`mergeReports` - This method merges multiple CTRF reports into a single report.
#### `isTestFlaky(test: Test): boolean`
`readSingleReport` - Reads and parses a single CTRF report file from a specified file path.
Determines if a test is flaky based on its retries and status.
`readReportsFromDirectory` - Reads all CTRF report files from a given directory.
**Parameters:**
- `test` - The CTRF test to evaluate
`readReportsFromGlobPattern` - Reads all CTRF report files from a given glob pattern.
**Returns:** `true` if the test is considered flaky, otherwise `false`
## TypeScript Support
#### `formatAsPercentage(ratio: number, decimals?: number): string`
This library exports TypeScript types for all CTRF report structures. You can import them in your TypeScript projects:
Formats a ratio (0-1) as a percentage string for display.
```typescript
import {
Report,
Results,
Summary,
Test,
Environment,
Tool,
Insights,
TestInsights,
InsightsMetric,
TestState
} from 'ctrf';
**Parameters:**
- `ratio` - The ratio to format (0-1)
- `decimals` - Number of decimal places (default: 2)
// Example: Type your CTRF report data
const myReport: Report = {
reportFormat: 'ctrf',
specVersion: '0.0.0',
results: {
tool: { name: 'jest' },
summary: {
tests: 10,
passed: 8,
failed: 2,
skipped: 0,
pending: 0,
other: 0,
start: Date.now(),
stop: Date.now() + 5000
},
tests: []
}
};
**Returns:** Formatted percentage string (e.g., "25.50%")
// Example: Type your test data
const myTest: Test = {
name: 'should validate user input',
status: 'passed',
duration: 150
};
#### `formatInsightsMetricAsPercentage(metric: InsightsMetric, decimals?: number): object`
// Example: Use with library functions
import { enrichReportWithInsights, mergeReports } from 'ctrf';
Formats an InsightsMetric as percentage strings for display.
const enrichedReport: Report = enrichReportWithInsights(myReport, []);
```
**Parameters:**
- `metric` - The insights metric to format
- `decimals` - Number of decimal places (default: 2)
### Available Types
**Returns:** Object with formatted percentage strings for current, previous, and change values
- **`Report`** - Main CTRF report structure
- **`Results`** - Test results container
- **`Summary`** - Test execution summary
- **`Test`** - Individual test case
- **`Environment`** - Test environment details
- **`Tool`** - Testing tool information
- **`Insights`** - Report-level insights and metrics
- **`TestInsights`** - Test-level insights and metrics
- **`InsightsMetric`** - Metric with current/previous/change values
- **`TestState`** - Union type for test statuses: `'passed' | 'failed' | 'skipped' | 'pending' | 'other'`
## What is CTRF?

@@ -174,0 +216,0 @@

@@ -5,7 +5,8 @@ export interface Report {

reportId?: string
timestamp?: number
timestamp?: string
generatedBy?: string
results: Results
insights?: Insights
extra?: Record<string, any>
baseline?: Baseline
extra?: Record<string, unknown>
}

@@ -16,5 +17,6 @@

summary: Summary
metrics?: Metrics
tests: Test[]
environment?: Environment
extra?: Record<string, any>
extra?: Record<string, unknown>
}

@@ -29,8 +31,18 @@

other: number
flaky?: number
suites?: number
start: number
stop: number
extra?: Record<string, any>
extra?: Record<string, unknown>
}
export interface Metrics {
passRate?: number
failRate?: number
flakyRate?: number
averageTestDuration?: number
p95TestDuration?: number
extra?: Record<string, unknown>
}
export interface Test {

@@ -54,2 +66,3 @@ id?: string

retries?: number
retryAttempts?: RetryAttempt[]
flaky?: boolean

@@ -60,10 +73,9 @@ stdout?: string[]

attachments?: Attachment[]
retryAttempts?: RetryAttempts[]
browser?: string
device?: string
screenshot?: string
parameters?: Record<string, any>
parameters?: Record<string, unknown>
steps?: Step[]
insights?: TestInsights
extra?: Record<string, any>
extra?: Record<string, unknown>
}

@@ -75,5 +87,2 @@

appVersion?: string
osPlatform?: string
osRelease?: string
osVersion?: string
buildId?: string

@@ -87,4 +96,7 @@ buildName?: string

branchName?: string
osPlatform?: string
osRelease?: string
osVersion?: string
testEnvironment?: string
extra?: Record<string, any>
extra?: Record<string, unknown>
}

@@ -95,3 +107,3 @@

version?: string
extra?: Record<string, any>
extra?: Record<string, unknown>
}

@@ -102,3 +114,3 @@

status: TestState
extra?: Record<string, any>
extra?: Record<string, unknown>
}

@@ -110,28 +122,29 @@

path: string
extra?: Record<string, unknown>
}
export interface RetryAttempts {
retry: number;
status: TestState;
rawStatus?: string;
duration?: number;
message?: string;
trace?: string;
snippet?: string;
line?: number;
stdout?: string[];
stderr?: string[];
start?: number;
stop?: number;
attachments?: Attachment[];
extra?: Record<string, any>;
export interface RetryAttempt {
attempt: number
status: TestState
duration?: number
message?: string
trace?: string
line?: number
snippet?: string
stdout?: string[]
stderr?: string[]
start?: number
stop?: number
attachments?: Attachment[]
extra?: Record<string, unknown>
}
export interface Insights {
flakyRate: InsightsMetric
failRate: InsightsMetric
skippedRate: InsightsMetric
averageTestDuration: InsightsMetric
averageRunDuration: InsightsMetric
reportsAnalyzed: number
runsAnalyzed?: number
passRate?: InsightsMetric
failRate?: InsightsMetric
flakyRate?: InsightsMetric
averageRunDuration?: InsightsMetric
p95RunDuration?: InsightsMetric
averageTestDuration?: InsightsMetric
extra?: Record<string, unknown>

@@ -141,8 +154,8 @@ }

export interface TestInsights {
flakyRate: InsightsMetric
failRate: InsightsMetric
skippedRate: InsightsMetric
averageTestDuration: InsightsMetric
p95Duration: InsightsMetric
appearsInRuns: number
passRate?: InsightsMetric
failRate?: InsightsMetric
flakyRate?: InsightsMetric
averageTestDuration?: InsightsMetric
p95TestDuration?: InsightsMetric
executedInRuns?: number
extra?: Record<string, unknown>

@@ -157,2 +170,9 @@ }

export interface Baseline {
reportId: string
source?: string
timestamp?: string
extra?: Record<string, unknown>
}
export type TestState =

@@ -159,0 +179,0 @@ | 'passed'