@testrelic/maestro-analytics 1.3.2 → 1.4.0-next.121
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/README.md +13 -0
- package/dist/cli.cjs +680 -23
- package/dist/index.cjs +643 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +643 -59
- package/dist/index.js.map +1 -1
- package/dist/merge.cjs +2 -2
- package/dist/merge.cjs.map +1 -1
- package/dist/merge.js +2 -2
- package/dist/merge.js.map +1 -1
- package/package.json +2 -2
package/dist/merge.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var fs=require('fs'),path=require('path');var A={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0};function
|
|
2
|
-
`);}let n=T(i),
|
|
1
|
+
'use strict';var fs=require('fs'),path=require('path');var A={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0};function b(t){return t===null?"error":t>=200&&t<300?"2xx":t>=300&&t<400?"3xx":t>=400&&t<500?"4xx":t>=500&&t<600?"5xx":"error"}function d(t,s){if(t.length===0)return null;let i=Math.min(t.length-1,Math.floor(s/100*t.length));return t[i]}function E(t){if(t.length===0)return {totalApiCalls:0,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{...A},apiResponseTime:null};let s={},i={...A},r=new Set,e=[];for(let n of t)s[n.method]=(s[n.method]??0)+1,i[b(n.responseStatusCode)]+=1,r.add(n.url),Number.isFinite(n.responseTimeMs)&&n.responseTimeMs>0&&e.push(n.responseTimeMs);e.sort((n,a)=>n-a);let l=e.length>0?{p50:d(e,50)??0,p95:d(e,95)??0,p99:d(e,99)??0,avg:e.reduce((n,a)=>n+a,0)/e.length,min:e[0],max:e[e.length-1]}:null;return {totalApiCalls:t.length,uniqueApiUrls:r.size,apiCallsByMethod:s,apiCallsByStatusRange:i,apiResponseTime:l}}function T(t){let s=0,i=0,r=0,e=0,l=0,n=0,a=0,c=0,m=0,f=t.length,o=0,u={},g=new Set,S=[];for(let R of t){g.add(R.url);for(let p of R.tests){switch(s++,p.status){case "passed":i++;break;case "failed":r++;break;case "flaky":e++;break;case "skipped":l++;break;case "timedout":n++;break}if(p.actions)for(let y of p.actions)if(o++,y.category==="assertion")a++,y.status==="passed"?c++:m++;else {let x=y.category;u[x]=(u[x]??0)+1;}p.apiCalls&&p.apiCalls.length>0&&S.push(...p.apiCalls);}}let C=E(S);return {total:s,passed:i,failed:r,flaky:e,skipped:l,timedout:n,...C,totalAssertions:a,passedAssertions:c,failedAssertions:m,totalNavigations:f,uniqueNavigationUrls:g.size,totalTimelineSteps:t.length,totalActionSteps:o,actionStepsByCategory:u}}var h="1.1.0";function N(t){return "tests"in t&&"visitedAt"in t}function U(t){let s=[],i=[],r="",e="",l="";for(let m of t)try{let f=fs.readFileSync(m,"utf-8"),o=JSON.parse(f);l||(l=o.testRunId),(!r||o.startedAt<r)&&(r=o.startedAt),(!e||o.completedAt&&o.completedAt>e)&&(e=o.completedAt);for(let u of o.timeline)s.push(u),N(u)&&i.push(u);}catch{process.stderr.write(`\u26A0 TestRelic: Unable to read report file: ${m}
|
|
2
|
+
`);}let n=T(i),a=r?new Date(r).getTime():Date.now(),c=e?new Date(e).getTime():Date.now();return {schemaVersion:h,testRunId:l||`maestro-merged-${Date.now()}`,startedAt:r||new Date().toISOString(),completedAt:e||new Date().toISOString(),totalDuration:c-a,summary:n,ci:null,metadata:null,timeline:s,shardRunIds:null}}function $(t,s){if(!fs.existsSync(t))throw new Error(`Directory does not exist: ${t}`);let i=fs.readdirSync(t).filter(e=>path.extname(e)===".json"&&e.includes("testrelic")).map(e=>path.join(t,e)),r=U(i);return fs.writeFileSync(s,JSON.stringify(r,null,2),"utf-8"),r}exports.mergeReports=U;exports.mergeReportsFromDirectory=$;//# sourceMappingURL=merge.cjs.map
|
|
3
3
|
//# sourceMappingURL=merge.cjs.map
|
package/dist/merge.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/summary-builder.ts","../src/merge.ts"],"names":["EMPTY_STATUS_RANGE","statusBucket","code","percentile","sorted","p","idx","buildApiStats","calls","byMethod","byStatus","urls","times","c","a","b","apiResponseTime","s","n","buildSummary","timeline","total","passed","failed","flaky","skipped","timedout","totalAssertions","passedAssertions","failedAssertions","totalNavigations","totalActionSteps","actionCategoryCounts","uniqueUrls","allApiCalls","entry","test","action","cat","apiStats","isTimelineEntry","item","mergeReports","reportPaths","allTimeline","timelineEntries","earliestStart","latestEnd","runId","reportPath","content","readFileSync","report","summary","startMs","endMs","mergeReportsFromDirectory","dirPath","outputPath","existsSync","readdirSync","f","extname","join","merged","writeFileSync"],"mappings":"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","file":"merge.cjs","sourcesContent":["/**\n * Computes Summary stats from TimelineEntry arrays.\n */\n\nimport type { Summary, TimelineEntry, ApiCallRecord } from '@testrelic/core';\n\ntype ApiCallsByStatusRange = Record<'2xx' | '3xx' | '4xx' | '5xx' | 'error', number>;\n\nconst EMPTY_STATUS_RANGE: ApiCallsByStatusRange = {\n '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0,\n};\n\nfunction statusBucket(code: number | null): keyof ApiCallsByStatusRange {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\nfunction percentile(sorted: number[], p: number): number | null {\n if (sorted.length === 0) return null;\n const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));\n return sorted[idx];\n}\n\nfunction buildApiStats(calls: ApiCallRecord[]): {\n totalApiCalls: number;\n uniqueApiUrls: number;\n apiCallsByMethod: Record<string, number>;\n apiCallsByStatusRange: ApiCallsByStatusRange;\n apiResponseTime: Summary['apiResponseTime'];\n} {\n if (calls.length === 0) {\n return {\n totalApiCalls: 0,\n uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { ...EMPTY_STATUS_RANGE },\n apiResponseTime: null,\n };\n }\n\n const byMethod: Record<string, number> = {};\n const byStatus: ApiCallsByStatusRange = { ...EMPTY_STATUS_RANGE };\n const urls = new Set<string>();\n const times: number[] = [];\n\n for (const c of calls) {\n byMethod[c.method] = (byMethod[c.method] ?? 0) + 1;\n byStatus[statusBucket(c.responseStatusCode)] += 1;\n urls.add(c.url);\n if (Number.isFinite(c.responseTimeMs) && c.responseTimeMs > 0) {\n times.push(c.responseTimeMs);\n }\n }\n\n times.sort((a, b) => a - b);\n const apiResponseTime: Summary['apiResponseTime'] = times.length > 0 ? {\n p50: percentile(times, 50) ?? 0,\n p95: percentile(times, 95) ?? 0,\n p99: percentile(times, 99) ?? 0,\n avg: times.reduce((s, n) => s + n, 0) / times.length,\n min: times[0],\n max: times[times.length - 1],\n } : null;\n\n return {\n totalApiCalls: calls.length,\n uniqueApiUrls: urls.size,\n apiCallsByMethod: byMethod,\n apiCallsByStatusRange: byStatus,\n apiResponseTime,\n };\n}\n\nexport function buildSummary(timeline: readonly TimelineEntry[]): Summary {\n let total = 0;\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n const totalNavigations = timeline.length;\n let totalActionSteps = 0;\n const actionCategoryCounts: Record<string, number> = {};\n const uniqueUrls = new Set<string>();\n const allApiCalls: ApiCallRecord[] = [];\n\n for (const entry of timeline) {\n uniqueUrls.add(entry.url);\n\n for (const test of entry.tests) {\n total++;\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n\n if (test.actions) {\n for (const action of test.actions) {\n totalActionSteps++;\n if (action.category === 'assertion') {\n totalAssertions++;\n if (action.status === 'passed') passedAssertions++;\n else failedAssertions++;\n } else {\n const cat = action.category;\n actionCategoryCounts[cat] = (actionCategoryCounts[cat] ?? 0) + 1;\n }\n }\n }\n\n if (test.apiCalls && test.apiCalls.length > 0) {\n allApiCalls.push(...test.apiCalls);\n }\n }\n }\n\n const apiStats = buildApiStats(allApiCalls);\n\n return {\n total,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n ...apiStats,\n totalAssertions,\n passedAssertions,\n failedAssertions,\n totalNavigations,\n uniqueNavigationUrls: uniqueUrls.size,\n totalTimelineSteps: timeline.length,\n totalActionSteps,\n actionStepsByCategory: actionCategoryCounts,\n };\n}\n","/**\n * Merge multiple Maestro report JSON files into a single report.\n */\n\nimport { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestRunReport, TimelineEntry, TimelineStep } from '@testrelic/core';\nimport { buildSummary } from './summary-builder.js';\n\nfunction isTimelineEntry(item: TimelineEntry | TimelineStep): item is TimelineEntry {\n return 'tests' in item && 'visitedAt' in item;\n}\n\nexport function mergeReports(reportPaths: string[]): TestRunReport {\n const allTimeline: (TimelineEntry | TimelineStep)[] = [];\n const timelineEntries: TimelineEntry[] = [];\n let earliestStart = '';\n let latestEnd = '';\n let runId = '';\n\n for (const reportPath of reportPaths) {\n try {\n const content = readFileSync(reportPath, 'utf-8');\n const report = JSON.parse(content) as TestRunReport;\n\n if (!runId) runId = report.testRunId;\n if (!earliestStart || report.startedAt < earliestStart) earliestStart = report.startedAt;\n if (!latestEnd || (report.completedAt && report.completedAt > latestEnd)) latestEnd = report.completedAt;\n\n for (const item of report.timeline) {\n allTimeline.push(item);\n if (isTimelineEntry(item)) timelineEntries.push(item);\n }\n } catch {\n process.stderr.write(`\\u26A0 TestRelic: Unable to read report file: ${reportPath}\\n`);\n }\n }\n\n const summary = buildSummary(timelineEntries);\n const startMs = earliestStart ? new Date(earliestStart).getTime() : Date.now();\n const endMs = latestEnd ? new Date(latestEnd).getTime() : Date.now();\n\n return {\n schemaVersion: '1.0.0',\n testRunId: runId || `maestro-merged-${Date.now()}`,\n startedAt: earliestStart || new Date().toISOString(),\n completedAt: latestEnd || new Date().toISOString(),\n totalDuration: endMs - startMs,\n summary,\n ci: null,\n metadata: null,\n timeline: allTimeline,\n shardRunIds: null,\n };\n}\n\nexport function mergeReportsFromDirectory(dirPath: string, outputPath: string): TestRunReport {\n if (!existsSync(dirPath)) {\n throw new Error(`Directory does not exist: ${dirPath}`);\n }\n\n const reportPaths = readdirSync(dirPath)\n .filter((f) => extname(f) === '.json' && f.includes('testrelic'))\n .map((f) => join(dirPath, f));\n\n const merged = mergeReports(reportPaths);\n writeFileSync(outputPath, JSON.stringify(merged, null, 2), 'utf-8');\n return merged;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/summary-builder.ts","../src/schema.ts","../src/merge.ts"],"names":["EMPTY_STATUS_RANGE","statusBucket","code","percentile","sorted","p","idx","buildApiStats","calls","byMethod","byStatus","urls","times","c","a","b","apiResponseTime","s","n","buildSummary","timeline","total","passed","failed","flaky","skipped","timedout","totalAssertions","passedAssertions","failedAssertions","totalNavigations","totalActionSteps","actionCategoryCounts","uniqueUrls","allApiCalls","entry","test","action","cat","apiStats","SCHEMA_VERSION","isTimelineEntry","item","mergeReports","reportPaths","allTimeline","timelineEntries","earliestStart","latestEnd","runId","reportPath","content","readFileSync","report","summary","startMs","endMs","mergeReportsFromDirectory","dirPath","outputPath","existsSync","readdirSync","f","extname","join","merged","writeFileSync"],"mappings":"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","file":"merge.cjs","sourcesContent":["/**\n * Computes Summary stats from TimelineEntry arrays.\n */\n\nimport type { Summary, TimelineEntry, ApiCallRecord } from '@testrelic/core';\n\ntype ApiCallsByStatusRange = Record<'2xx' | '3xx' | '4xx' | '5xx' | 'error', number>;\n\nconst EMPTY_STATUS_RANGE: ApiCallsByStatusRange = {\n '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0,\n};\n\nfunction statusBucket(code: number | null): keyof ApiCallsByStatusRange {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\nfunction percentile(sorted: number[], p: number): number | null {\n if (sorted.length === 0) return null;\n const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));\n return sorted[idx];\n}\n\nfunction buildApiStats(calls: ApiCallRecord[]): {\n totalApiCalls: number;\n uniqueApiUrls: number;\n apiCallsByMethod: Record<string, number>;\n apiCallsByStatusRange: ApiCallsByStatusRange;\n apiResponseTime: Summary['apiResponseTime'];\n} {\n if (calls.length === 0) {\n return {\n totalApiCalls: 0,\n uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { ...EMPTY_STATUS_RANGE },\n apiResponseTime: null,\n };\n }\n\n const byMethod: Record<string, number> = {};\n const byStatus: ApiCallsByStatusRange = { ...EMPTY_STATUS_RANGE };\n const urls = new Set<string>();\n const times: number[] = [];\n\n for (const c of calls) {\n byMethod[c.method] = (byMethod[c.method] ?? 0) + 1;\n byStatus[statusBucket(c.responseStatusCode)] += 1;\n urls.add(c.url);\n if (Number.isFinite(c.responseTimeMs) && c.responseTimeMs > 0) {\n times.push(c.responseTimeMs);\n }\n }\n\n times.sort((a, b) => a - b);\n const apiResponseTime: Summary['apiResponseTime'] = times.length > 0 ? {\n p50: percentile(times, 50) ?? 0,\n p95: percentile(times, 95) ?? 0,\n p99: percentile(times, 99) ?? 0,\n avg: times.reduce((s, n) => s + n, 0) / times.length,\n min: times[0],\n max: times[times.length - 1],\n } : null;\n\n return {\n totalApiCalls: calls.length,\n uniqueApiUrls: urls.size,\n apiCallsByMethod: byMethod,\n apiCallsByStatusRange: byStatus,\n apiResponseTime,\n };\n}\n\nexport function buildSummary(timeline: readonly TimelineEntry[]): Summary {\n let total = 0;\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n const totalNavigations = timeline.length;\n let totalActionSteps = 0;\n const actionCategoryCounts: Record<string, number> = {};\n const uniqueUrls = new Set<string>();\n const allApiCalls: ApiCallRecord[] = [];\n\n for (const entry of timeline) {\n uniqueUrls.add(entry.url);\n\n for (const test of entry.tests) {\n total++;\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n\n if (test.actions) {\n for (const action of test.actions) {\n totalActionSteps++;\n if (action.category === 'assertion') {\n totalAssertions++;\n if (action.status === 'passed') passedAssertions++;\n else failedAssertions++;\n } else {\n const cat = action.category;\n actionCategoryCounts[cat] = (actionCategoryCounts[cat] ?? 0) + 1;\n }\n }\n }\n\n if (test.apiCalls && test.apiCalls.length > 0) {\n allApiCalls.push(...test.apiCalls);\n }\n }\n }\n\n const apiStats = buildApiStats(allApiCalls);\n\n return {\n total,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n ...apiStats,\n totalAssertions,\n passedAssertions,\n failedAssertions,\n totalNavigations,\n uniqueNavigationUrls: uniqueUrls.size,\n totalTimelineSteps: timeline.length,\n totalActionSteps,\n actionStepsByCategory: actionCategoryCounts,\n };\n}\n","/**\n * JSON schema version for the Maestro analytics report output.\n *\n * Two writers emit a report — the orchestrator and `merge` — and they hard-coded\n * the version separately, which is how the two drift.\n */\nexport const SCHEMA_VERSION = '1.1.0';\n","/**\n * Merge multiple Maestro report JSON files into a single report.\n */\n\nimport { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestRunReport, TimelineEntry, TimelineStep } from '@testrelic/core';\nimport { buildSummary } from './summary-builder.js';\nimport { SCHEMA_VERSION } from './schema.js';\n\nfunction isTimelineEntry(item: TimelineEntry | TimelineStep): item is TimelineEntry {\n return 'tests' in item && 'visitedAt' in item;\n}\n\nexport function mergeReports(reportPaths: string[]): TestRunReport {\n const allTimeline: (TimelineEntry | TimelineStep)[] = [];\n const timelineEntries: TimelineEntry[] = [];\n let earliestStart = '';\n let latestEnd = '';\n let runId = '';\n\n for (const reportPath of reportPaths) {\n try {\n const content = readFileSync(reportPath, 'utf-8');\n const report = JSON.parse(content) as TestRunReport;\n\n if (!runId) runId = report.testRunId;\n if (!earliestStart || report.startedAt < earliestStart) earliestStart = report.startedAt;\n if (!latestEnd || (report.completedAt && report.completedAt > latestEnd)) latestEnd = report.completedAt;\n\n for (const item of report.timeline) {\n allTimeline.push(item);\n if (isTimelineEntry(item)) timelineEntries.push(item);\n }\n } catch {\n process.stderr.write(`\\u26A0 TestRelic: Unable to read report file: ${reportPath}\\n`);\n }\n }\n\n const summary = buildSummary(timelineEntries);\n const startMs = earliestStart ? new Date(earliestStart).getTime() : Date.now();\n const endMs = latestEnd ? new Date(latestEnd).getTime() : Date.now();\n\n return {\n schemaVersion: SCHEMA_VERSION,\n testRunId: runId || `maestro-merged-${Date.now()}`,\n startedAt: earliestStart || new Date().toISOString(),\n completedAt: latestEnd || new Date().toISOString(),\n totalDuration: endMs - startMs,\n summary,\n ci: null,\n metadata: null,\n timeline: allTimeline,\n shardRunIds: null,\n };\n}\n\nexport function mergeReportsFromDirectory(dirPath: string, outputPath: string): TestRunReport {\n if (!existsSync(dirPath)) {\n throw new Error(`Directory does not exist: ${dirPath}`);\n }\n\n const reportPaths = readdirSync(dirPath)\n .filter((f) => extname(f) === '.json' && f.includes('testrelic'))\n .map((f) => join(dirPath, f));\n\n const merged = mergeReports(reportPaths);\n writeFileSync(outputPath, JSON.stringify(merged, null, 2), 'utf-8');\n return merged;\n}\n"]}
|
package/dist/merge.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {readFileSync,existsSync,readdirSync,writeFileSync}from'fs';import {extname,join}from'path';var A={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0};function
|
|
2
|
-
`);}let n=T(i),
|
|
1
|
+
import {readFileSync,existsSync,readdirSync,writeFileSync}from'fs';import {extname,join}from'path';var A={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0};function b(t){return t===null?"error":t>=200&&t<300?"2xx":t>=300&&t<400?"3xx":t>=400&&t<500?"4xx":t>=500&&t<600?"5xx":"error"}function d(t,s){if(t.length===0)return null;let i=Math.min(t.length-1,Math.floor(s/100*t.length));return t[i]}function E(t){if(t.length===0)return {totalApiCalls:0,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{...A},apiResponseTime:null};let s={},i={...A},r=new Set,e=[];for(let n of t)s[n.method]=(s[n.method]??0)+1,i[b(n.responseStatusCode)]+=1,r.add(n.url),Number.isFinite(n.responseTimeMs)&&n.responseTimeMs>0&&e.push(n.responseTimeMs);e.sort((n,a)=>n-a);let l=e.length>0?{p50:d(e,50)??0,p95:d(e,95)??0,p99:d(e,99)??0,avg:e.reduce((n,a)=>n+a,0)/e.length,min:e[0],max:e[e.length-1]}:null;return {totalApiCalls:t.length,uniqueApiUrls:r.size,apiCallsByMethod:s,apiCallsByStatusRange:i,apiResponseTime:l}}function T(t){let s=0,i=0,r=0,e=0,l=0,n=0,a=0,c=0,m=0,f=t.length,o=0,u={},g=new Set,S=[];for(let R of t){g.add(R.url);for(let p of R.tests){switch(s++,p.status){case "passed":i++;break;case "failed":r++;break;case "flaky":e++;break;case "skipped":l++;break;case "timedout":n++;break}if(p.actions)for(let y of p.actions)if(o++,y.category==="assertion")a++,y.status==="passed"?c++:m++;else {let x=y.category;u[x]=(u[x]??0)+1;}p.apiCalls&&p.apiCalls.length>0&&S.push(...p.apiCalls);}}let C=E(S);return {total:s,passed:i,failed:r,flaky:e,skipped:l,timedout:n,...C,totalAssertions:a,passedAssertions:c,failedAssertions:m,totalNavigations:f,uniqueNavigationUrls:g.size,totalTimelineSteps:t.length,totalActionSteps:o,actionStepsByCategory:u}}var h="1.1.0";function N(t){return "tests"in t&&"visitedAt"in t}function U(t){let s=[],i=[],r="",e="",l="";for(let m of t)try{let f=readFileSync(m,"utf-8"),o=JSON.parse(f);l||(l=o.testRunId),(!r||o.startedAt<r)&&(r=o.startedAt),(!e||o.completedAt&&o.completedAt>e)&&(e=o.completedAt);for(let u of o.timeline)s.push(u),N(u)&&i.push(u);}catch{process.stderr.write(`\u26A0 TestRelic: Unable to read report file: ${m}
|
|
2
|
+
`);}let n=T(i),a=r?new Date(r).getTime():Date.now(),c=e?new Date(e).getTime():Date.now();return {schemaVersion:h,testRunId:l||`maestro-merged-${Date.now()}`,startedAt:r||new Date().toISOString(),completedAt:e||new Date().toISOString(),totalDuration:c-a,summary:n,ci:null,metadata:null,timeline:s,shardRunIds:null}}function $(t,s){if(!existsSync(t))throw new Error(`Directory does not exist: ${t}`);let i=readdirSync(t).filter(e=>extname(e)===".json"&&e.includes("testrelic")).map(e=>join(t,e)),r=U(i);return writeFileSync(s,JSON.stringify(r,null,2),"utf-8"),r}export{U as mergeReports,$ as mergeReportsFromDirectory};//# sourceMappingURL=merge.js.map
|
|
3
3
|
//# sourceMappingURL=merge.js.map
|
package/dist/merge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/summary-builder.ts","../src/merge.ts"],"names":["EMPTY_STATUS_RANGE","statusBucket","code","percentile","sorted","p","idx","buildApiStats","calls","byMethod","byStatus","urls","times","c","a","b","apiResponseTime","s","n","buildSummary","timeline","total","passed","failed","flaky","skipped","timedout","totalAssertions","passedAssertions","failedAssertions","totalNavigations","totalActionSteps","actionCategoryCounts","uniqueUrls","allApiCalls","entry","test","action","cat","apiStats","isTimelineEntry","item","mergeReports","reportPaths","allTimeline","timelineEntries","earliestStart","latestEnd","runId","reportPath","content","readFileSync","report","summary","startMs","endMs","mergeReportsFromDirectory","dirPath","outputPath","existsSync","readdirSync","f","extname","join","merged","writeFileSync"],"mappings":"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","file":"merge.js","sourcesContent":["/**\n * Computes Summary stats from TimelineEntry arrays.\n */\n\nimport type { Summary, TimelineEntry, ApiCallRecord } from '@testrelic/core';\n\ntype ApiCallsByStatusRange = Record<'2xx' | '3xx' | '4xx' | '5xx' | 'error', number>;\n\nconst EMPTY_STATUS_RANGE: ApiCallsByStatusRange = {\n '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0,\n};\n\nfunction statusBucket(code: number | null): keyof ApiCallsByStatusRange {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\nfunction percentile(sorted: number[], p: number): number | null {\n if (sorted.length === 0) return null;\n const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));\n return sorted[idx];\n}\n\nfunction buildApiStats(calls: ApiCallRecord[]): {\n totalApiCalls: number;\n uniqueApiUrls: number;\n apiCallsByMethod: Record<string, number>;\n apiCallsByStatusRange: ApiCallsByStatusRange;\n apiResponseTime: Summary['apiResponseTime'];\n} {\n if (calls.length === 0) {\n return {\n totalApiCalls: 0,\n uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { ...EMPTY_STATUS_RANGE },\n apiResponseTime: null,\n };\n }\n\n const byMethod: Record<string, number> = {};\n const byStatus: ApiCallsByStatusRange = { ...EMPTY_STATUS_RANGE };\n const urls = new Set<string>();\n const times: number[] = [];\n\n for (const c of calls) {\n byMethod[c.method] = (byMethod[c.method] ?? 0) + 1;\n byStatus[statusBucket(c.responseStatusCode)] += 1;\n urls.add(c.url);\n if (Number.isFinite(c.responseTimeMs) && c.responseTimeMs > 0) {\n times.push(c.responseTimeMs);\n }\n }\n\n times.sort((a, b) => a - b);\n const apiResponseTime: Summary['apiResponseTime'] = times.length > 0 ? {\n p50: percentile(times, 50) ?? 0,\n p95: percentile(times, 95) ?? 0,\n p99: percentile(times, 99) ?? 0,\n avg: times.reduce((s, n) => s + n, 0) / times.length,\n min: times[0],\n max: times[times.length - 1],\n } : null;\n\n return {\n totalApiCalls: calls.length,\n uniqueApiUrls: urls.size,\n apiCallsByMethod: byMethod,\n apiCallsByStatusRange: byStatus,\n apiResponseTime,\n };\n}\n\nexport function buildSummary(timeline: readonly TimelineEntry[]): Summary {\n let total = 0;\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n const totalNavigations = timeline.length;\n let totalActionSteps = 0;\n const actionCategoryCounts: Record<string, number> = {};\n const uniqueUrls = new Set<string>();\n const allApiCalls: ApiCallRecord[] = [];\n\n for (const entry of timeline) {\n uniqueUrls.add(entry.url);\n\n for (const test of entry.tests) {\n total++;\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n\n if (test.actions) {\n for (const action of test.actions) {\n totalActionSteps++;\n if (action.category === 'assertion') {\n totalAssertions++;\n if (action.status === 'passed') passedAssertions++;\n else failedAssertions++;\n } else {\n const cat = action.category;\n actionCategoryCounts[cat] = (actionCategoryCounts[cat] ?? 0) + 1;\n }\n }\n }\n\n if (test.apiCalls && test.apiCalls.length > 0) {\n allApiCalls.push(...test.apiCalls);\n }\n }\n }\n\n const apiStats = buildApiStats(allApiCalls);\n\n return {\n total,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n ...apiStats,\n totalAssertions,\n passedAssertions,\n failedAssertions,\n totalNavigations,\n uniqueNavigationUrls: uniqueUrls.size,\n totalTimelineSteps: timeline.length,\n totalActionSteps,\n actionStepsByCategory: actionCategoryCounts,\n };\n}\n","/**\n * Merge multiple Maestro report JSON files into a single report.\n */\n\nimport { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestRunReport, TimelineEntry, TimelineStep } from '@testrelic/core';\nimport { buildSummary } from './summary-builder.js';\n\nfunction isTimelineEntry(item: TimelineEntry | TimelineStep): item is TimelineEntry {\n return 'tests' in item && 'visitedAt' in item;\n}\n\nexport function mergeReports(reportPaths: string[]): TestRunReport {\n const allTimeline: (TimelineEntry | TimelineStep)[] = [];\n const timelineEntries: TimelineEntry[] = [];\n let earliestStart = '';\n let latestEnd = '';\n let runId = '';\n\n for (const reportPath of reportPaths) {\n try {\n const content = readFileSync(reportPath, 'utf-8');\n const report = JSON.parse(content) as TestRunReport;\n\n if (!runId) runId = report.testRunId;\n if (!earliestStart || report.startedAt < earliestStart) earliestStart = report.startedAt;\n if (!latestEnd || (report.completedAt && report.completedAt > latestEnd)) latestEnd = report.completedAt;\n\n for (const item of report.timeline) {\n allTimeline.push(item);\n if (isTimelineEntry(item)) timelineEntries.push(item);\n }\n } catch {\n process.stderr.write(`\\u26A0 TestRelic: Unable to read report file: ${reportPath}\\n`);\n }\n }\n\n const summary = buildSummary(timelineEntries);\n const startMs = earliestStart ? new Date(earliestStart).getTime() : Date.now();\n const endMs = latestEnd ? new Date(latestEnd).getTime() : Date.now();\n\n return {\n schemaVersion: '1.0.0',\n testRunId: runId || `maestro-merged-${Date.now()}`,\n startedAt: earliestStart || new Date().toISOString(),\n completedAt: latestEnd || new Date().toISOString(),\n totalDuration: endMs - startMs,\n summary,\n ci: null,\n metadata: null,\n timeline: allTimeline,\n shardRunIds: null,\n };\n}\n\nexport function mergeReportsFromDirectory(dirPath: string, outputPath: string): TestRunReport {\n if (!existsSync(dirPath)) {\n throw new Error(`Directory does not exist: ${dirPath}`);\n }\n\n const reportPaths = readdirSync(dirPath)\n .filter((f) => extname(f) === '.json' && f.includes('testrelic'))\n .map((f) => join(dirPath, f));\n\n const merged = mergeReports(reportPaths);\n writeFileSync(outputPath, JSON.stringify(merged, null, 2), 'utf-8');\n return merged;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/summary-builder.ts","../src/schema.ts","../src/merge.ts"],"names":["EMPTY_STATUS_RANGE","statusBucket","code","percentile","sorted","p","idx","buildApiStats","calls","byMethod","byStatus","urls","times","c","a","b","apiResponseTime","s","n","buildSummary","timeline","total","passed","failed","flaky","skipped","timedout","totalAssertions","passedAssertions","failedAssertions","totalNavigations","totalActionSteps","actionCategoryCounts","uniqueUrls","allApiCalls","entry","test","action","cat","apiStats","SCHEMA_VERSION","isTimelineEntry","item","mergeReports","reportPaths","allTimeline","timelineEntries","earliestStart","latestEnd","runId","reportPath","content","readFileSync","report","summary","startMs","endMs","mergeReportsFromDirectory","dirPath","outputPath","existsSync","readdirSync","f","extname","join","merged","writeFileSync"],"mappings":"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","file":"merge.js","sourcesContent":["/**\n * Computes Summary stats from TimelineEntry arrays.\n */\n\nimport type { Summary, TimelineEntry, ApiCallRecord } from '@testrelic/core';\n\ntype ApiCallsByStatusRange = Record<'2xx' | '3xx' | '4xx' | '5xx' | 'error', number>;\n\nconst EMPTY_STATUS_RANGE: ApiCallsByStatusRange = {\n '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0,\n};\n\nfunction statusBucket(code: number | null): keyof ApiCallsByStatusRange {\n if (code === null) return 'error';\n if (code >= 200 && code < 300) return '2xx';\n if (code >= 300 && code < 400) return '3xx';\n if (code >= 400 && code < 500) return '4xx';\n if (code >= 500 && code < 600) return '5xx';\n return 'error';\n}\n\nfunction percentile(sorted: number[], p: number): number | null {\n if (sorted.length === 0) return null;\n const idx = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));\n return sorted[idx];\n}\n\nfunction buildApiStats(calls: ApiCallRecord[]): {\n totalApiCalls: number;\n uniqueApiUrls: number;\n apiCallsByMethod: Record<string, number>;\n apiCallsByStatusRange: ApiCallsByStatusRange;\n apiResponseTime: Summary['apiResponseTime'];\n} {\n if (calls.length === 0) {\n return {\n totalApiCalls: 0,\n uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { ...EMPTY_STATUS_RANGE },\n apiResponseTime: null,\n };\n }\n\n const byMethod: Record<string, number> = {};\n const byStatus: ApiCallsByStatusRange = { ...EMPTY_STATUS_RANGE };\n const urls = new Set<string>();\n const times: number[] = [];\n\n for (const c of calls) {\n byMethod[c.method] = (byMethod[c.method] ?? 0) + 1;\n byStatus[statusBucket(c.responseStatusCode)] += 1;\n urls.add(c.url);\n if (Number.isFinite(c.responseTimeMs) && c.responseTimeMs > 0) {\n times.push(c.responseTimeMs);\n }\n }\n\n times.sort((a, b) => a - b);\n const apiResponseTime: Summary['apiResponseTime'] = times.length > 0 ? {\n p50: percentile(times, 50) ?? 0,\n p95: percentile(times, 95) ?? 0,\n p99: percentile(times, 99) ?? 0,\n avg: times.reduce((s, n) => s + n, 0) / times.length,\n min: times[0],\n max: times[times.length - 1],\n } : null;\n\n return {\n totalApiCalls: calls.length,\n uniqueApiUrls: urls.size,\n apiCallsByMethod: byMethod,\n apiCallsByStatusRange: byStatus,\n apiResponseTime,\n };\n}\n\nexport function buildSummary(timeline: readonly TimelineEntry[]): Summary {\n let total = 0;\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n const totalNavigations = timeline.length;\n let totalActionSteps = 0;\n const actionCategoryCounts: Record<string, number> = {};\n const uniqueUrls = new Set<string>();\n const allApiCalls: ApiCallRecord[] = [];\n\n for (const entry of timeline) {\n uniqueUrls.add(entry.url);\n\n for (const test of entry.tests) {\n total++;\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n\n if (test.actions) {\n for (const action of test.actions) {\n totalActionSteps++;\n if (action.category === 'assertion') {\n totalAssertions++;\n if (action.status === 'passed') passedAssertions++;\n else failedAssertions++;\n } else {\n const cat = action.category;\n actionCategoryCounts[cat] = (actionCategoryCounts[cat] ?? 0) + 1;\n }\n }\n }\n\n if (test.apiCalls && test.apiCalls.length > 0) {\n allApiCalls.push(...test.apiCalls);\n }\n }\n }\n\n const apiStats = buildApiStats(allApiCalls);\n\n return {\n total,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n ...apiStats,\n totalAssertions,\n passedAssertions,\n failedAssertions,\n totalNavigations,\n uniqueNavigationUrls: uniqueUrls.size,\n totalTimelineSteps: timeline.length,\n totalActionSteps,\n actionStepsByCategory: actionCategoryCounts,\n };\n}\n","/**\n * JSON schema version for the Maestro analytics report output.\n *\n * Two writers emit a report — the orchestrator and `merge` — and they hard-coded\n * the version separately, which is how the two drift.\n */\nexport const SCHEMA_VERSION = '1.1.0';\n","/**\n * Merge multiple Maestro report JSON files into a single report.\n */\n\nimport { readFileSync, readdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestRunReport, TimelineEntry, TimelineStep } from '@testrelic/core';\nimport { buildSummary } from './summary-builder.js';\nimport { SCHEMA_VERSION } from './schema.js';\n\nfunction isTimelineEntry(item: TimelineEntry | TimelineStep): item is TimelineEntry {\n return 'tests' in item && 'visitedAt' in item;\n}\n\nexport function mergeReports(reportPaths: string[]): TestRunReport {\n const allTimeline: (TimelineEntry | TimelineStep)[] = [];\n const timelineEntries: TimelineEntry[] = [];\n let earliestStart = '';\n let latestEnd = '';\n let runId = '';\n\n for (const reportPath of reportPaths) {\n try {\n const content = readFileSync(reportPath, 'utf-8');\n const report = JSON.parse(content) as TestRunReport;\n\n if (!runId) runId = report.testRunId;\n if (!earliestStart || report.startedAt < earliestStart) earliestStart = report.startedAt;\n if (!latestEnd || (report.completedAt && report.completedAt > latestEnd)) latestEnd = report.completedAt;\n\n for (const item of report.timeline) {\n allTimeline.push(item);\n if (isTimelineEntry(item)) timelineEntries.push(item);\n }\n } catch {\n process.stderr.write(`\\u26A0 TestRelic: Unable to read report file: ${reportPath}\\n`);\n }\n }\n\n const summary = buildSummary(timelineEntries);\n const startMs = earliestStart ? new Date(earliestStart).getTime() : Date.now();\n const endMs = latestEnd ? new Date(latestEnd).getTime() : Date.now();\n\n return {\n schemaVersion: SCHEMA_VERSION,\n testRunId: runId || `maestro-merged-${Date.now()}`,\n startedAt: earliestStart || new Date().toISOString(),\n completedAt: latestEnd || new Date().toISOString(),\n totalDuration: endMs - startMs,\n summary,\n ci: null,\n metadata: null,\n timeline: allTimeline,\n shardRunIds: null,\n };\n}\n\nexport function mergeReportsFromDirectory(dirPath: string, outputPath: string): TestRunReport {\n if (!existsSync(dirPath)) {\n throw new Error(`Directory does not exist: ${dirPath}`);\n }\n\n const reportPaths = readdirSync(dirPath)\n .filter((f) => extname(f) === '.json' && f.includes('testrelic'))\n .map((f) => join(dirPath, f));\n\n const merged = mergeReports(reportPaths);\n writeFileSync(outputPath, JSON.stringify(merged, null, 2), 'utf-8');\n return merged;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testrelic/maestro-analytics",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-next.121",
|
|
4
4
|
"description": "Maestro mobile testing analytics — JUnit/artifact parsing, step-level timing, AI defect aggregation, visual regression tracking, and interactive HTML reports",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maestro",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"fast-xml-parser": "^5.7.0",
|
|
57
57
|
"yaml": "^2.7.0",
|
|
58
|
-
"@testrelic/core": "2.
|
|
58
|
+
"@testrelic/core": "2.16.0-next.121"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^20.0.0",
|