@arpanp/zen-reporter 0.10.1

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.
@@ -0,0 +1,21 @@
1
+ import type { ResultSummary, TestCase } from './types';
2
+ /**
3
+ * Exact ordered field list for a single history row (single source of truth
4
+ * for the JSONL row shape that DuckDB's read_json consumes).
5
+ */
6
+ export declare const RUN_ROW_FIELDS: string[];
7
+ /** Lowercase, trim, every non-alphanumeric run -> single '-', strip edge '-'. */
8
+ export declare function slugify(name: string): string;
9
+ /**
10
+ * Deterministic run identifier: ISO start time (colons/dots -> '-') + slugified
11
+ * test run name, plus a shard suffix when running sharded.
12
+ */
13
+ export declare function buildRunId(startedAtIso: string, testRunName: string, shard: {
14
+ current: number;
15
+ total: number;
16
+ } | null): string;
17
+ /**
18
+ * One row per test case, each carrying the run-level fields plus the per-test
19
+ * fields, so DuckDB can group/join across runs on the uniform schema.
20
+ */
21
+ export declare function flattenRunRows(runId: string, testRunName: string, projectName: string, summary: ResultSummary, endedAt: string, testCases: TestCase[]): Record<string, unknown>[];
@@ -0,0 +1,19 @@
1
+ export declare const TAG_COLORS: {
2
+ bg: string;
3
+ text: string;
4
+ ring: string;
5
+ }[];
6
+ /**
7
+ * Get color for a tag. First time a tag is seen, assigns the next available color.
8
+ * Once assigned, a tag always returns the same color.
9
+ * Colors cycle after 10 unique tags.
10
+ */
11
+ export declare function getTagColor(tag: string): {
12
+ bg: string;
13
+ text: string;
14
+ ring: string;
15
+ };
16
+ /**
17
+ * Reset the global tag color assignment. Useful for testing or re-rendering.
18
+ */
19
+ export declare function resetTagColors(): void;
@@ -0,0 +1,192 @@
1
+ export interface TestError {
2
+ name: string;
3
+ message: string;
4
+ stack: string;
5
+ location: Location | null;
6
+ snippet: string;
7
+ cause: TestError | null;
8
+ }
9
+ export interface TestStep {
10
+ title: string;
11
+ subtitle?: string;
12
+ location?: Location | null;
13
+ snippet?: string;
14
+ params?: Record<string, unknown>;
15
+ duration: number;
16
+ status: 'passed' | 'failed' | 'skipped' | 'timedOut' | 'interrupted';
17
+ annotations: Annotation[];
18
+ attachments: Attachment[];
19
+ error: TestError | null;
20
+ subSteps?: TestStep[];
21
+ }
22
+ export interface TestCase {
23
+ title: string;
24
+ parent: string;
25
+ project: string;
26
+ fileName: string;
27
+ status: 'passed' | 'failed' | 'skipped' | 'timedOut' | 'interrupted';
28
+ duration: number;
29
+ steps?: TestStep[];
30
+ errors?: TestError[];
31
+ stdout?: string[];
32
+ stderr?: string[];
33
+ annotations?: Annotation[];
34
+ attachments?: Attachment[];
35
+ tags?: string[];
36
+ describePath?: string[];
37
+ attempts?: number;
38
+ failedAttempts?: FailedAttempt[];
39
+ }
40
+ export interface TestSuite {
41
+ title: string;
42
+ cases: TestCase[];
43
+ subSuites?: TestSuite[];
44
+ }
45
+ export interface ResultSummary {
46
+ total: number;
47
+ passed: number;
48
+ failed: number;
49
+ skipped: number;
50
+ timedOut: number;
51
+ interrupted?: number;
52
+ startTime: string;
53
+ endTime: string;
54
+ duration: number;
55
+ totalSequentialDuration?: number;
56
+ numberOfProjects: number;
57
+ workers?: number;
58
+ }
59
+ export interface TestRun {
60
+ summary: ResultSummary;
61
+ suites: TestSuite[];
62
+ projectName?: string;
63
+ testRunName?: string;
64
+ theme?: string;
65
+ darkMode?: boolean;
66
+ enableHistory?: 'auto' | boolean;
67
+ minimalReport?: boolean;
68
+ }
69
+ export interface ReportData {
70
+ testRun: TestRun;
71
+ }
72
+ export interface Annotation {
73
+ type: string;
74
+ description: string | null;
75
+ location: Location | null;
76
+ }
77
+ export interface Attachment {
78
+ name: string;
79
+ contentType: string;
80
+ path: string | null;
81
+ body: string | null;
82
+ }
83
+ export interface Location {
84
+ file: string;
85
+ line: number;
86
+ column: number;
87
+ }
88
+ export interface FailedAttempt {
89
+ status: 'failed' | 'timedOut' | 'interrupted';
90
+ duration: number;
91
+ error: TestError | null;
92
+ /** Full execution details of this attempt, so it can be rendered like the final attempt. */
93
+ steps?: TestStep[];
94
+ stdout?: string[];
95
+ stderr?: string[];
96
+ attachments?: Attachment[];
97
+ }
98
+ export interface ReporterConfig {
99
+ outputDir?: string;
100
+ packageManager?: string;
101
+ projectName?: string;
102
+ testRunName?: string;
103
+ singleSummaryFile?: boolean;
104
+ theme?: string;
105
+ darkMode?: boolean;
106
+ enableHistory?: 'auto' | boolean;
107
+ minimalReport?: boolean;
108
+ }
109
+ export interface HistoryRun {
110
+ run_id: string;
111
+ run_name: string;
112
+ started_at: string;
113
+ run_duration_ms: number;
114
+ run_sequential_duration_ms?: number | null;
115
+ run_workers?: number;
116
+ run_total: number;
117
+ run_passed: number;
118
+ run_failed: number;
119
+ run_skipped: number;
120
+ run_timed_out: number;
121
+ run_interrupted: number;
122
+ pass_rate: number | null;
123
+ project_durations?: Record<string, number>;
124
+ }
125
+ export interface HistoryFlakyRow {
126
+ suite: string;
127
+ file: string;
128
+ title: string;
129
+ project: string;
130
+ failed_runs: number;
131
+ passed_runs: number;
132
+ recovered_by_retry: number;
133
+ total_runs: number;
134
+ }
135
+ export interface HistoryRegressionRow {
136
+ regressed_in: string;
137
+ regressed_at: string;
138
+ last_status: string;
139
+ last_run_at: string;
140
+ suite: string;
141
+ file: string;
142
+ title: string;
143
+ project: string;
144
+ }
145
+ export interface HistorySlowRow {
146
+ suite: string;
147
+ file: string;
148
+ title: string;
149
+ project: string;
150
+ avg_duration_ms?: number;
151
+ max_duration_ms?: number;
152
+ last_duration_ms?: number;
153
+ avg_ms?: number;
154
+ max_ms?: number;
155
+ last_ms?: number;
156
+ runs: number;
157
+ }
158
+ export interface HistoryFileRow {
159
+ run_id?: string;
160
+ started_at: string;
161
+ file: string;
162
+ total: number;
163
+ passed: number;
164
+ failed: number;
165
+ timed_out: number;
166
+ interrupted: number;
167
+ skipped: number;
168
+ }
169
+ export interface HistoryTestRow {
170
+ run_id?: string;
171
+ started_at: string;
172
+ suite: string;
173
+ file: string;
174
+ title: string;
175
+ project: string;
176
+ total: number;
177
+ passed: number;
178
+ failed: number;
179
+ timed_out: number;
180
+ interrupted: number;
181
+ skipped: number;
182
+ avg_duration_ms?: number;
183
+ }
184
+ export interface HistoryData {
185
+ generated_at: string;
186
+ runs: HistoryRun[];
187
+ flaky: HistoryFlakyRow[];
188
+ regressions: HistoryRegressionRow[];
189
+ slowest: HistorySlowRow[];
190
+ files?: HistoryFileRow[];
191
+ tests?: HistoryTestRow[];
192
+ }
@@ -0,0 +1,126 @@
1
+ import type { TestStep as PwTestStep } from '@playwright/test/reporter';
2
+ import type { existsSync as fsExistsSync, readFileSync as fsReadFileSync } from 'fs';
3
+ import type { Attachment, Location, ResultSummary, TestCase, TestError, TestStep, TestSuite } from './types';
4
+ export interface FsModule {
5
+ existsSync: typeof fsExistsSync;
6
+ readFileSync: typeof fsReadFileSync;
7
+ }
8
+ export declare function setFsModule(fsInstance: FsModule | null): void;
9
+ export declare function getStepCodeSnippet(location: Location | null | undefined, fsModule?: FsModule | null): string | undefined;
10
+ export declare function highlightJsTokens(code: string): string;
11
+ export declare function highlightCodeLine(codeLine: string, status?: string): string;
12
+ export declare function highlightCodeSnippet(snippet: string, status?: string): string;
13
+ export declare function convertPlaywrightSteps(pwSteps: PwTestStep[], fsModule?: FsModule | null): TestStep[];
14
+ export declare function sanitizeAnsi(str: string): string;
15
+ export declare function parseAnsiToHtml(str: string): string;
16
+ export declare function highlightExpectedReceived(str: string): string;
17
+ export declare function escapeHTML(html: string): string;
18
+ export declare function buildHierarchyForFile(fileName: string, cases: TestCase[]): TestSuite;
19
+ export declare function buildSuitesFromCases(testCases: TestCase[]): TestSuite[];
20
+ export declare function formatDuration(ms: number): string;
21
+ export declare function formatDurationVerbose(ms?: number | null): string;
22
+ export declare function formatDate(iso: string): string;
23
+ /**
24
+ * Format a start and end ISO timestamp into a smart, non-repetitive date-time range string.
25
+ * e.g., "Sep 20, 2026 • 05:38:12 AM – 05:40:26 AM IST" (for same-day runs)
26
+ */
27
+ export declare function formatDateRange(startIso: string, endIso?: string): string;
28
+ /**
29
+ * Split an ISO timestamp into a human-readable date line ("Sep 18, 2026")
30
+ * and an AM/PM time line ("04:57 AM"). No time zone, for two-line chart labels.
31
+ */
32
+ export declare function formatDateParts(iso: string): [string, string];
33
+ export interface FailedTest {
34
+ title: string;
35
+ suiteTitle: string;
36
+ fileName: string;
37
+ duration: number;
38
+ type: string;
39
+ tags?: string[];
40
+ errors: TestError[];
41
+ testCase: TestCase;
42
+ }
43
+ export declare function extractFailedTests(suites: TestSuite[]): FailedTest[];
44
+ export declare function collectAllCases(suites: TestSuite[]): TestCase[];
45
+ export interface ProjectStats {
46
+ name: string;
47
+ total: number;
48
+ passed: number;
49
+ failed: number;
50
+ skipped: number;
51
+ timedOut: number;
52
+ interrupted: number;
53
+ passRate: number;
54
+ flakyCount: number;
55
+ flakyRate: number;
56
+ totalDuration: number;
57
+ avgDuration: number;
58
+ p95Duration: number;
59
+ medianDuration: number;
60
+ minDuration: number;
61
+ maxDuration: number;
62
+ uniqueFiles: number;
63
+ uniqueTagsCount: number;
64
+ speedMultiplier: number;
65
+ cases: TestCase[];
66
+ }
67
+ export interface ProjectExecutiveKPIs {
68
+ totalProjects: number;
69
+ mostStableProject: {
70
+ name: string;
71
+ passRate: number;
72
+ total: number;
73
+ } | null;
74
+ slowestProject: {
75
+ name: string;
76
+ totalDuration: number;
77
+ avgDuration: number;
78
+ } | null;
79
+ passRateParityDelta: number;
80
+ totalFlakyCount: number;
81
+ overallAvgDuration: number;
82
+ }
83
+ export declare function computeProjectStats(allCases: TestCase[]): ProjectStats[];
84
+ export declare function computeProjectExecutiveKPIs(projectStats: ProjectStats[]): ProjectExecutiveKPIs;
85
+ export declare function computePassRate(summary: ResultSummary): number;
86
+ export declare function computeSlowestTest(allCases: TestCase[]): number;
87
+ export declare function computeFastestTest(allCases: TestCase[]): number;
88
+ export declare function getTotalTags(allCases: TestCase[]): number;
89
+ export interface FileStats {
90
+ fileName: string;
91
+ total: number;
92
+ passed: number;
93
+ failed: number;
94
+ skipped: number;
95
+ timedOut: number;
96
+ interrupted: number;
97
+ totalDuration: number;
98
+ avgDuration: number;
99
+ flakyCount: number;
100
+ }
101
+ export declare function computeFileStats(allCases: TestCase[]): FileStats[];
102
+ export declare function truncateFileName(fileName: string, maxLength?: number): string;
103
+ export declare function truncateMiddlePath(filePath: string): string;
104
+ /**
105
+ * Normalizes error messages and stack traces by masking dynamic tokens (timestamps,
106
+ * pointers, line numbers, ports, durations) so identical failure causes yield the same signature.
107
+ */
108
+ export declare function normalizeErrorText(message: string, stack: string): string;
109
+ /**
110
+ * Synchronous SHA-256 hash generator for string inputs.
111
+ */
112
+ export declare function sha256Sync(str: string): string;
113
+ export interface ErrorSignatureDetails {
114
+ hash: string;
115
+ shortHash: string;
116
+ representativeMessage: string;
117
+ }
118
+ export declare function getErrorSignature(test: FailedTest): ErrorSignatureDetails;
119
+ export interface VisualDiffPair {
120
+ actualUrl: string;
121
+ expectedUrl: string;
122
+ diffUrl?: string;
123
+ name?: string;
124
+ }
125
+ export declare function extractVisualDiffPairs(attachments: Attachment[], getAttachmentUrl: (att: Attachment) => string | null): VisualDiffPair[];
126
+ export declare function generateTerminalSummaryTable(summary: ResultSummary, projectName?: string, testRunName?: string): string;
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@arpanp/zen-reporter",
3
+ "private": false,
4
+ "version": "0.10.1",
5
+ "type": "module",
6
+ "description": "Beautiful test execution reports for Playwright",
7
+ "bin": {
8
+ "zen-reporter": "bin/zen-reporter.js",
9
+ "zr": "bin/zen-reporter.js"
10
+ },
11
+ "main": "dist/reporter.cjs",
12
+ "module": "dist/reporter.js",
13
+ "types": "dist/reporter.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/reporter.d.ts",
17
+ "import": "./dist/reporter.js",
18
+ "require": "./dist/reporter.cjs"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "bin",
24
+ "dist"
25
+ ],
26
+ "author": {
27
+ "name": "Arpan Patelia",
28
+ "email": "arpan.is@gmail.com"
29
+ },
30
+ "license": "MIT",
31
+ "scripts": {
32
+ "dev": "vite",
33
+ "build": "vite build && vite build --config vite.reporter.config.ts && tsc -p tsconfig.lib.json",
34
+ "preview": "vite preview",
35
+ "test": "npx playwright test",
36
+ "report": "npm run build && node scripts/generate_report.js",
37
+ "show": "node bin/zen-reporter.js show",
38
+ "lint": "eslint .",
39
+ "lint:fix": "eslint . --fix",
40
+ "typecheck": "tsc --noEmit",
41
+ "format": "prettier --write .",
42
+ "format:check": "prettier --check .",
43
+ "prepare": "husky",
44
+ "prepack": "npm run build",
45
+ "prepublish": "npm run build"
46
+ },
47
+ "lint-staged": {
48
+ "*.{js,jsx,ts,tsx}": [
49
+ "eslint --fix",
50
+ "prettier --write"
51
+ ],
52
+ "*.{json,css,md,yml,yaml,html}": [
53
+ "prettier --write"
54
+ ]
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@playwright/test": ">=1.63.0",
61
+ "@duckdb/node-api": "1.5.5-r.5"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@playwright/test": {
65
+ "optional": true
66
+ },
67
+ "@duckdb/node-api": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "devDependencies": {
72
+ "@eslint-react/eslint-plugin": "^5.20.8",
73
+ "@duckdb/node-api": "1.5.5-r.5",
74
+ "@eslint/js": "^10.0.1",
75
+ "@eslint/json": "^2.1.0",
76
+ "@eslint/markdown": "^8.0.3",
77
+ "@playwright/cli": "^0.1.21",
78
+ "@playwright/test": "^1.63.0",
79
+ "@tailwindcss/postcss": "^4.3.3",
80
+ "@types/node": "^26.6.2",
81
+ "@types/react": "^19.3.0",
82
+ "@types/react-dom": "^19.3.0",
83
+ "@vitejs/plugin-react": "^6.1.1",
84
+ "autoprefixer": "^10.6.1",
85
+ "eslint": "^10.11.0",
86
+ "eslint-config-prettier": "^10.1.8",
87
+ "globals": "^17.12.0",
88
+ "husky": "^9.1.7",
89
+ "lint-staged": "^17.5.1",
90
+ "postcss": "^8.5.28",
91
+ "prettier": "^3.9.9",
92
+ "react": "^19.3.0",
93
+ "react-dom": "^19.3.0",
94
+ "react-is": "^19.3.0",
95
+ "recharts": "^3.10.1",
96
+ "tailwindcss": "^4.3.3",
97
+ "typescript": "^6.0.3",
98
+ "typescript-eslint": "^8.70.1",
99
+ "vite": "^8.3.1",
100
+ "vite-plugin-singlefile": "^2.3.3"
101
+ }
102
+ }