@testrelic/playwright-analytics 2.3.10 → 2.3.12

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 CHANGED
@@ -359,6 +359,23 @@ Each navigation in the timeline includes detailed network statistics:
359
359
 
360
360
  Disable with `includeNetworkStats: false`.
361
361
 
362
+ ## Viewing Reports
363
+
364
+ For **small test suites** (< 50 tests), the report is a self-contained HTML file — just open it in your browser.
365
+
366
+ For **large test suites** (50+ tests), TestRelic uses streaming mode to avoid memory issues. Test details are stored as separate files and loaded on demand via a local server. After tests complete, the terminal shows:
367
+
368
+ ```
369
+ [testrelic] View report: npx testrelic serve ./test-results/.testrelic-report
370
+ ```
371
+
372
+ Run that command and open `http://127.0.0.1:9323` in your browser. The server auto-starts after test runs when not in CI, but if you need to restart it later:
373
+
374
+ ```bash
375
+ npx testrelic serve ./test-results/.testrelic-report
376
+ npx testrelic serve ./test-results/.testrelic-report --port 9400 # custom port
377
+ ```
378
+
362
379
  ## Merging Shard Reports
363
380
 
364
381
  When running Playwright with sharding, each shard produces its own report. Merge them with the CLI:
@@ -415,6 +432,31 @@ test('CRUD operations', { tag: ['@api'] }, async ({ request }) => {
415
432
  });
