@testrelic/playwright-analytics 2.16.1-next.142 → 2.16.2-next.145

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/dist/cli.cjs CHANGED
@@ -436,7 +436,7 @@ __export(report_server_exports, {
436
436
  startReportServer: () => startReportServer
437
437
  });
438
438
  function startReportServer(reportDir, options) {
439
- return new Promise((resolve2, reject) => {
439
+ return new Promise((resolve3, reject) => {
440
440
  const startPort = options?.port ?? DEFAULT_PORT;
441
441
  const startTime = Date.now();
442
442
  let inactivityTimer;
@@ -553,7 +553,7 @@ function startReportServer(reportDir, options) {
553
553
  return;
554
554
  }
555
555
  resetTimer();
556
- resolve2({
556
+ resolve3({
557
557
  port: addr.port,
558
558
  dispose: () => new Promise((res) => {
559
559
  clearTimeout(inactivityTimer);
@@ -648,6 +648,10 @@ function loadStreamingReport(dir) {
648
648
  }
649
649
  return { summary, index, dir };
650
650
  }
651
+ function copyInto(src, dest, recursive) {
652
+ if ((0, import_node_path.resolve)(src) === (0, import_node_path.resolve)(dest)) return;
653
+ (0, import_node_fs.cpSync)(src, dest, recursive ? { recursive: true } : void 0);
654
+ }
651
655
  async function mergeReports(files, options) {
652
656
  const reports = [];
653
657
  const streamingReports = [];
@@ -707,15 +711,15 @@ async function mergeReports(files, options) {
707
711
  const testFiles = (0, import_node_fs.readdirSync)(srcTestsDir, { withFileTypes: true });
708
712
  for (const f of testFiles) {
709
713
  if (f.isDirectory()) {
710
- (0, import_node_fs.cpSync)((0, import_node_path.join)(srcTestsDir, f.name), (0, import_node_path.join)(mergedTestsDir, f.name), { recursive: true });
714
+ copyInto((0, import_node_path.join)(srcTestsDir, f.name), (0, import_node_path.join)(mergedTestsDir, f.name), true);
711
715
  } else if (f.name.endsWith(".json")) {
712
- (0, import_node_fs.cpSync)((0, import_node_path.join)(srcTestsDir, f.name), (0, import_node_path.join)(mergedTestsDir, f.name));
716
+ copyInto((0, import_node_path.join)(srcTestsDir, f.name), (0, import_node_path.join)(mergedTestsDir, f.name), false);
713
717
  }
714
718
  }
715
719
  }
716
720
  const srcArtifacts = (0, import_node_path.join)((0, import_node_path.dirname)(sr.dir), "artifacts");
717
721
  if ((0, import_node_fs.existsSync)(srcArtifacts)) {
718
- (0, import_node_fs.cpSync)(srcArtifacts, (0, import_node_path.join)(outputDir, "artifacts"), { recursive: true });
722
+ copyInto(srcArtifacts, (0, import_node_path.join)(outputDir, "artifacts"), true);
719
723
  }
720
724
  for (const entry of sr.index) {
721
725
  if (!seenTestIds.has(entry.id)) {
package/dist/merge.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var crypto=require('crypto'),fs=require('fs'),path=require('path'),core=require('@testrelic/core');function J(n){try{return fs.statSync(n).isDirectory()&&fs.existsSync(path.join(n,"index.json"))}catch{return false}}function L(n){let a=path.join(n,"summary.json"),e=path.join(n,"index.json"),o;try{o=JSON.parse(fs.readFileSync(a,"utf-8"));}catch(i){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read summary.json from: ${n}`,i)}let c;try{c=JSON.parse(fs.readFileSync(e,"utf-8"));}catch(i){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read index.json from: ${n}`,i)}return {summary:o,index:c,dir:n}}async function P(n,a){let e=[],o=[];for(let t of n){if(J(t)){o.push(L(t));continue}let r;try{r=fs.readFileSync(t,"utf-8");}catch(l){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read file: ${t}`,l)}let m;try{m=JSON.parse(r);}catch(l){throw core.createError(core.ErrorCode.MERGE_INVALID_SCHEMA,`Invalid JSON in file: ${t}`,l)}if(!core.isValidTestRunReport(m))throw core.createError(core.ErrorCode.MERGE_INVALID_SCHEMA,`Invalid report schema in file: ${t}`);e.push(m);}let c=e.map(t=>t.testRunId),i=[];for(let t of e)i.push(...t.timeline);if(i.sort((t,r)=>{let m="visitedAt"in t?t.visitedAt:t.timestamp,l="visitedAt"in r?r.visitedAt:r.timestamp;return new Date(m).getTime()-new Date(l).getTime()}),o.length>0){let t=path.dirname(a.output),r=path.join(t,".testrelic-report"),m=path.join(r,"tests");fs.mkdirSync(m,{recursive:true});let l=new Set,F=[];for(let T of o){let x=path.join(T.dir,"tests");if(fs.existsSync(x)){let y=fs.readdirSync(x,{withFileTypes:true});for(let u of y)u.isDirectory()?fs.cpSync(path.join(x,u.name),path.join(m,u.name),{recursive:true}):u.name.endsWith(".json")&&fs.cpSync(path.join(x,u.name),path.join(m,u.name));}let N=path.join(path.dirname(T.dir),"artifacts");fs.existsSync(N)&&fs.cpSync(N,path.join(t,"artifacts"),{recursive:true});for(let y of T.index)l.has(y.id)||(l.add(y.id),F.push(y));}fs.writeFileSync(path.join(r,"index.json"),JSON.stringify(F),"utf-8");}let g=[...e.map(t=>t.summary),...o.map(t=>t.summary)],R=G(g,i.length),d=e.reduce((t,r)=>r.startedAt<t?r.startedAt:t,e[0]?.startedAt??new Date().toISOString()),p=e.reduce((t,r)=>r.completedAt>t?r.completedAt:t,e[0]?.completedAt??new Date().toISOString()),E=new Date(p).getTime()-new Date(d).getTime(),f={schemaVersion:e[0]?.schemaVersion??"1.0.0",testRunId:a.testRunId??crypto.randomUUID(),startedAt:d,completedAt:p,totalDuration:E,summary:R,ci:e.find(t=>t.ci!==null)?.ci??null,metadata:e.find(t=>t.metadata!==null)?.metadata??null,timeline:i,shardRunIds:c},h=path.dirname(a.output);return fs.mkdirSync(h,{recursive:true}),fs.writeFileSync(a.output,JSON.stringify(f,null,2),"utf-8"),f}function G(n,a){let e=0,o=0,c=0,i=0,g=0,R=0,d=0,p=0,E=0,f=0,h=0;for(let t of n)e+=t.total,o+=t.passed,c+=t.failed,i+=t.flaky,g+=t.skipped,R+=t.timedout??0,d+=t.totalApiCalls??0,p+=t.totalAssertions??0,E+=t.passedAssertions??0,f+=t.failedAssertions??0,h+=t.totalNavigations??0;return {total:e,passed:o,failed:c,flaky:i,skipped:g,timedout:R,totalApiCalls:d,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},apiResponseTime:null,totalAssertions:p,passedAssertions:E,failedAssertions:f,totalNavigations:h,uniqueNavigationUrls:0,totalTimelineSteps:a,totalActionSteps:0,actionStepsByCategory:{}}}exports.mergeReports=P;//# sourceMappingURL=merge.cjs.map
1
+ 'use strict';var crypto=require('crypto'),fs=require('fs'),path=require('path'),core=require('@testrelic/core');function G(r){try{return fs.statSync(r).isDirectory()&&fs.existsSync(path.join(r,"index.json"))}catch{return false}}function V(r){let i=path.join(r,"summary.json"),e=path.join(r,"index.json"),a;try{a=JSON.parse(fs.readFileSync(i,"utf-8"));}catch(o){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read summary.json from: ${r}`,o)}let c;try{c=JSON.parse(fs.readFileSync(e,"utf-8"));}catch(o){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read index.json from: ${r}`,o)}return {summary:a,index:c,dir:r}}function D(r,i,e){path.resolve(r)!==path.resolve(i)&&fs.cpSync(r,i,e?{recursive:true}:void 0);}async function H(r,i){let e=[],a=[];for(let t of r){if(G(t)){a.push(V(t));continue}let s;try{s=fs.readFileSync(t,"utf-8");}catch(l){throw core.createError(core.ErrorCode.MERGE_READ_FAILED,`Failed to read file: ${t}`,l)}let m;try{m=JSON.parse(s);}catch(l){throw core.createError(core.ErrorCode.MERGE_INVALID_SCHEMA,`Invalid JSON in file: ${t}`,l)}if(!core.isValidTestRunReport(m))throw core.createError(core.ErrorCode.MERGE_INVALID_SCHEMA,`Invalid report schema in file: ${t}`);e.push(m);}let c=e.map(t=>t.testRunId),o=[];for(let t of e)o.push(...t.timeline);if(o.sort((t,s)=>{let m="visitedAt"in t?t.visitedAt:t.timestamp,l="visitedAt"in s?s.visitedAt:s.timestamp;return new Date(m).getTime()-new Date(l).getTime()}),a.length>0){let t=path.dirname(i.output),s=path.join(t,".testrelic-report"),m=path.join(s,"tests");fs.mkdirSync(m,{recursive:true});let l=new Set,F=[];for(let T of a){let x=path.join(T.dir,"tests");if(fs.existsSync(x)){let y=fs.readdirSync(x,{withFileTypes:true});for(let u of y)u.isDirectory()?D(path.join(x,u.name),path.join(m,u.name),true):u.name.endsWith(".json")&&D(path.join(x,u.name),path.join(m,u.name),false);}let N=path.join(path.dirname(T.dir),"artifacts");fs.existsSync(N)&&D(N,path.join(t,"artifacts"),true);for(let y of T.index)l.has(y.id)||(l.add(y.id),F.push(y));}fs.writeFileSync(path.join(s,"index.json"),JSON.stringify(F),"utf-8");}let g=[...e.map(t=>t.summary),...a.map(t=>t.summary)],R=$(g,o.length),d=e.reduce((t,s)=>s.startedAt<t?s.startedAt:t,e[0]?.startedAt??new Date().toISOString()),p=e.reduce((t,s)=>s.completedAt>t?s.completedAt:t,e[0]?.completedAt??new Date().toISOString()),E=new Date(p).getTime()-new Date(d).getTime(),f={schemaVersion:e[0]?.schemaVersion??"1.0.0",testRunId:i.testRunId??crypto.randomUUID(),startedAt:d,completedAt:p,totalDuration:E,summary:R,ci:e.find(t=>t.ci!==null)?.ci??null,metadata:e.find(t=>t.metadata!==null)?.metadata??null,timeline:o,shardRunIds:c},h=path.dirname(i.output);return fs.mkdirSync(h,{recursive:true}),fs.writeFileSync(i.output,JSON.stringify(f,null,2),"utf-8"),f}function $(r,i){let e=0,a=0,c=0,o=0,g=0,R=0,d=0,p=0,E=0,f=0,h=0;for(let t of r)e+=t.total,a+=t.passed,c+=t.failed,o+=t.flaky,g+=t.skipped,R+=t.timedout??0,d+=t.totalApiCalls??0,p+=t.totalAssertions??0,E+=t.passedAssertions??0,f+=t.failedAssertions??0,h+=t.totalNavigations??0;return {total:e,passed:a,failed:c,flaky:o,skipped:g,timedout:R,totalApiCalls:d,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},apiResponseTime:null,totalAssertions:p,passedAssertions:E,failedAssertions:f,totalNavigations:h,uniqueNavigationUrls:0,totalTimelineSteps:i,totalActionSteps:0,actionStepsByCategory:{}}}exports.mergeReports=H;//# sourceMappingURL=merge.cjs.map
2
2
  //# sourceMappingURL=merge.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/merge.ts"],"names":["isStreamingDir","path","statSync","existsSync","join","loadStreamingReport","dir","summaryPath","indexPath","summary","readFileSync","err","createError","ErrorCode","index","mergeReports","files","options","reports","streamingReports","file","raw","parsed","isValidTestRunReport","shardRunIds","r","allTimelines","report","a","b","timeA","timeB","outputDir","dirname","mergedReportDir","mergedTestsDir","mkdirSync","seenTestIds","mergedIndex","sr","srcTestsDir","testFiles","readdirSync","f","cpSync","srcArtifacts","entry","writeFileSync","allSummaries","recalculateSummaryFromList","startedAt","earliest","completedAt","latest","totalDuration","merged","randomUUID","summaries","timelineLength","total","passed","failed","flaky","skipped","timedout","totalApiCalls","totalAssertions","passedAssertions","failedAssertions","totalNavigations","s"],"mappings":"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","file":"merge.cjs","sourcesContent":["/**\n * @testrelic/playwright-analytics/merge\n *\n * Merges multiple shard report files into a single unified timeline.\n * Tree-shakeable: separate entry point from the main reporter.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, readdirSync, cpSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type {\n TestRunReport,\n MergeOptions,\n Summary,\n TimelineEntry,\n TimelineStep,\n TestIndexEntry,\n} from '@testrelic/core';\nimport { isValidTestRunReport, createError, ErrorCode } from '@testrelic/core';\n\n/**\n * Check if a path is a streaming report directory (contains index.json).\n */\nfunction isStreamingDir(path: string): boolean {\n try {\n const stat = statSync(path);\n return stat.isDirectory() && existsSync(join(path, 'index.json'));\n } catch {\n return false;\n }\n}\n\n/**\n * Load a streaming report directory as a TestRunReport with summary data.\n */\nfunction loadStreamingReport(dir: string): { summary: Summary; index: TestIndexEntry[]; dir: string } {\n const summaryPath = join(dir, 'summary.json');\n const indexPath = join(dir, 'index.json');\n\n let summary: Summary;\n try {\n summary = JSON.parse(readFileSync(summaryPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read summary.json from: ${dir}`, err);\n }\n\n let index: TestIndexEntry[];\n try {\n index = JSON.parse(readFileSync(indexPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read index.json from: ${dir}`, err);\n }\n\n return { summary, index, dir };\n}\n\nexport async function mergeReports(\n files: string[],\n options: MergeOptions,\n): Promise<TestRunReport> {\n const reports: TestRunReport[] = [];\n const streamingReports: Array<{ summary: Summary; index: TestIndexEntry[]; dir: string }> = [];\n\n for (const file of files) {\n // Check if this is a streaming report directory\n if (isStreamingDir(file)) {\n streamingReports.push(loadStreamingReport(file));\n continue;\n }\n\n let raw: string;\n try {\n raw = readFileSync(file, 'utf-8');\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_READ_FAILED,\n `Failed to read file: ${file}`,\n err,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid JSON in file: ${file}`,\n err,\n );\n }\n\n if (!isValidTestRunReport(parsed)) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid report schema in file: ${file}`,\n );\n }\n\n reports.push(parsed);\n }\n\n // Collect shard IDs\n const shardRunIds = reports.map((r) => r.testRunId);\n\n // Merge timelines chronologically\n const allTimelines: (TimelineEntry | TimelineStep)[] = [];\n for (const report of reports) {\n allTimelines.push(...report.timeline);\n }\n allTimelines.sort((a, b) => {\n const timeA = 'visitedAt' in a ? a.visitedAt : a.timestamp;\n const timeB = 'visitedAt' in b ? b.visitedAt : b.timestamp;\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Merge streaming report data: copy test detail files and merge indexes\n if (streamingReports.length > 0) {\n const outputDir = dirname(options.output);\n const mergedReportDir = join(outputDir, '.testrelic-report');\n const mergedTestsDir = join(mergedReportDir, 'tests');\n mkdirSync(mergedTestsDir, { recursive: true });\n\n const seenTestIds = new Set<string>();\n const mergedIndex: TestIndexEntry[] = [];\n\n for (const sr of streamingReports) {\n // Copy test detail files. The v3 layout keeps each test in its own\n // `tests/<id>/` directory (meta.json, network.jsonl, ...), and only\n // flat `*.json` files were ever copied — so the merged index claimed\n // comparisons that no meta.json could back, and `tr visual` refuses a\n // report whose counts it cannot verify. Directories are copied whole;\n // flat files are kept for the older per-test layout.\n const srcTestsDir = join(sr.dir, 'tests');\n if (existsSync(srcTestsDir)) {\n const testFiles = readdirSync(srcTestsDir, { withFileTypes: true });\n for (const f of testFiles) {\n if (f.isDirectory()) {\n cpSync(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), { recursive: true });\n } else if (f.name.endsWith('.json')) {\n cpSync(join(srcTestsDir, f.name), join(mergedTestsDir, f.name));\n }\n }\n }\n\n // The images those meta.json files point at — `artifacts/<run>/<test>/…`\n // — are written BESIDE `.testrelic-report`, not inside it, so a merged\n // report that copied only the JSON listed comparisons whose pictures\n // were left behind on the shard. Shards write distinct run folders, so\n // the trees merge without overwriting.\n const srcArtifacts = join(dirname(sr.dir), 'artifacts');\n if (existsSync(srcArtifacts)) {\n cpSync(srcArtifacts, join(outputDir, 'artifacts'), { recursive: true });\n }\n\n // Merge index entries (dedup by id)\n for (const entry of sr.index) {\n if (!seenTestIds.has(entry.id)) {\n seenTestIds.add(entry.id);\n mergedIndex.push(entry);\n }\n }\n }\n\n // Write merged index and summary\n writeFileSync(join(mergedReportDir, 'index.json'), JSON.stringify(mergedIndex), 'utf-8');\n }\n\n // Recalculate summary across all shards (including streaming)\n const allSummaries = [\n ...reports.map((r) => r.summary),\n ...streamingReports.map((sr) => sr.summary),\n ];\n const summary = recalculateSummaryFromList(allSummaries, allTimelines.length);\n\n // Compute timing\n const startedAt = reports.reduce(\n (earliest, r) => (r.startedAt < earliest ? r.startedAt : earliest),\n reports[0]?.startedAt ?? new Date().toISOString(),\n );\n const completedAt = reports.reduce(\n (latest, r) => (r.completedAt > latest ? r.completedAt : latest),\n reports[0]?.completedAt ?? new Date().toISOString(),\n );\n const totalDuration = new Date(completedAt).getTime() - new Date(startedAt).getTime();\n\n const merged: TestRunReport = {\n schemaVersion: reports[0]?.schemaVersion ?? '1.0.0',\n testRunId: options.testRunId ?? randomUUID(),\n startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: reports.find((r) => r.ci !== null)?.ci ?? null,\n metadata: reports.find((r) => r.metadata !== null)?.metadata ?? null,\n timeline: allTimelines,\n shardRunIds,\n };\n\n // Write to disk\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, JSON.stringify(merged, null, 2), 'utf-8');\n\n return merged;\n}\n\nfunction recalculateSummaryFromList(summaries: Summary[], timelineLength: number): 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 totalApiCalls = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n let totalNavigations = 0;\n\n for (const s of summaries) {\n total += s.total;\n passed += s.passed;\n failed += s.failed;\n flaky += s.flaky;\n skipped += s.skipped;\n timedout += s.timedout ?? 0;\n totalApiCalls += s.totalApiCalls ?? 0;\n totalAssertions += s.totalAssertions ?? 0;\n passedAssertions += s.passedAssertions ?? 0;\n failedAssertions += s.failedAssertions ?? 0;\n totalNavigations += s.totalNavigations ?? 0;\n }\n\n return {\n total, passed, failed, flaky, skipped, timedout,\n totalApiCalls, uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n apiResponseTime: null,\n totalAssertions, passedAssertions, failedAssertions,\n totalNavigations, uniqueNavigationUrls: 0,\n totalTimelineSteps: timelineLength,\n totalActionSteps: 0,\n actionStepsByCategory: {},\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/merge.ts"],"names":["isStreamingDir","path","statSync","existsSync","join","loadStreamingReport","dir","summaryPath","indexPath","summary","readFileSync","err","createError","ErrorCode","index","copyInto","src","dest","recursive","resolve","cpSync","mergeReports","files","options","reports","streamingReports","file","raw","parsed","isValidTestRunReport","shardRunIds","r","allTimelines","report","a","b","timeA","timeB","outputDir","dirname","mergedReportDir","mergedTestsDir","mkdirSync","seenTestIds","mergedIndex","sr","srcTestsDir","testFiles","readdirSync","f","srcArtifacts","entry","writeFileSync","allSummaries","recalculateSummaryFromList","startedAt","earliest","completedAt","latest","totalDuration","merged","randomUUID","summaries","timelineLength","total","passed","failed","flaky","skipped","timedout","totalApiCalls","totalAssertions","passedAssertions","failedAssertions","totalNavigations","s"],"mappings":"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","file":"merge.cjs","sourcesContent":["/**\n * @testrelic/playwright-analytics/merge\n *\n * Merges multiple shard report files into a single unified timeline.\n * Tree-shakeable: separate entry point from the main reporter.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, readdirSync, cpSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport type {\n TestRunReport,\n MergeOptions,\n Summary,\n TimelineEntry,\n TimelineStep,\n TestIndexEntry,\n} from '@testrelic/core';\nimport { isValidTestRunReport, createError, ErrorCode } from '@testrelic/core';\n\n/**\n * Check if a path is a streaming report directory (contains index.json).\n */\nfunction isStreamingDir(path: string): boolean {\n try {\n const stat = statSync(path);\n return stat.isDirectory() && existsSync(join(path, 'index.json'));\n } catch {\n return false;\n }\n}\n\n/**\n * Load a streaming report directory as a TestRunReport with summary data.\n */\nfunction loadStreamingReport(dir: string): { summary: Summary; index: TestIndexEntry[]; dir: string } {\n const summaryPath = join(dir, 'summary.json');\n const indexPath = join(dir, 'index.json');\n\n let summary: Summary;\n try {\n summary = JSON.parse(readFileSync(summaryPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read summary.json from: ${dir}`, err);\n }\n\n let index: TestIndexEntry[];\n try {\n index = JSON.parse(readFileSync(indexPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read index.json from: ${dir}`, err);\n }\n\n return { summary, index, dir };\n}\n\n/**\n * Copy a shard's file or directory into the merged report, unless it is already\n * there.\n *\n * `-o` may point into a directory that is itself one of the inputs — merging\n * shard 2 into the tree shard 1 already wrote is a reasonable thing to ask for,\n * and Node's `cpSync` rejects identical src and dest outright\n * (`ERR_FS_CP_EINVAL`). Before the per-test directories were copied at all this\n * simply did nothing for that shard; now it would fail the whole merge.\n */\nfunction copyInto(src: string, dest: string, recursive: boolean): void {\n if (resolve(src) === resolve(dest)) return;\n cpSync(src, dest, recursive ? { recursive: true } : undefined);\n}\n\nexport async function mergeReports(\n files: string[],\n options: MergeOptions,\n): Promise<TestRunReport> {\n const reports: TestRunReport[] = [];\n const streamingReports: Array<{ summary: Summary; index: TestIndexEntry[]; dir: string }> = [];\n\n for (const file of files) {\n // Check if this is a streaming report directory\n if (isStreamingDir(file)) {\n streamingReports.push(loadStreamingReport(file));\n continue;\n }\n\n let raw: string;\n try {\n raw = readFileSync(file, 'utf-8');\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_READ_FAILED,\n `Failed to read file: ${file}`,\n err,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid JSON in file: ${file}`,\n err,\n );\n }\n\n if (!isValidTestRunReport(parsed)) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid report schema in file: ${file}`,\n );\n }\n\n reports.push(parsed);\n }\n\n // Collect shard IDs\n const shardRunIds = reports.map((r) => r.testRunId);\n\n // Merge timelines chronologically\n const allTimelines: (TimelineEntry | TimelineStep)[] = [];\n for (const report of reports) {\n allTimelines.push(...report.timeline);\n }\n allTimelines.sort((a, b) => {\n const timeA = 'visitedAt' in a ? a.visitedAt : a.timestamp;\n const timeB = 'visitedAt' in b ? b.visitedAt : b.timestamp;\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Merge streaming report data: copy test detail files and merge indexes\n if (streamingReports.length > 0) {\n const outputDir = dirname(options.output);\n const mergedReportDir = join(outputDir, '.testrelic-report');\n const mergedTestsDir = join(mergedReportDir, 'tests');\n mkdirSync(mergedTestsDir, { recursive: true });\n\n const seenTestIds = new Set<string>();\n const mergedIndex: TestIndexEntry[] = [];\n\n for (const sr of streamingReports) {\n // Copy test detail files. The v3 layout keeps each test in its own\n // `tests/<id>/` directory (meta.json, network.jsonl, ...), and only\n // flat `*.json` files were ever copied — so the merged index claimed\n // comparisons that no meta.json could back, and `tr visual` refuses a\n // report whose counts it cannot verify. Directories are copied whole;\n // flat files are kept for the older per-test layout.\n const srcTestsDir = join(sr.dir, 'tests');\n if (existsSync(srcTestsDir)) {\n const testFiles = readdirSync(srcTestsDir, { withFileTypes: true });\n for (const f of testFiles) {\n if (f.isDirectory()) {\n copyInto(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), true);\n } else if (f.name.endsWith('.json')) {\n copyInto(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), false);\n }\n }\n }\n\n // The images those meta.json files point at — `artifacts/<run>/<test>/…`\n // — are written BESIDE `.testrelic-report`, not inside it, so a merged\n // report that copied only the JSON listed comparisons whose pictures\n // were left behind on the shard. Shards write distinct run folders, so\n // the trees merge without overwriting.\n const srcArtifacts = join(dirname(sr.dir), 'artifacts');\n if (existsSync(srcArtifacts)) {\n copyInto(srcArtifacts, join(outputDir, 'artifacts'), true);\n }\n\n // Merge index entries (dedup by id)\n for (const entry of sr.index) {\n if (!seenTestIds.has(entry.id)) {\n seenTestIds.add(entry.id);\n mergedIndex.push(entry);\n }\n }\n }\n\n // Write merged index and summary\n writeFileSync(join(mergedReportDir, 'index.json'), JSON.stringify(mergedIndex), 'utf-8');\n }\n\n // Recalculate summary across all shards (including streaming)\n const allSummaries = [\n ...reports.map((r) => r.summary),\n ...streamingReports.map((sr) => sr.summary),\n ];\n const summary = recalculateSummaryFromList(allSummaries, allTimelines.length);\n\n // Compute timing\n const startedAt = reports.reduce(\n (earliest, r) => (r.startedAt < earliest ? r.startedAt : earliest),\n reports[0]?.startedAt ?? new Date().toISOString(),\n );\n const completedAt = reports.reduce(\n (latest, r) => (r.completedAt > latest ? r.completedAt : latest),\n reports[0]?.completedAt ?? new Date().toISOString(),\n );\n const totalDuration = new Date(completedAt).getTime() - new Date(startedAt).getTime();\n\n const merged: TestRunReport = {\n schemaVersion: reports[0]?.schemaVersion ?? '1.0.0',\n testRunId: options.testRunId ?? randomUUID(),\n startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: reports.find((r) => r.ci !== null)?.ci ?? null,\n metadata: reports.find((r) => r.metadata !== null)?.metadata ?? null,\n timeline: allTimelines,\n shardRunIds,\n };\n\n // Write to disk\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, JSON.stringify(merged, null, 2), 'utf-8');\n\n return merged;\n}\n\nfunction recalculateSummaryFromList(summaries: Summary[], timelineLength: number): 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 totalApiCalls = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n let totalNavigations = 0;\n\n for (const s of summaries) {\n total += s.total;\n passed += s.passed;\n failed += s.failed;\n flaky += s.flaky;\n skipped += s.skipped;\n timedout += s.timedout ?? 0;\n totalApiCalls += s.totalApiCalls ?? 0;\n totalAssertions += s.totalAssertions ?? 0;\n passedAssertions += s.passedAssertions ?? 0;\n failedAssertions += s.failedAssertions ?? 0;\n totalNavigations += s.totalNavigations ?? 0;\n }\n\n return {\n total, passed, failed, flaky, skipped, timedout,\n totalApiCalls, uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n apiResponseTime: null,\n totalAssertions, passedAssertions, failedAssertions,\n totalNavigations, uniqueNavigationUrls: 0,\n totalTimelineSteps: timelineLength,\n totalActionSteps: 0,\n actionStepsByCategory: {},\n };\n}\n"]}
package/dist/merge.js CHANGED
@@ -1,2 +1,2 @@
1
- import {randomUUID}from'crypto';import {readFileSync,mkdirSync,existsSync,readdirSync,cpSync,writeFileSync,statSync}from'fs';import {dirname,join}from'path';import {createError,ErrorCode,isValidTestRunReport}from'@testrelic/core';function J(n){try{return statSync(n).isDirectory()&&existsSync(join(n,"index.json"))}catch{return false}}function L(n){let a=join(n,"summary.json"),e=join(n,"index.json"),o;try{o=JSON.parse(readFileSync(a,"utf-8"));}catch(i){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read summary.json from: ${n}`,i)}let c;try{c=JSON.parse(readFileSync(e,"utf-8"));}catch(i){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read index.json from: ${n}`,i)}return {summary:o,index:c,dir:n}}async function P(n,a){let e=[],o=[];for(let t of n){if(J(t)){o.push(L(t));continue}let r;try{r=readFileSync(t,"utf-8");}catch(l){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read file: ${t}`,l)}let m;try{m=JSON.parse(r);}catch(l){throw createError(ErrorCode.MERGE_INVALID_SCHEMA,`Invalid JSON in file: ${t}`,l)}if(!isValidTestRunReport(m))throw createError(ErrorCode.MERGE_INVALID_SCHEMA,`Invalid report schema in file: ${t}`);e.push(m);}let c=e.map(t=>t.testRunId),i=[];for(let t of e)i.push(...t.timeline);if(i.sort((t,r)=>{let m="visitedAt"in t?t.visitedAt:t.timestamp,l="visitedAt"in r?r.visitedAt:r.timestamp;return new Date(m).getTime()-new Date(l).getTime()}),o.length>0){let t=dirname(a.output),r=join(t,".testrelic-report"),m=join(r,"tests");mkdirSync(m,{recursive:true});let l=new Set,F=[];for(let T of o){let x=join(T.dir,"tests");if(existsSync(x)){let y=readdirSync(x,{withFileTypes:true});for(let u of y)u.isDirectory()?cpSync(join(x,u.name),join(m,u.name),{recursive:true}):u.name.endsWith(".json")&&cpSync(join(x,u.name),join(m,u.name));}let N=join(dirname(T.dir),"artifacts");existsSync(N)&&cpSync(N,join(t,"artifacts"),{recursive:true});for(let y of T.index)l.has(y.id)||(l.add(y.id),F.push(y));}writeFileSync(join(r,"index.json"),JSON.stringify(F),"utf-8");}let g=[...e.map(t=>t.summary),...o.map(t=>t.summary)],R=G(g,i.length),d=e.reduce((t,r)=>r.startedAt<t?r.startedAt:t,e[0]?.startedAt??new Date().toISOString()),p=e.reduce((t,r)=>r.completedAt>t?r.completedAt:t,e[0]?.completedAt??new Date().toISOString()),E=new Date(p).getTime()-new Date(d).getTime(),f={schemaVersion:e[0]?.schemaVersion??"1.0.0",testRunId:a.testRunId??randomUUID(),startedAt:d,completedAt:p,totalDuration:E,summary:R,ci:e.find(t=>t.ci!==null)?.ci??null,metadata:e.find(t=>t.metadata!==null)?.metadata??null,timeline:i,shardRunIds:c},h=dirname(a.output);return mkdirSync(h,{recursive:true}),writeFileSync(a.output,JSON.stringify(f,null,2),"utf-8"),f}function G(n,a){let e=0,o=0,c=0,i=0,g=0,R=0,d=0,p=0,E=0,f=0,h=0;for(let t of n)e+=t.total,o+=t.passed,c+=t.failed,i+=t.flaky,g+=t.skipped,R+=t.timedout??0,d+=t.totalApiCalls??0,p+=t.totalAssertions??0,E+=t.passedAssertions??0,f+=t.failedAssertions??0,h+=t.totalNavigations??0;return {total:e,passed:o,failed:c,flaky:i,skipped:g,timedout:R,totalApiCalls:d,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},apiResponseTime:null,totalAssertions:p,passedAssertions:E,failedAssertions:f,totalNavigations:h,uniqueNavigationUrls:0,totalTimelineSteps:a,totalActionSteps:0,actionStepsByCategory:{}}}export{P as mergeReports};//# sourceMappingURL=merge.js.map
1
+ import {randomUUID}from'crypto';import {readFileSync,mkdirSync,existsSync,readdirSync,writeFileSync,statSync,cpSync}from'fs';import {dirname,join,resolve}from'path';import {createError,ErrorCode,isValidTestRunReport}from'@testrelic/core';function G(r){try{return statSync(r).isDirectory()&&existsSync(join(r,"index.json"))}catch{return false}}function V(r){let i=join(r,"summary.json"),e=join(r,"index.json"),a;try{a=JSON.parse(readFileSync(i,"utf-8"));}catch(o){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read summary.json from: ${r}`,o)}let c;try{c=JSON.parse(readFileSync(e,"utf-8"));}catch(o){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read index.json from: ${r}`,o)}return {summary:a,index:c,dir:r}}function D(r,i,e){resolve(r)!==resolve(i)&&cpSync(r,i,e?{recursive:true}:void 0);}async function H(r,i){let e=[],a=[];for(let t of r){if(G(t)){a.push(V(t));continue}let s;try{s=readFileSync(t,"utf-8");}catch(l){throw createError(ErrorCode.MERGE_READ_FAILED,`Failed to read file: ${t}`,l)}let m;try{m=JSON.parse(s);}catch(l){throw createError(ErrorCode.MERGE_INVALID_SCHEMA,`Invalid JSON in file: ${t}`,l)}if(!isValidTestRunReport(m))throw createError(ErrorCode.MERGE_INVALID_SCHEMA,`Invalid report schema in file: ${t}`);e.push(m);}let c=e.map(t=>t.testRunId),o=[];for(let t of e)o.push(...t.timeline);if(o.sort((t,s)=>{let m="visitedAt"in t?t.visitedAt:t.timestamp,l="visitedAt"in s?s.visitedAt:s.timestamp;return new Date(m).getTime()-new Date(l).getTime()}),a.length>0){let t=dirname(i.output),s=join(t,".testrelic-report"),m=join(s,"tests");mkdirSync(m,{recursive:true});let l=new Set,F=[];for(let T of a){let x=join(T.dir,"tests");if(existsSync(x)){let y=readdirSync(x,{withFileTypes:true});for(let u of y)u.isDirectory()?D(join(x,u.name),join(m,u.name),true):u.name.endsWith(".json")&&D(join(x,u.name),join(m,u.name),false);}let N=join(dirname(T.dir),"artifacts");existsSync(N)&&D(N,join(t,"artifacts"),true);for(let y of T.index)l.has(y.id)||(l.add(y.id),F.push(y));}writeFileSync(join(s,"index.json"),JSON.stringify(F),"utf-8");}let g=[...e.map(t=>t.summary),...a.map(t=>t.summary)],R=$(g,o.length),d=e.reduce((t,s)=>s.startedAt<t?s.startedAt:t,e[0]?.startedAt??new Date().toISOString()),p=e.reduce((t,s)=>s.completedAt>t?s.completedAt:t,e[0]?.completedAt??new Date().toISOString()),E=new Date(p).getTime()-new Date(d).getTime(),f={schemaVersion:e[0]?.schemaVersion??"1.0.0",testRunId:i.testRunId??randomUUID(),startedAt:d,completedAt:p,totalDuration:E,summary:R,ci:e.find(t=>t.ci!==null)?.ci??null,metadata:e.find(t=>t.metadata!==null)?.metadata??null,timeline:o,shardRunIds:c},h=dirname(i.output);return mkdirSync(h,{recursive:true}),writeFileSync(i.output,JSON.stringify(f,null,2),"utf-8"),f}function $(r,i){let e=0,a=0,c=0,o=0,g=0,R=0,d=0,p=0,E=0,f=0,h=0;for(let t of r)e+=t.total,a+=t.passed,c+=t.failed,o+=t.flaky,g+=t.skipped,R+=t.timedout??0,d+=t.totalApiCalls??0,p+=t.totalAssertions??0,E+=t.passedAssertions??0,f+=t.failedAssertions??0,h+=t.totalNavigations??0;return {total:e,passed:a,failed:c,flaky:o,skipped:g,timedout:R,totalApiCalls:d,uniqueApiUrls:0,apiCallsByMethod:{},apiCallsByStatusRange:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},apiResponseTime:null,totalAssertions:p,passedAssertions:E,failedAssertions:f,totalNavigations:h,uniqueNavigationUrls:0,totalTimelineSteps:i,totalActionSteps:0,actionStepsByCategory:{}}}export{H as mergeReports};//# sourceMappingURL=merge.js.map
2
2
  //# sourceMappingURL=merge.js.map
package/dist/merge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/merge.ts"],"names":["isStreamingDir","path","statSync","existsSync","join","loadStreamingReport","dir","summaryPath","indexPath","summary","readFileSync","err","createError","ErrorCode","index","mergeReports","files","options","reports","streamingReports","file","raw","parsed","isValidTestRunReport","shardRunIds","r","allTimelines","report","a","b","timeA","timeB","outputDir","dirname","mergedReportDir","mergedTestsDir","mkdirSync","seenTestIds","mergedIndex","sr","srcTestsDir","testFiles","readdirSync","f","cpSync","srcArtifacts","entry","writeFileSync","allSummaries","recalculateSummaryFromList","startedAt","earliest","completedAt","latest","totalDuration","merged","randomUUID","summaries","timelineLength","total","passed","failed","flaky","skipped","timedout","totalApiCalls","totalAssertions","passedAssertions","failedAssertions","totalNavigations","s"],"mappings":"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","file":"merge.js","sourcesContent":["/**\n * @testrelic/playwright-analytics/merge\n *\n * Merges multiple shard report files into a single unified timeline.\n * Tree-shakeable: separate entry point from the main reporter.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, readdirSync, cpSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport type {\n TestRunReport,\n MergeOptions,\n Summary,\n TimelineEntry,\n TimelineStep,\n TestIndexEntry,\n} from '@testrelic/core';\nimport { isValidTestRunReport, createError, ErrorCode } from '@testrelic/core';\n\n/**\n * Check if a path is a streaming report directory (contains index.json).\n */\nfunction isStreamingDir(path: string): boolean {\n try {\n const stat = statSync(path);\n return stat.isDirectory() && existsSync(join(path, 'index.json'));\n } catch {\n return false;\n }\n}\n\n/**\n * Load a streaming report directory as a TestRunReport with summary data.\n */\nfunction loadStreamingReport(dir: string): { summary: Summary; index: TestIndexEntry[]; dir: string } {\n const summaryPath = join(dir, 'summary.json');\n const indexPath = join(dir, 'index.json');\n\n let summary: Summary;\n try {\n summary = JSON.parse(readFileSync(summaryPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read summary.json from: ${dir}`, err);\n }\n\n let index: TestIndexEntry[];\n try {\n index = JSON.parse(readFileSync(indexPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read index.json from: ${dir}`, err);\n }\n\n return { summary, index, dir };\n}\n\nexport async function mergeReports(\n files: string[],\n options: MergeOptions,\n): Promise<TestRunReport> {\n const reports: TestRunReport[] = [];\n const streamingReports: Array<{ summary: Summary; index: TestIndexEntry[]; dir: string }> = [];\n\n for (const file of files) {\n // Check if this is a streaming report directory\n if (isStreamingDir(file)) {\n streamingReports.push(loadStreamingReport(file));\n continue;\n }\n\n let raw: string;\n try {\n raw = readFileSync(file, 'utf-8');\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_READ_FAILED,\n `Failed to read file: ${file}`,\n err,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid JSON in file: ${file}`,\n err,\n );\n }\n\n if (!isValidTestRunReport(parsed)) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid report schema in file: ${file}`,\n );\n }\n\n reports.push(parsed);\n }\n\n // Collect shard IDs\n const shardRunIds = reports.map((r) => r.testRunId);\n\n // Merge timelines chronologically\n const allTimelines: (TimelineEntry | TimelineStep)[] = [];\n for (const report of reports) {\n allTimelines.push(...report.timeline);\n }\n allTimelines.sort((a, b) => {\n const timeA = 'visitedAt' in a ? a.visitedAt : a.timestamp;\n const timeB = 'visitedAt' in b ? b.visitedAt : b.timestamp;\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Merge streaming report data: copy test detail files and merge indexes\n if (streamingReports.length > 0) {\n const outputDir = dirname(options.output);\n const mergedReportDir = join(outputDir, '.testrelic-report');\n const mergedTestsDir = join(mergedReportDir, 'tests');\n mkdirSync(mergedTestsDir, { recursive: true });\n\n const seenTestIds = new Set<string>();\n const mergedIndex: TestIndexEntry[] = [];\n\n for (const sr of streamingReports) {\n // Copy test detail files. The v3 layout keeps each test in its own\n // `tests/<id>/` directory (meta.json, network.jsonl, ...), and only\n // flat `*.json` files were ever copied — so the merged index claimed\n // comparisons that no meta.json could back, and `tr visual` refuses a\n // report whose counts it cannot verify. Directories are copied whole;\n // flat files are kept for the older per-test layout.\n const srcTestsDir = join(sr.dir, 'tests');\n if (existsSync(srcTestsDir)) {\n const testFiles = readdirSync(srcTestsDir, { withFileTypes: true });\n for (const f of testFiles) {\n if (f.isDirectory()) {\n cpSync(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), { recursive: true });\n } else if (f.name.endsWith('.json')) {\n cpSync(join(srcTestsDir, f.name), join(mergedTestsDir, f.name));\n }\n }\n }\n\n // The images those meta.json files point at — `artifacts/<run>/<test>/…`\n // — are written BESIDE `.testrelic-report`, not inside it, so a merged\n // report that copied only the JSON listed comparisons whose pictures\n // were left behind on the shard. Shards write distinct run folders, so\n // the trees merge without overwriting.\n const srcArtifacts = join(dirname(sr.dir), 'artifacts');\n if (existsSync(srcArtifacts)) {\n cpSync(srcArtifacts, join(outputDir, 'artifacts'), { recursive: true });\n }\n\n // Merge index entries (dedup by id)\n for (const entry of sr.index) {\n if (!seenTestIds.has(entry.id)) {\n seenTestIds.add(entry.id);\n mergedIndex.push(entry);\n }\n }\n }\n\n // Write merged index and summary\n writeFileSync(join(mergedReportDir, 'index.json'), JSON.stringify(mergedIndex), 'utf-8');\n }\n\n // Recalculate summary across all shards (including streaming)\n const allSummaries = [\n ...reports.map((r) => r.summary),\n ...streamingReports.map((sr) => sr.summary),\n ];\n const summary = recalculateSummaryFromList(allSummaries, allTimelines.length);\n\n // Compute timing\n const startedAt = reports.reduce(\n (earliest, r) => (r.startedAt < earliest ? r.startedAt : earliest),\n reports[0]?.startedAt ?? new Date().toISOString(),\n );\n const completedAt = reports.reduce(\n (latest, r) => (r.completedAt > latest ? r.completedAt : latest),\n reports[0]?.completedAt ?? new Date().toISOString(),\n );\n const totalDuration = new Date(completedAt).getTime() - new Date(startedAt).getTime();\n\n const merged: TestRunReport = {\n schemaVersion: reports[0]?.schemaVersion ?? '1.0.0',\n testRunId: options.testRunId ?? randomUUID(),\n startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: reports.find((r) => r.ci !== null)?.ci ?? null,\n metadata: reports.find((r) => r.metadata !== null)?.metadata ?? null,\n timeline: allTimelines,\n shardRunIds,\n };\n\n // Write to disk\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, JSON.stringify(merged, null, 2), 'utf-8');\n\n return merged;\n}\n\nfunction recalculateSummaryFromList(summaries: Summary[], timelineLength: number): 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 totalApiCalls = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n let totalNavigations = 0;\n\n for (const s of summaries) {\n total += s.total;\n passed += s.passed;\n failed += s.failed;\n flaky += s.flaky;\n skipped += s.skipped;\n timedout += s.timedout ?? 0;\n totalApiCalls += s.totalApiCalls ?? 0;\n totalAssertions += s.totalAssertions ?? 0;\n passedAssertions += s.passedAssertions ?? 0;\n failedAssertions += s.failedAssertions ?? 0;\n totalNavigations += s.totalNavigations ?? 0;\n }\n\n return {\n total, passed, failed, flaky, skipped, timedout,\n totalApiCalls, uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n apiResponseTime: null,\n totalAssertions, passedAssertions, failedAssertions,\n totalNavigations, uniqueNavigationUrls: 0,\n totalTimelineSteps: timelineLength,\n totalActionSteps: 0,\n actionStepsByCategory: {},\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/merge.ts"],"names":["isStreamingDir","path","statSync","existsSync","join","loadStreamingReport","dir","summaryPath","indexPath","summary","readFileSync","err","createError","ErrorCode","index","copyInto","src","dest","recursive","resolve","cpSync","mergeReports","files","options","reports","streamingReports","file","raw","parsed","isValidTestRunReport","shardRunIds","r","allTimelines","report","a","b","timeA","timeB","outputDir","dirname","mergedReportDir","mergedTestsDir","mkdirSync","seenTestIds","mergedIndex","sr","srcTestsDir","testFiles","readdirSync","f","srcArtifacts","entry","writeFileSync","allSummaries","recalculateSummaryFromList","startedAt","earliest","completedAt","latest","totalDuration","merged","randomUUID","summaries","timelineLength","total","passed","failed","flaky","skipped","timedout","totalApiCalls","totalAssertions","passedAssertions","failedAssertions","totalNavigations","s"],"mappings":"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","file":"merge.js","sourcesContent":["/**\n * @testrelic/playwright-analytics/merge\n *\n * Merges multiple shard report files into a single unified timeline.\n * Tree-shakeable: separate entry point from the main reporter.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, readdirSync, cpSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport type {\n TestRunReport,\n MergeOptions,\n Summary,\n TimelineEntry,\n TimelineStep,\n TestIndexEntry,\n} from '@testrelic/core';\nimport { isValidTestRunReport, createError, ErrorCode } from '@testrelic/core';\n\n/**\n * Check if a path is a streaming report directory (contains index.json).\n */\nfunction isStreamingDir(path: string): boolean {\n try {\n const stat = statSync(path);\n return stat.isDirectory() && existsSync(join(path, 'index.json'));\n } catch {\n return false;\n }\n}\n\n/**\n * Load a streaming report directory as a TestRunReport with summary data.\n */\nfunction loadStreamingReport(dir: string): { summary: Summary; index: TestIndexEntry[]; dir: string } {\n const summaryPath = join(dir, 'summary.json');\n const indexPath = join(dir, 'index.json');\n\n let summary: Summary;\n try {\n summary = JSON.parse(readFileSync(summaryPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read summary.json from: ${dir}`, err);\n }\n\n let index: TestIndexEntry[];\n try {\n index = JSON.parse(readFileSync(indexPath, 'utf-8'));\n } catch (err) {\n throw createError(ErrorCode.MERGE_READ_FAILED, `Failed to read index.json from: ${dir}`, err);\n }\n\n return { summary, index, dir };\n}\n\n/**\n * Copy a shard's file or directory into the merged report, unless it is already\n * there.\n *\n * `-o` may point into a directory that is itself one of the inputs — merging\n * shard 2 into the tree shard 1 already wrote is a reasonable thing to ask for,\n * and Node's `cpSync` rejects identical src and dest outright\n * (`ERR_FS_CP_EINVAL`). Before the per-test directories were copied at all this\n * simply did nothing for that shard; now it would fail the whole merge.\n */\nfunction copyInto(src: string, dest: string, recursive: boolean): void {\n if (resolve(src) === resolve(dest)) return;\n cpSync(src, dest, recursive ? { recursive: true } : undefined);\n}\n\nexport async function mergeReports(\n files: string[],\n options: MergeOptions,\n): Promise<TestRunReport> {\n const reports: TestRunReport[] = [];\n const streamingReports: Array<{ summary: Summary; index: TestIndexEntry[]; dir: string }> = [];\n\n for (const file of files) {\n // Check if this is a streaming report directory\n if (isStreamingDir(file)) {\n streamingReports.push(loadStreamingReport(file));\n continue;\n }\n\n let raw: string;\n try {\n raw = readFileSync(file, 'utf-8');\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_READ_FAILED,\n `Failed to read file: ${file}`,\n err,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid JSON in file: ${file}`,\n err,\n );\n }\n\n if (!isValidTestRunReport(parsed)) {\n throw createError(\n ErrorCode.MERGE_INVALID_SCHEMA,\n `Invalid report schema in file: ${file}`,\n );\n }\n\n reports.push(parsed);\n }\n\n // Collect shard IDs\n const shardRunIds = reports.map((r) => r.testRunId);\n\n // Merge timelines chronologically\n const allTimelines: (TimelineEntry | TimelineStep)[] = [];\n for (const report of reports) {\n allTimelines.push(...report.timeline);\n }\n allTimelines.sort((a, b) => {\n const timeA = 'visitedAt' in a ? a.visitedAt : a.timestamp;\n const timeB = 'visitedAt' in b ? b.visitedAt : b.timestamp;\n return new Date(timeA).getTime() - new Date(timeB).getTime();\n });\n\n // Merge streaming report data: copy test detail files and merge indexes\n if (streamingReports.length > 0) {\n const outputDir = dirname(options.output);\n const mergedReportDir = join(outputDir, '.testrelic-report');\n const mergedTestsDir = join(mergedReportDir, 'tests');\n mkdirSync(mergedTestsDir, { recursive: true });\n\n const seenTestIds = new Set<string>();\n const mergedIndex: TestIndexEntry[] = [];\n\n for (const sr of streamingReports) {\n // Copy test detail files. The v3 layout keeps each test in its own\n // `tests/<id>/` directory (meta.json, network.jsonl, ...), and only\n // flat `*.json` files were ever copied — so the merged index claimed\n // comparisons that no meta.json could back, and `tr visual` refuses a\n // report whose counts it cannot verify. Directories are copied whole;\n // flat files are kept for the older per-test layout.\n const srcTestsDir = join(sr.dir, 'tests');\n if (existsSync(srcTestsDir)) {\n const testFiles = readdirSync(srcTestsDir, { withFileTypes: true });\n for (const f of testFiles) {\n if (f.isDirectory()) {\n copyInto(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), true);\n } else if (f.name.endsWith('.json')) {\n copyInto(join(srcTestsDir, f.name), join(mergedTestsDir, f.name), false);\n }\n }\n }\n\n // The images those meta.json files point at — `artifacts/<run>/<test>/…`\n // — are written BESIDE `.testrelic-report`, not inside it, so a merged\n // report that copied only the JSON listed comparisons whose pictures\n // were left behind on the shard. Shards write distinct run folders, so\n // the trees merge without overwriting.\n const srcArtifacts = join(dirname(sr.dir), 'artifacts');\n if (existsSync(srcArtifacts)) {\n copyInto(srcArtifacts, join(outputDir, 'artifacts'), true);\n }\n\n // Merge index entries (dedup by id)\n for (const entry of sr.index) {\n if (!seenTestIds.has(entry.id)) {\n seenTestIds.add(entry.id);\n mergedIndex.push(entry);\n }\n }\n }\n\n // Write merged index and summary\n writeFileSync(join(mergedReportDir, 'index.json'), JSON.stringify(mergedIndex), 'utf-8');\n }\n\n // Recalculate summary across all shards (including streaming)\n const allSummaries = [\n ...reports.map((r) => r.summary),\n ...streamingReports.map((sr) => sr.summary),\n ];\n const summary = recalculateSummaryFromList(allSummaries, allTimelines.length);\n\n // Compute timing\n const startedAt = reports.reduce(\n (earliest, r) => (r.startedAt < earliest ? r.startedAt : earliest),\n reports[0]?.startedAt ?? new Date().toISOString(),\n );\n const completedAt = reports.reduce(\n (latest, r) => (r.completedAt > latest ? r.completedAt : latest),\n reports[0]?.completedAt ?? new Date().toISOString(),\n );\n const totalDuration = new Date(completedAt).getTime() - new Date(startedAt).getTime();\n\n const merged: TestRunReport = {\n schemaVersion: reports[0]?.schemaVersion ?? '1.0.0',\n testRunId: options.testRunId ?? randomUUID(),\n startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: reports.find((r) => r.ci !== null)?.ci ?? null,\n metadata: reports.find((r) => r.metadata !== null)?.metadata ?? null,\n timeline: allTimelines,\n shardRunIds,\n };\n\n // Write to disk\n const dir = dirname(options.output);\n mkdirSync(dir, { recursive: true });\n writeFileSync(options.output, JSON.stringify(merged, null, 2), 'utf-8');\n\n return merged;\n}\n\nfunction recalculateSummaryFromList(summaries: Summary[], timelineLength: number): 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 totalApiCalls = 0;\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n let totalNavigations = 0;\n\n for (const s of summaries) {\n total += s.total;\n passed += s.passed;\n failed += s.failed;\n flaky += s.flaky;\n skipped += s.skipped;\n timedout += s.timedout ?? 0;\n totalApiCalls += s.totalApiCalls ?? 0;\n totalAssertions += s.totalAssertions ?? 0;\n passedAssertions += s.passedAssertions ?? 0;\n failedAssertions += s.failedAssertions ?? 0;\n totalNavigations += s.totalNavigations ?? 0;\n }\n\n return {\n total, passed, failed, flaky, skipped, timedout,\n totalApiCalls, uniqueApiUrls: 0,\n apiCallsByMethod: {},\n apiCallsByStatusRange: { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 },\n apiResponseTime: null,\n totalAssertions, passedAssertions, failedAssertions,\n totalNavigations, uniqueNavigationUrls: 0,\n totalTimelineSteps: timelineLength,\n totalActionSteps: 0,\n actionStepsByCategory: {},\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testrelic/playwright-analytics",
3
- "version": "2.16.1-next.142",
3
+ "version": "2.16.2-next.145",
4
4
  "description": "Playwright test analytics reporter with E2E navigation tracking, API call capture, network stats, failure diagnostics, and interactive HTML reports",
5
5
  "keywords": [
6
6
  "playwright",
@@ -114,7 +114,7 @@
114
114
  "@playwright/test": ">=1.35.0"
115
115
  },
116
116
  "dependencies": {
117
- "@testrelic/core": "2.16.1-next.142"
117
+ "@testrelic/core": "2.16.2-next.145"
118
118
  },
119
119
  "devDependencies": {
120
120
  "@playwright/test": "^1.35.0",