416
433
  ```
417
434
 
435
+ ### Custom Page Fixtures (authenticated pages, etc.)
436
+
437
+ If your tests create pages manually via `browser.newContext()` / `context.newPage()` (e.g., for auth with `storageState`), the built-in `page` fixture is bypassed and **no network/console/navigation data is captured**. Use `trackPage()` to enable tracking on any manually-created page:
438
+
439
+ ```typescript
440
+ import { test as base, expect, trackPage } from '@testrelic/playwright-analytics/fixture';
441
+
442
+ type MyFixtures = {
443
+ authenticatedPage: Page;
444
+ };
445
+
446
+ export const test = base.extend<MyFixtures>({
447
+ authenticatedPage: async ({ browser }, use, testInfo) => {
448
+ const ctx = await browser.newContext({ storageState: 'auth.json' });
449
+ const page = await ctx.newPage();
450
+ const cleanup = await trackPage(page, testInfo); // ← enables tracking
451
+ await use(page);
452
+ await cleanup(); // ← flushes data to report
453
+ await ctx.close();
454
+ },
455
+ });
456
+ ```
457
+
458
+ > **Important:** Without `trackPage()`, custom page fixtures will only show screenshots, videos, and action steps — but no network requests, console logs, or navigation timeline.
459
+
418
460
  ### Unified Testing (Browser + API)
419
461
 
420
462
  Use the unified fixture which provides **both** `page` and `request` — the TestRelic report shows navigation timeline AND API call details for the same test. This is ideal for:
package/dist/cli.cjs CHANGED
@@ -361,7 +361,7 @@ var init_report_server_routes = __esm({
361
361
  init_jsonl_stream();
362
362
  SAFE_ID_PATTERN = /^[a-f0-9]{12}$/;
363
363
  TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/;
364
- MAX_PAGE_SIZE = 500;
364
+ MAX_PAGE_SIZE = 5e3;
365
365
  MIME_TYPES = {
366
366
  ".png": "image/png",
367
367
  ".jpg": "image/jpeg",
package/dist/index.cjs CHANGED
@@ -947,7 +947,7 @@ body{
947
947
  /* Load first page of network requests */
948
948
  if(merged._networkCount>0){
949
949
  pending++;
950
- fetchData(testId,'network',1,100,function(err,result){
950
+ fetchData(testId,'network',1,5000,function(err,result){
951
951
  if(!err&&result){
952
952
  merged.networkRequests=result.items;
953
953
  merged._networkTotal=result.total;
@@ -960,7 +960,7 @@ body{
960
960
  /* Load first page of console logs */
961
961
  if(merged._consoleCount>0){
962
962
  pending++;
963
- fetchData(testId,'console',1,200,function(err,result){
963
+ fetchData(testId,'console',1,5000,function(err,result){
964
964
  if(!err&&result){
965
965
  merged.consoleLogs=result.items;
966
966
  merged._consoleTotal=result.total;
@@ -2478,7 +2478,7 @@ ${o?`<script id="artifact-manifest-data" type="application/json">${o}</script>`:
2478
2478
  </body>
2479
2479
  </html>`}function Le(t){try{let e=process.platform,r;e==="darwin"?r=`open "${t}"`:e==="win32"?r=`start "" "${t}"`:r=`xdg-open "${t}"`,child_process.exec(r,n=>{n&&process.stderr.write(`[testrelic] Failed to open browser: ${n.message}
2480
2480
  `);});}catch{}}var yt=path.join(os$1.tmpdir(),"testrelic-data"),N=class{constructor(e){this.count=0;this.closed=false;fs$1.mkdirSync(yt,{recursive:true}),this.filePath=path.join(yt,`${e}-${crypto.randomUUID().substring(0,8)}.jsonl`),this.fd=fs$1.openSync(this.filePath,"w");}append(e){if(this.closed)return;let r=JSON.stringify(e)+`
2481
- `;fs$1.writeSync(this.fd,r),this.count++;}getPath(){return this.filePath}getCount(){return this.count}close(){if(!this.closed){this.closed=true;try{fs$1.closeSync(this.fd);}catch{}}}cleanup(){try{fs$1.unlinkSync(this.filePath);}catch{}}};async function wt(t,e,r,n){let s=(e-1)*r,a=[],o=0,i=readline.createInterface({input:fs$1.createReadStream(t,{encoding:"utf-8"}),crlfDelay:1/0});for await(let c of i)if(c.length!==0){if(o>=s&&a.length<r)try{a.push(JSON.parse(c));}catch{}if(o++,a.length>=r&&n!==void 0)break}let l=n??o,d=Math.max(1,Math.ceil(l/r));return {items:a,total:l,page:e,pageSize:r,totalPages:d}}var Ct=/^[a-f0-9]{12}$/,Ne=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,Tt=500;function x(t,e,r){t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(r));}function St(t){t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, DELETE, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type");}function re(t){try{return fs$1.existsSync(t)?JSON.parse(fs$1.readFileSync(t,"utf-8")):null}catch{return null}}function ne(t){let e=0;try{let r=fs$1.readdirSync(t,{withFileTypes:!0});for(let n of r){let s=path.join(t,n.name);n.isFile()?e+=fs$1.statSync(s).size:n.isDirectory()&&(e+=ne(s));}}catch{}return e}function Rt(t,e,r,n){let s=re(path.join(r,"index.json"));x(e,200,{status:"ok",reportMode:"streaming",testCount:s?.length??0,uptime:Math.floor((Date.now()-n)/1e3)});}function At(t,e,r){let n=re(path.join(r,"summary.json"));if(!n){x(e,404,{error:"Summary not found"});return}x(e,200,n);}function _t(t,e,r){let n=re(path.join(r,"index.json"));if(!n){x(e,404,{error:"Test index not found"});return}let a=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,o=Math.max(1,parseInt(a.get("page")??"1",10)||1),i=Math.min(Tt,Math.max(1,parseInt(a.get("pageSize")??"100",10)||100)),l=a.get("status")?.split(",").filter(Boolean)??null,d=a.get("file")??null,c=a.get("search")?.toLowerCase()??null,p=a.get("tag")?.split(",").filter(Boolean)??null,f=a.get("sort")??"file",u=a.get("order")==="desc"?-1:1,g=n;l&&l.length>0&&(g=g.filter(w=>l.includes(w.status))),d&&(g=g.filter(w=>w.filePath===d||w.filePath.startsWith(d+"/"))),c&&(g=g.filter(w=>w.title.toLowerCase().includes(c)||w.filePath.toLowerCase().includes(c))),p&&p.length>0&&(g=g.filter(w=>p.some(y=>w.tags.includes(y)))),g=[...g].sort((w,y)=>{let T=0;switch(f){case "duration":T=w.duration-y.duration;break;case "status":T=w.status.localeCompare(y.status);break;case "title":T=w.title.localeCompare(y.title);break;default:T=w.filePath.localeCompare(y.filePath);break}return T*u});let h=g.length,m=Math.max(1,Math.ceil(h/i)),b=(o-1)*i,C=g.slice(b,b+i);x(e,200,{tests:C,pagination:{page:o,pageSize:i,totalItems:h,totalPages:m},filters:{status:l,file:d,search:c,tag:p}});}function It(t,e,r,n){if(!Ct.test(n)){x(e,400,{error:"Invalid test ID format"});return}let s=path.join(r,"tests",n,"meta.json"),a=path.join(r,"tests",`${n}.json`),o=fs$1.existsSync(s)?s:fs$1.existsSync(a)?a:null;if(!o){x(e,404,{error:`Test not found: ${n}`});return}try{let i=fs$1.readFileSync(o,"utf-8");e.writeHead(200,{"Content-Type":"application/json"}),e.end(i);}catch(i){x(e,500,{error:i instanceof Error?i.message:String(i)});}}async function Lt(t,e,r,n,s){if(!Ct.test(n)){x(e,400,{error:"Invalid test ID format"});return}let o=path.join(r,"tests",n,{network:"network.jsonl",console:"console.jsonl","api-calls":"api-calls.jsonl"}[s]);if(!fs$1.existsSync(o)){x(e,200,{items:[],total:0,page:1,pageSize:50,totalPages:0});return}try{let l=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,d=Math.max(1,parseInt(l.get("page")??"1",10)||1),c=Math.min(Tt,Math.max(1,parseInt(l.get("pageSize")??"50",10)||50)),p,f=path.join(r,"tests",n,"meta.json");if(fs$1.existsSync(f))try{let g=JSON.parse(fs$1.readFileSync(f,"utf-8"));switch(s){case "network":p=g.networkRequestsCount;break;case "console":p=g.consoleLogsCount;break;case "api-calls":p=g.apiCallsCount;break}}catch{}let u=await wt(o,d,c,p);x(e,200,u);}catch(i){x(e,500,{error:i instanceof Error?i.message:String(i)});}}function Nt(t,e,r){let n=re(path.join(r,"index.json"));if(!n){x(e,404,{error:"Test index not found"});return}let s=new Map;for(let o of n){if(o.isRetry)continue;let i=s.get(o.filePath);switch(i||(i={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0},s.set(o.filePath,i)),i.total++,o.status){case "passed":i.passed++;break;case "failed":i.failed++;break;case "flaky":i.flaky++;break;case "skipped":i.skipped++;break;case "timedout":i.timedOut++;break}}let a=Array.from(s.entries()).map(([o,i])=>({filePath:o,...i})).sort((o,i)=>o.filePath.localeCompare(i.filePath));x(e,200,{files:a});}function Mt(t,e,r){if(!fs$1.existsSync(r)){x(e,200,{runs:[],totalSizeBytes:0});return}try{let n=[],s=0,a=fs$1.readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!Ne.test(o.name))continue;let i=path.join(r,o.name),l=ne(i),d=fs$1.readdirSync(i,{withFileTypes:!0}).filter(c=>c.isDirectory());n.push({folderName:o.name,totalSizeBytes:l,testCount:d.length}),s+=l;}n.sort((o,i)=>i.folderName.localeCompare(o.folderName)),x(e,200,{runs:n,totalSizeBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Et(t,e,r){try{let n=0,s=0;if(fs$1.existsSync(r)){let a=fs$1.readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!Ne.test(o.name))continue;let i=path.join(r,o.name),l=ne(i);fs$1.rmSync(i,{recursive:!0,force:!0}),s+=l,n++;}}x(e,200,{deletedCount:n,freedBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Pt(t,e,r,n){if(!Ne.test(n)){x(e,400,{error:"Invalid folder name"});return}let s=path.join(r,n);try{if(!fs$1.statSync(s).isDirectory()){x(e,404,{error:"Not found"});return}}catch{x(e,404,{error:"Not found"});return}try{let a=ne(s);fs$1.rmSync(s,{recursive:!0,force:!0}),x(e,200,{deleted:n,freedBytes:a});}catch(a){x(e,500,{error:a instanceof Error?a.message:String(a)});}}function Ft(t,e,r){x(e,200,{status:"shutting_down"}),r.close();}function Me(t,e,r){if(!fs$1.existsSync(r)){x(e,404,{error:"File not found"});return}try{let n=fs$1.readFileSync(r),s=path.extname(r).toLowerCase(),a=wn[s]??"application/octet-stream";e.writeHead(200,{"Content-Type":a}),e.end(n);}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}var wn={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".webm":"video/webm",".mp4":"video/mp4",".json":"application/json",".html":"text/html",".css":"text/css",".js":"text/javascript",".svg":"image/svg+xml"};var Tn=9323,Sn=10,Rn=1800*1e3;function G(t,e){return new Promise((r,n)=>{let s=e?.port??Tn,a=Date.now(),o,i=0,l=fs$1.existsSync(path.join(t,"artifacts"))?path.join(t,"artifacts"):fs$1.existsSync(path.join(t,"..","artifacts"))?path.join(t,"..","artifacts"):path.join(t,"artifacts");function d(){clearTimeout(o),o=setTimeout(()=>{p.close();},Rn);}let c=e?.htmlPath??null;if(!c){let u=path.dirname(t);try{let g=fs$1.readdirSync(u).find(h=>h.endsWith(".html"));g&&(c=path.join(u,g));}catch{}}let p=http.createServer((u,g)=>{if(d(),St(g),u.method==="OPTIONS"){g.writeHead(204),g.end();return}let h;try{h=new URL(u.url??"/",`http://${u.headers.host??"localhost"}`).pathname;}catch{x(g,400,{error:"Invalid URL"});return}if(u.method==="GET"&&(h==="/"||h==="/index.html")){if(c&&fs$1.existsSync(c)){Me(u,g,c);return}x(g,404,{error:"HTML report not found"});return}if(u.method==="GET"&&h==="/api/health"){Rt(u,g,t,a);return}if(u.method==="GET"&&h==="/api/summary"){At(u,g,t);return}if(u.method==="GET"&&h==="/api/tests"){_t(u,g,t);return}if(u.method==="GET"&&h==="/api/files"){Nt(u,g,t);return}let m=h.match(/^\/api\/tests\/([a-f0-9]+)\/(network|console|api-calls)$/);if(u.method==="GET"&&m){Lt(u,g,t,m[1],m[2]);return}let b=h.match(/^\/api\/tests\/([a-f0-9]+)$/);if(u.method==="GET"&&b){It(u,g,t,b[1]);return}if(u.method==="GET"&&h==="/api/artifacts"){Mt(u,g,l);return}if(u.method==="DELETE"&&h==="/api/artifacts"){Et(u,g,l);return}let C=h.match(/^\/api\/artifacts\/(.+)$/);if(u.method==="DELETE"&&C){Pt(u,g,l,decodeURIComponent(C[1]));return}if(u.method==="GET"&&h.startsWith("/artifacts/")){let w=decodeURIComponent(h.slice(11));if(w.includes("..")||w.includes("\0")){x(g,400,{error:"Invalid path"});return}Me(u,g,path.join(l,w));return}if(u.method==="POST"&&h==="/api/shutdown"){Ft(u,g,p);return}x(g,404,{error:"Not found"});});function f(u){let g=h=>{h.code==="EADDRINUSE"&&i<Sn?(i++,f(u+1)):n(h);};p.once("error",g),p.listen(u,"127.0.0.1",()=>{p.removeListener("error",g);let h=p.address();if(!h||typeof h=="string"){n(new Error("Failed to get server address"));return}d(),r({port:h.port,dispose:()=>new Promise(m=>{clearTimeout(o),p.close(()=>m());})});});}f(s);})}async function Dt(t){let e=await G(t);return {port:e.port,dispose:e.dispose}}function In(t,e,r){let n=JSON.stringify(t),s=r?JSON.stringify(r):null;return vt(n,e,s)}async function Pe(t,e,r){try{let n=null,s=null,a=e.reportMode==="streaming"||e.reportMode==="auto"&&t.timeline.length===0&&t.summary.total>=e.streamingThreshold,o,i=e.htmlReportPath,l=path.dirname(i);fs$1.mkdirSync(l,{recursive:!0});let d="",c="[]",p=null;if(a){d=JSON.stringify(t.summary);let u=path.dirname(e.outputPath),g=path.join(u,".testrelic-report");try{let h=path.join(g,"index-compact.json"),m=path.join(g,"index.json");fs$1.existsSync(h)?c=fs$1.readFileSync(h,"utf-8"):fs$1.existsSync(m)&&(c=fs$1.readFileSync(m,"utf-8"));}catch{}p=r?JSON.stringify(r):null,o=Ie(d,c,null,p);}else o=In(t,null,r);let f=i+".tmp";if(fs$1.writeFileSync(f,o,"utf-8"),fs$1.renameSync(f,i),a){if(t.ci===null){let u=path.dirname(e.outputPath),g=path.join(u,".testrelic-report"),h=path.resolve(i);try{if(await Nn(),n=await Mn(g),!n){s=await G(g,{htmlPath:h}),n=s.port;let m=setInterval(()=>{},6e4),b=()=>{clearInterval(m),s?.dispose();};process.on("SIGINT",b),process.on("SIGTERM",b),setTimeout(b,1800*1e3).unref();}if(n){process.stderr.write(`
2481
+ `;fs$1.writeSync(this.fd,r),this.count++;}getPath(){return this.filePath}getCount(){return this.count}close(){if(!this.closed){this.closed=true;try{fs$1.closeSync(this.fd);}catch{}}}cleanup(){try{fs$1.unlinkSync(this.filePath);}catch{}}};async function wt(t,e,r,n){let s=(e-1)*r,a=[],o=0,i=readline.createInterface({input:fs$1.createReadStream(t,{encoding:"utf-8"}),crlfDelay:1/0});for await(let c of i)if(c.length!==0){if(o>=s&&a.length<r)try{a.push(JSON.parse(c));}catch{}if(o++,a.length>=r&&n!==void 0)break}let l=n??o,d=Math.max(1,Math.ceil(l/r));return {items:a,total:l,page:e,pageSize:r,totalPages:d}}var Ct=/^[a-f0-9]{12}$/,Ne=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,Tt=5e3;function x(t,e,r){t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(r));}function St(t){t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, DELETE, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type");}function re(t){try{return fs$1.existsSync(t)?JSON.parse(fs$1.readFileSync(t,"utf-8")):null}catch{return null}}function ne(t){let e=0;try{let r=fs$1.readdirSync(t,{withFileTypes:!0});for(let n of r){let s=path.join(t,n.name);n.isFile()?e+=fs$1.statSync(s).size:n.isDirectory()&&(e+=ne(s));}}catch{}return e}function Rt(t,e,r,n){let s=re(path.join(r,"index.json"));x(e,200,{status:"ok",reportMode:"streaming",testCount:s?.length??0,uptime:Math.floor((Date.now()-n)/1e3)});}function At(t,e,r){let n=re(path.join(r,"summary.json"));if(!n){x(e,404,{error:"Summary not found"});return}x(e,200,n);}function _t(t,e,r){let n=re(path.join(r,"index.json"));if(!n){x(e,404,{error:"Test index not found"});return}let a=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,o=Math.max(1,parseInt(a.get("page")??"1",10)||1),i=Math.min(Tt,Math.max(1,parseInt(a.get("pageSize")??"100",10)||100)),l=a.get("status")?.split(",").filter(Boolean)??null,d=a.get("file")??null,c=a.get("search")?.toLowerCase()??null,p=a.get("tag")?.split(",").filter(Boolean)??null,f=a.get("sort")??"file",u=a.get("order")==="desc"?-1:1,g=n;l&&l.length>0&&(g=g.filter(w=>l.includes(w.status))),d&&(g=g.filter(w=>w.filePath===d||w.filePath.startsWith(d+"/"))),c&&(g=g.filter(w=>w.title.toLowerCase().includes(c)||w.filePath.toLowerCase().includes(c))),p&&p.length>0&&(g=g.filter(w=>p.some(y=>w.tags.includes(y)))),g=[...g].sort((w,y)=>{let T=0;switch(f){case "duration":T=w.duration-y.duration;break;case "status":T=w.status.localeCompare(y.status);break;case "title":T=w.title.localeCompare(y.title);break;default:T=w.filePath.localeCompare(y.filePath);break}return T*u});let h=g.length,m=Math.max(1,Math.ceil(h/i)),b=(o-1)*i,C=g.slice(b,b+i);x(e,200,{tests:C,pagination:{page:o,pageSize:i,totalItems:h,totalPages:m},filters:{status:l,file:d,search:c,tag:p}});}function It(t,e,r,n){if(!Ct.test(n)){x(e,400,{error:"Invalid test ID format"});return}let s=path.join(r,"tests",n,"meta.json"),a=path.join(r,"tests",`${n}.json`),o=fs$1.existsSync(s)?s:fs$1.existsSync(a)?a:null;if(!o){x(e,404,{error:`Test not found: ${n}`});return}try{let i=fs$1.readFileSync(o,"utf-8");e.writeHead(200,{"Content-Type":"application/json"}),e.end(i);}catch(i){x(e,500,{error:i instanceof Error?i.message:String(i)});}}async function Lt(t,e,r,n,s){if(!Ct.test(n)){x(e,400,{error:"Invalid test ID format"});return}let o=path.join(r,"tests",n,{network:"network.jsonl",console:"console.jsonl","api-calls":"api-calls.jsonl"}[s]);if(!fs$1.existsSync(o)){x(e,200,{items:[],total:0,page:1,pageSize:50,totalPages:0});return}try{let l=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,d=Math.max(1,parseInt(l.get("page")??"1",10)||1),c=Math.min(Tt,Math.max(1,parseInt(l.get("pageSize")??"50",10)||50)),p,f=path.join(r,"tests",n,"meta.json");if(fs$1.existsSync(f))try{let g=JSON.parse(fs$1.readFileSync(f,"utf-8"));switch(s){case "network":p=g.networkRequestsCount;break;case "console":p=g.consoleLogsCount;break;case "api-calls":p=g.apiCallsCount;break}}catch{}let u=await wt(o,d,c,p);x(e,200,u);}catch(i){x(e,500,{error:i instanceof Error?i.message:String(i)});}}function Nt(t,e,r){let n=re(path.join(r,"index.json"));if(!n){x(e,404,{error:"Test index not found"});return}let s=new Map;for(let o of n){if(o.isRetry)continue;let i=s.get(o.filePath);switch(i||(i={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0},s.set(o.filePath,i)),i.total++,o.status){case "passed":i.passed++;break;case "failed":i.failed++;break;case "flaky":i.flaky++;break;case "skipped":i.skipped++;break;case "timedout":i.timedOut++;break}}let a=Array.from(s.entries()).map(([o,i])=>({filePath:o,...i})).sort((o,i)=>o.filePath.localeCompare(i.filePath));x(e,200,{files:a});}function Mt(t,e,r){if(!fs$1.existsSync(r)){x(e,200,{runs:[],totalSizeBytes:0});return}try{let n=[],s=0,a=fs$1.readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!Ne.test(o.name))continue;let i=path.join(r,o.name),l=ne(i),d=fs$1.readdirSync(i,{withFileTypes:!0}).filter(c=>c.isDirectory());n.push({folderName:o.name,totalSizeBytes:l,testCount:d.length}),s+=l;}n.sort((o,i)=>i.folderName.localeCompare(o.folderName)),x(e,200,{runs:n,totalSizeBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Et(t,e,r){try{let n=0,s=0;if(fs$1.existsSync(r)){let a=fs$1.readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!Ne.test(o.name))continue;let i=path.join(r,o.name),l=ne(i);fs$1.rmSync(i,{recursive:!0,force:!0}),s+=l,n++;}}x(e,200,{deletedCount:n,freedBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Pt(t,e,r,n){if(!Ne.test(n)){x(e,400,{error:"Invalid folder name"});return}let s=path.join(r,n);try{if(!fs$1.statSync(s).isDirectory()){x(e,404,{error:"Not found"});return}}catch{x(e,404,{error:"Not found"});return}try{let a=ne(s);fs$1.rmSync(s,{recursive:!0,force:!0}),x(e,200,{deleted:n,freedBytes:a});}catch(a){x(e,500,{error:a instanceof Error?a.message:String(a)});}}function Ft(t,e,r){x(e,200,{status:"shutting_down"}),r.close();}function Me(t,e,r){if(!fs$1.existsSync(r)){x(e,404,{error:"File not found"});return}try{let n=fs$1.readFileSync(r),s=path.extname(r).toLowerCase(),a=wn[s]??"application/octet-stream";e.writeHead(200,{"Content-Type":a}),e.end(n);}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}var wn={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".webm":"video/webm",".mp4":"video/mp4",".json":"application/json",".html":"text/html",".css":"text/css",".js":"text/javascript",".svg":"image/svg+xml"};var Tn=9323,Sn=10,Rn=1800*1e3;function G(t,e){return new Promise((r,n)=>{let s=e?.port??Tn,a=Date.now(),o,i=0,l=fs$1.existsSync(path.join(t,"artifacts"))?path.join(t,"artifacts"):fs$1.existsSync(path.join(t,"..","artifacts"))?path.join(t,"..","artifacts"):path.join(t,"artifacts");function d(){clearTimeout(o),o=setTimeout(()=>{p.close();},Rn);}let c=e?.htmlPath??null;if(!c){let u=path.dirname(t);try{let g=fs$1.readdirSync(u).find(h=>h.endsWith(".html"));g&&(c=path.join(u,g));}catch{}}let p=http.createServer((u,g)=>{if(d(),St(g),u.method==="OPTIONS"){g.writeHead(204),g.end();return}let h;try{h=new URL(u.url??"/",`http://${u.headers.host??"localhost"}`).pathname;}catch{x(g,400,{error:"Invalid URL"});return}if(u.method==="GET"&&(h==="/"||h==="/index.html")){if(c&&fs$1.existsSync(c)){Me(u,g,c);return}x(g,404,{error:"HTML report not found"});return}if(u.method==="GET"&&h==="/api/health"){Rt(u,g,t,a);return}if(u.method==="GET"&&h==="/api/summary"){At(u,g,t);return}if(u.method==="GET"&&h==="/api/tests"){_t(u,g,t);return}if(u.method==="GET"&&h==="/api/files"){Nt(u,g,t);return}let m=h.match(/^\/api\/tests\/([a-f0-9]+)\/(network|console|api-calls)$/);if(u.method==="GET"&&m){Lt(u,g,t,m[1],m[2]);return}let b=h.match(/^\/api\/tests\/([a-f0-9]+)$/);if(u.method==="GET"&&b){It(u,g,t,b[1]);return}if(u.method==="GET"&&h==="/api/artifacts"){Mt(u,g,l);return}if(u.method==="DELETE"&&h==="/api/artifacts"){Et(u,g,l);return}let C=h.match(/^\/api\/artifacts\/(.+)$/);if(u.method==="DELETE"&&C){Pt(u,g,l,decodeURIComponent(C[1]));return}if(u.method==="GET"&&h.startsWith("/artifacts/")){let w=decodeURIComponent(h.slice(11));if(w.includes("..")||w.includes("\0")){x(g,400,{error:"Invalid path"});return}Me(u,g,path.join(l,w));return}if(u.method==="POST"&&h==="/api/shutdown"){Ft(u,g,p);return}x(g,404,{error:"Not found"});});function f(u){let g=h=>{h.code==="EADDRINUSE"&&i<Sn?(i++,f(u+1)):n(h);};p.once("error",g),p.listen(u,"127.0.0.1",()=>{p.removeListener("error",g);let h=p.address();if(!h||typeof h=="string"){n(new Error("Failed to get server address"));return}d(),r({port:h.port,dispose:()=>new Promise(m=>{clearTimeout(o),p.close(()=>m());})});});}f(s);})}async function Dt(t){let e=await G(t);return {port:e.port,dispose:e.dispose}}function In(t,e,r){let n=JSON.stringify(t),s=r?JSON.stringify(r):null;return vt(n,e,s)}async function Pe(t,e,r){try{let n=null,s=null,a=e.reportMode==="streaming"||e.reportMode==="auto"&&t.timeline.length===0&&t.summary.total>=e.streamingThreshold,o,i=e.htmlReportPath,l=path.dirname(i);fs$1.mkdirSync(l,{recursive:!0});let d="",c="[]",p=null;if(a){d=JSON.stringify(t.summary);let u=path.dirname(e.outputPath),g=path.join(u,".testrelic-report");try{let h=path.join(g,"index-compact.json"),m=path.join(g,"index.json");fs$1.existsSync(h)?c=fs$1.readFileSync(h,"utf-8"):fs$1.existsSync(m)&&(c=fs$1.readFileSync(m,"utf-8"));}catch{}p=r?JSON.stringify(r):null,o=Ie(d,c,null,p);}else o=In(t,null,r);let f=i+".tmp";if(fs$1.writeFileSync(f,o,"utf-8"),fs$1.renameSync(f,i),a){if(t.ci===null){let u=path.dirname(e.outputPath),g=path.join(u,".testrelic-report"),h=path.resolve(i);try{if(await Nn(),n=await Mn(g),!n){s=await G(g,{htmlPath:h}),n=s.port;let m=setInterval(()=>{},6e4),b=()=>{clearInterval(m),s?.dispose();};process.on("SIGINT",b),process.on("SIGTERM",b),setTimeout(b,1800*1e3).unref();}if(n){process.stderr.write(`
2482
2482
  Report server: http://127.0.0.1:${n}
2483
2483
  `);try{let m=Ie(d,c,n,p),b=i+".tmp";fs$1.writeFileSync(b,m,"utf-8"),fs$1.renameSync(b,i);}catch{}}}catch{}}}else if(e.openReport&&t.ci===null&&e.includeArtifacts){let u=path.dirname(e.outputPath),g=path.join(u,"artifacts");if(fs$1.existsSync(g))try{let h=await Dt(g);n=h.port,process.on("exit",()=>{h.dispose();});}catch{}}if(e.openReport&&t.ci===null)if(a&&n)Le(`http://127.0.0.1:${n}`);else {let u=path.resolve(i);Le(u);}}catch(n){process.stderr.write(`[testrelic] Failed to write HTML report: ${n instanceof Error?n.message:String(n)}
2484
2484
  `);}}var ie=9323,Ut=10;async function Ln(){for(let t=ie;t<ie+Ut;t++)try{let e=new AbortController,r=setTimeout(()=>e.abort(),500),n=await fetch(`http://127.0.0.1:${t}/api/health`,{signal:e.signal});if(clearTimeout(r),n.ok)return t}catch{}return null}async function Nn(){for(let t=ie;t<ie+Ut;t++)try{let e=new AbortController,r=setTimeout(()=>e.abort(),1e3);await fetch(`http://127.0.0.1:${t}/api/shutdown`,{method:"POST",signal:e.signal}),clearTimeout(r);}catch{}await new Promise(t=>setTimeout(t,300));}async function Mn(t){let r=[path.join(__dirname,"cli.cjs"),path.join(__dirname,"cli.js"),path.join(__dirname,"..","dist","cli.cjs"),path.join(__dirname,"..","dist","cli.js")].find(n=>fs$1.existsSync(n));if(!r){let n=await G(t);return process.on("exit",()=>{n?.dispose();}),n.port}return new Promise(n=>{let s=child_process.spawn(process.execPath,[r,"serve",t],{detached:true,stdio:["ignore","ignore","pipe"],env:{...process.env}}),a="",o=false,i=setTimeout(()=>{o||(o=true,s.stderr?.removeAllListeners(),Ln().then(n));},5e3);s.stderr?.on("data",l=>{a+=l.toString();let d=a.match(/127\.0\.0\.1:(\d+)/);d&&!o&&(o=true,clearTimeout(i),s.stderr?.removeAllListeners(),s.unref(),n(Number(d[1])));}),s.on("error",()=>{o||(o=true,clearTimeout(i),n(null));}),s.on("exit",()=>{o||(o=true,clearTimeout(i),n(null));}),s.unref();})}var Fn=20,Fe=0,De=[];async function jt(t,e){Fe>=Fn&&await new Promise(r=>De.push(r)),Fe++;try{return await promises.copyFile(t,e),!0}catch{return false}finally{Fe--,De.length>0&&De.shift()();}}var K=[];async function zt(){K.length!==0&&(await Promise.allSettled(K),K.length=0);}function Vt(t){let e=new Date,r=a=>String(a).padStart(2,"0"),n=`${e.getFullYear()}-${r(e.getMonth()+1)}-${r(e.getDate())}T${r(e.getHours())}-${r(e.getMinutes())}-${r(e.getSeconds())}`;if(!fs$1.existsSync(path.join(t,n)))return n;let s=1;for(;fs$1.existsSync(path.join(t,`${n}-${s}`));)s++;return `${n}-${s}`}function Dn(t){let e=t.replace(/[^a-zA-Z0-9\-_ ]/g,"-").replace(/\s+/g,"-").replace(/-{2,}/g,"-").replace(/^-+|-+$/g,"");return e.length>100&&(e=e.substring(0,100).replace(/-+$/,"")),e||"unnamed-test"}function Wt(t,e,r,n,s){let a=t.find(f=>f.name==="screenshot"&&f.path),o=t.find(f=>f.name==="video"&&f.path);if(!a&&!o)return null;let i=Dn(e);r>0&&(i+=`--retry-${r}`);let l=s?["artifacts",s,i]:["artifacts",i],d=path.join(n,...l),c=l,p={};try{fs$1.mkdirSync(d,{recursive:!0});}catch{return null}if(a?.path&&fs$1.existsSync(a.path)){let u=`screenshot${path.extname(a.path)||".png"}`,g=path.join(d,u);K.push(jt(a.path,g).then(()=>{})),p.screenshot=`${c.join("/")}/${u}`;}if(o?.path&&fs$1.existsSync(o.path)){let u=`video${path.extname(o.path)||".webm"}`,g=path.join(d,u);K.push(jt(o.path,g).then(()=>{})),p.video=`${c.join("/")}/${u}`;}return !p.screenshot&&!p.video?null:p}var qn=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,Hn="1.0";function Un(t){let e=path.extname(t).toLowerCase();return [".png",".jpg",".jpeg",".gif",".bmp",".webp"].includes(e)?"screenshot":[".webm",".mp4",".avi",".mov"].includes(e)?"video":"other"}function Gt(t,e,r){let n=[];try{let s=fs$1.readdirSync(t,{withFileTypes:!0});for(let a of s){if(!a.isFile())continue;let o=path.join(t,a.name),i=fs$1.statSync(o);n.push({name:a.name,type:Un(a.name),relativePath:`artifacts/${e}/${r}/${a.name}`,sizeBytes:i.size});}}catch{}return {testName:r,files:n}}function $n(t,e){let r=path.join(t,e),n=[],s=0;try{let o=fs$1.readdirSync(r,{withFileTypes:!0});for(let i of o){if(!i.isDirectory())continue;let l=Gt(path.join(r,i.name),e,i.name);n.push(l);for(let d of l.files)s+=d.sizeBytes;}}catch{}let a=e.replace(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})/,"$1T$2:$3:$4").replace(/-\d+$/,"");return n.sort((o,i)=>o.testName.localeCompare(i.testName)),{folderName:e,timestamp:a,totalSizeBytes:s,testCount:n.length,tests:n,isCurrentRun:false}}function Jt(t,e){let r=path.join(t,"artifacts"),n=[],s=[],a=0;try{let i=fs$1.readdirSync(r,{withFileTypes:!0});for(let l of i)if(l.isDirectory())if(qn.test(l.name)){let d=$n(r,l.name);n.push({...d,isCurrentRun:l.name===e});}else {let d=Gt(path.join(r,l.name),l.name,l.name);s.push(d);for(let c of d.files)a+=c.sizeBytes;}}catch{}n.sort((i,l)=>l.timestamp.localeCompare(i.timestamp)),s.length>0&&(s.sort((i,l)=>i.testName.localeCompare(l.testName)),n.push({folderName:"__legacy__",timestamp:"1970-01-01T00:00:00",totalSizeBytes:a,testCount:s.length,tests:s,isCurrentRun:false}));let o=n.reduce((i,l)=>i+l.totalSizeBytes,0);return {schemaVersion:Hn,generatedAt:new Date().toISOString(),artifactBaseDir:"artifacts",totalSizeBytes:o,runs:n,serverPort:null}}function Gn(t){let e=t,{root:r}=path.parse(e);for(;e!==r;){if(fs$1.existsSync(path.join(e,".git")))return e;e=path.join(e,"..");}return null}function Yt(t){try{let e=Gn(t);if(!e)return;let r=path.join(e,".gitignore"),n=path.relative(e,t).replace(/\\/g,"/"),s=n.endsWith("/")?n:`${n}/`;if(fs$1.existsSync(r)&&fs$1.readFileSync(r,"utf-8").split(`