@testrelic/playwright-analytics 2.15.0 → 2.16.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/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import {randomUUID,createHash}from'crypto';import {mkdirSync,openSync,writeSync,closeSync,unlinkSync,readFileSync,writeFileSync,renameSync,existsSync,appendFileSync,readdirSync,statSync,rmSync,createReadStream}from'fs';import {join,dirname,relative,extname,resolve,basename}from'path';import {tmpdir,platform}from'os';import {PAYLOAD_VERSION,ATTACHMENT_NAME,ATTACHMENT_CONTENT_TYPE,isValidConfig,createError,ErrorCode,isTestRelicFilePayload,isTestRelicDataPayload,isValidEndpointUrl,isValidCloudConfig,isValidQueueEntry}from'@testrelic/core';export{ATTACHMENT_CONTENT_TYPE,ATTACHMENT_NAME,PAYLOAD_VERSION,isTestRelicDataPayload}from'@testrelic/core';import {spawn,execFile,execSync}from'child_process';import {createServer}from'http';import {createInterface}from'readline';import {copyFile}from'fs/promises';import {gzip}from'zlib';import {promisify}from'util';import {Readable}from'stream';import {test,expect,defineConfig}from'@playwright/test';import {performance}from'perf_hooks';import {fileURLToPath}from'url';var Vn=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var Jn=".testrelic-report";function De(t,e,r,n){let s=`${t}|${e.join("|")}|${r}|${n}`;return createHash("sha256").update(s).digest("hex").substring(0,12)}function Be(t,e,r){let n=`${t}|${e.join("|")}|${r}`;return createHash("sha256").update(n).digest("hex").substring(0,12)}var se=class{constructor(e){this.writeErrors=[];this.totalBytesWritten=0;this.reportDir=join(e,Jn),this.testsDir=join(this.reportDir,"tests"),mkdirSync(this.testsDir,{recursive:true});}getReportDir(){return this.reportDir}getWriteErrors(){return this.writeErrors}writeTestDetail(e,r,n){try{let s=join(this.testsDir,e);mkdirSync(s,{recursive:!0});let a=join(s,"meta.json"),l=a+".tmp",i=JSON.stringify(r);return writeFileSync(l,i,"utf-8"),renameSync(l,a),this.totalBytesWritten+=Buffer.byteLength(i,"utf-8"),n?.networkRequestsFile&&this.moveFile(n.networkRequestsFile,join(s,"network.jsonl")),n?.consoleLogsFile&&this.moveFile(n.consoleLogsFile,join(s,"console.jsonl")),n?.apiCallsFile&&this.moveFile(n.apiCallsFile,join(s,"api-calls.jsonl")),!0}catch(s){let a=s instanceof Error?s.message:String(s);return this.writeErrors.push({testId:e,error:a,timestamp:new Date().toISOString()}),process.stderr.write(`[testrelic] Failed to write test detail for ${e}: ${a}
2
- `),false}}moveFile(e,r){try{renameSync(e,r);}catch{try{let{copyFileSync:n}=Vn("fs");n(e,r);try{unlinkSync(e);}catch{}}catch(n){process.stderr.write(`[testrelic] Failed to move file ${e} \u2192 ${r}: ${n instanceof Error?n.message:String(n)}
3
- `);}}}writeIndex(e){let r=join(this.reportDir,"index.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}writeCompactIndex(e){let r=join(this.reportDir,"index-compact.json"),n=r+".tmp",s=e.map(({titlePath:l,project:i,...d})=>d),a=JSON.stringify(s);writeFileSync(n,a,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(a,"utf-8");}writeSummary(e){let r=join(this.reportDir,"summary.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}writeManifest(e){let r=join(this.reportDir,"manifest.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}computeTotalSize(){return this.totalBytesWritten}dispose(){}};var er=".testrelic",ts="testrelic-config.json",rs=".testrelic",ns=5,ss=new Set(["__proto__","constructor","prototype"]),Zt=Object.freeze({apiKey:null,endpoint:"https://platform.testrelic.ai/api/v1",uploadStrategy:"batch",timeout:3e4,uploadBudgetMs:12e4,projectName:null,queueMaxAge:6048e5,queueDirectory:`${er}/queue`,uploadArtifacts:true,artifactMaxSizeMb:50}),as={s:1e3,m:60*1e3,h:3600*1e3,d:1440*60*1e3};function tr(t){let e=resolve(t);for(let r=0;r<=ns;r++){let n=join(e,er,ts);if(existsSync(n))try{if(statSync(n).isFile())return n}catch{}let s=join(e,rs);if(existsSync(s))try{if(statSync(s).isFile())return process.stderr.write(`[testrelic] Deprecation: config file ".testrelic" has moved to ".testrelic/testrelic-config.json". Please migrate your config file to the new location.
4
- `),s}catch{}let a=dirname(e);if(a===e)break;e=a;}return null}function rr(t){try{let e=readFileSync(t,"utf-8"),r=JSON.parse(e);return typeof r!="object"||r===null||Array.isArray(r)||!nr(r)?null:r}catch{return null}}function nr(t){for(let e of Object.keys(t)){if(ss.has(e))return false;let r=t[e];if(typeof r=="object"&&r!==null&&!Array.isArray(r)&&!nr(r))return false}return true}function sr(t){let e=/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(t);if(e)return process.env[e[1]]??null;let r=/^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(t);return r?process.env[r[1]]??null:t}function Oe(t){let e=Object.create(null);for(let r of Object.keys(t)){let n=t[r];typeof n=="string"&&n.startsWith("$")?e[r]=sr(n):typeof n=="object"&&n!==null&&!Array.isArray(n)?e[r]=Oe(n):e[r]=n;}return e}function Xt(t){let e=/^(\d+)\s*([smhd])$/.exec(t.trim());if(!e)return null;let r=parseInt(e[1],10),n=e[2],s=as[n];return !s||r<=0?null:r*s}function ar(t,e){let r=Object.create(null);if(Object.assign(r,Zt),t){let d=t.cloud;d&&typeof d=="object"&&(typeof d.endpoint=="string"&&(r.endpoint=d.endpoint),typeof d.upload=="string"&&(r.uploadStrategy=d.upload),typeof d.timeout=="number"&&(r.timeout=d.timeout),typeof d.uploadBudgetMs=="number"&&(r.uploadBudgetMs=d.uploadBudgetMs),typeof d.apiKey=="string"&&d.apiKey.length>0&&(r.apiKey=d.apiKey));let o=t["testrelic-repo"]??t.project;o&&typeof o=="object"&&typeof o.name=="string"&&(r.projectName=o.name),t.project&&!t["testrelic-repo"]&&process.stderr.write(`[testrelic] Deprecation: "project" key in config is deprecated. Rename it to "testrelic-repo".
5
- `);let c=t.queue;if(c&&typeof c=="object"){if(typeof c.maxAge=="string"){let p=Xt(c.maxAge);p!==null&&(r.queueMaxAge=p);}typeof c.directory=="string"&&(r.queueDirectory=c.directory);}}if(e){if(typeof e.apiKey=="string"&&e.apiKey.length>0){let d=e.apiKey.startsWith("$")?sr(e.apiKey):e.apiKey;d&&(r.apiKey=d);}if(typeof e.endpoint=="string"&&(r.endpoint=e.endpoint),typeof e.upload=="string"&&(r.uploadStrategy=e.upload),typeof e.timeout=="number"&&(r.timeout=e.timeout),typeof e.uploadBudgetMs=="number"&&(r.uploadBudgetMs=e.uploadBudgetMs),typeof e.projectName=="string"&&(r.projectName=e.projectName),typeof e.queueMaxAge=="string"){let d=Xt(e.queueMaxAge);d!==null&&(r.queueMaxAge=d);}typeof e.queueDirectory=="string"&&(r.queueDirectory=e.queueDirectory),typeof e.uploadArtifacts=="boolean"&&(r.uploadArtifacts=e.uploadArtifacts),typeof e.artifactMaxSizeMb=="number"&&(r.artifactMaxSizeMb=e.artifactMaxSizeMb);}let n=process.env.TESTRELIC_API_KEY;n&&n.length>0&&(r.apiKey=n);let s=process.env.TESTRELIC_CLOUD_ENDPOINT;s&&isValidEndpointUrl(s)&&(r.endpoint=s);let a=process.env.TESTRELIC_UPLOAD_STRATEGY;a&&["batch","realtime","both"].includes(a)&&(r.uploadStrategy=a);let l=process.env.TESTRELIC_CLOUD_TIMEOUT;if(l){let d=parseInt(l,10);!isNaN(d)&&d>=1e3&&d<=12e4&&(r.timeout=d);}let i=Object.freeze(r);return isValidCloudConfig(i)?i:Zt}var ds=[/AKIA[A-Z0-9]{16}/g,/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g,/-----BEGIN\s+(RSA\s+)?PRIVATE\sKEY-----[\s\S]*?-----END/g,/\/\/[^:]+:[^@]+@/g],cs=["authorization","cookie","set-cookie","x-api-key"],us=["password","secret","token","apiKey","api_key"];function ir(t){let e=Object.create(null);return e.trackApiCalls=t?.trackApiCalls??true,e.captureRequestHeaders=t?.captureRequestHeaders??true,e.captureResponseHeaders=t?.captureResponseHeaders??true,e.captureRequestBody=t?.captureRequestBody??true,e.captureResponseBody=t?.captureResponseBody??true,e.captureAssertions=t?.captureAssertions??true,e.redactHeaders=t?.redactHeaders??[...cs],e.redactBodyFields=t?.redactBodyFields??[...us],e.apiIncludeUrls=t?.apiIncludeUrls??[],e.apiExcludeUrls=t?.apiExcludeUrls??[],Object.freeze(e)}function or(t){if(t!==void 0&&!isValidConfig(t))throw createError(ErrorCode.CONFIG_INVALID,"Invalid reporter configuration");let e=Object.create(null);e.outputPath=t?.outputPath??"./test-results/analytics-timeline.json",e.includeStackTrace=t?.includeStackTrace??false,e.includeCodeSnippets=t?.includeCodeSnippets??true,e.codeContextLines=t?.codeContextLines??3,e.includeNetworkStats=t?.includeNetworkStats??true,e.captureNavigation=t?.captureNavigation??true,e.navigationTypes=t?.navigationTypes??null,e.redactPatterns=[...ds,...t?.redactPatterns??[]],e.testRunId=t?.testRunId??null,e.metadata=t?.metadata??null;let r=e.outputPath;e.openReport=t?.openReport??true,e.htmlReportPath=t?.htmlReportPath??r.replace(/\.json$/,".html"),e.includeArtifacts=t?.includeArtifacts??true,e.trackApiCalls=t?.trackApiCalls??true,e.quiet=t?.quiet??false,e.includeActionSteps=t?.includeActionSteps??true,e.captureConsoleLogs=t?.captureConsoleLogs??true;let n=tr(process.cwd()),s=n?rr(n):null,a=s?Oe(s):null,l=ar(a,t?.cloud);return e.cloud=l.apiKey?l:null,e.reportMode=t?.reportMode??"streaming",e.streamingThreshold=t?.streamingThreshold??0,Object.freeze(e)}var pe="1.3.0";function lr(t,e,r){try{let s=readFileSync(t,"utf-8").split(`
6
- `);if(e<1||e>s.length)return null;let a=Math.max(1,e-r),l=Math.min(s.length,e+r),i=[];for(let d=a;d<=l;d++){let o=d===e?">":" ",c=String(d).padStart(String(l).length," ");i.push(`${o} ${c} | ${s[d-1]}`);}return i.join(`
7
- `)}catch{return null}}function dr(t){return e=>{let r=e;for(let n of t)if(typeof n=="string"){let s=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");r=r.replace(new RegExp(s,"g"),"[REDACTED]");}else {let s=n.flags.includes("g")?n.flags:n.flags+"g",a=new RegExp(n.source,s);r=r.replace(a,"[REDACTED]");}return r}}function fs(t){return t.GITHUB_ACTIONS!=="true"?null:{provider:"github-actions",buildId:t.GITHUB_RUN_ID??null,commitSha:t.GITHUB_SHA??null,branch:t.GITHUB_REF_NAME??null,runUrl:t.GITHUB_SERVER_URL&&t.GITHUB_REPOSITORY&&t.GITHUB_RUN_ID?`${t.GITHUB_SERVER_URL}/${t.GITHUB_REPOSITORY}/actions/runs/${t.GITHUB_RUN_ID}`:null}}function gs(t){return t.GITLAB_CI!=="true"?null:{provider:"gitlab-ci",buildId:t.CI_PIPELINE_ID??null,commitSha:t.CI_COMMIT_SHA??null,branch:t.CI_COMMIT_BRANCH??t.CI_COMMIT_REF_NAME??null,runUrl:t.CI_PIPELINE_URL??null}}function hs(t){if(!t.JENKINS_URL)return null;let e=t.GIT_BRANCH??null;return e?.startsWith("origin/")&&(e=e.slice(7)),{provider:"jenkins",buildId:t.BUILD_ID??null,commitSha:t.GIT_COMMIT??null,branch:e,runUrl:t.BUILD_URL??null}}function ms(t){return t.CIRCLECI!=="true"?null:{provider:"circleci",buildId:t.CIRCLE_BUILD_NUM??null,commitSha:t.CIRCLE_SHA1??null,branch:t.CIRCLE_BRANCH??null,runUrl:t.CIRCLE_BUILD_URL??null}}function ys(t){if(!t.BITBUCKET_PIPELINE_UUID)return null;let e=t.BITBUCKET_WORKSPACE&&t.BITBUCKET_REPO_SLUG&&t.BITBUCKET_PIPELINE_UUID?`https://bitbucket.org/${t.BITBUCKET_WORKSPACE}/${t.BITBUCKET_REPO_SLUG}/pipelines/results/${t.BITBUCKET_PIPELINE_UUID}`:null;return {provider:"bitbucket-pipelines",buildId:t.BITBUCKET_BUILD_NUMBER??null,commitSha:t.BITBUCKET_COMMIT??null,branch:t.BITBUCKET_BRANCH??null,runUrl:e}}var vs=[fs,gs,hs,ms,ys];function F(t){let e=process.env;for(let r of vs){let n=r(e);if(n)return n}return null}var qe=`
1
+ import {randomUUID,createHash}from'crypto';import {writeFileSync,existsSync,mkdirSync,openSync,writeSync,closeSync,unlinkSync,readFileSync,renameSync,appendFileSync,readdirSync,statSync,readSync,createReadStream,rmSync}from'fs';import {join,extname,basename,dirname,resolve,relative}from'path';import {tmpdir,platform}from'os';import {PAYLOAD_VERSION,ATTACHMENT_NAME,ATTACHMENT_CONTENT_TYPE,isValidConfig,createError,ErrorCode,isTestRelicFilePayload,isTestRelicDataPayload,isValidEndpointUrl,isValidCloudConfig,isValidQueueEntry}from'@testrelic/core';export{ATTACHMENT_CONTENT_TYPE,ATTACHMENT_NAME,PAYLOAD_VERSION,isTestRelicDataPayload}from'@testrelic/core';import {spawn,execFile,execSync}from'child_process';import {createServer}from'http';import {createInterface}from'readline';import {copyFile}from'fs/promises';import {gzip}from'zlib';import {promisify}from'util';import {Readable}from'stream';import {expect,test,defineConfig}from'@playwright/test';import {performance}from'perf_hooks';import {fileURLToPath}from'url';var oa=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var da=".testrelic-report";function et(t,e,r,n){let s=`${t}|${e.join("|")}|${r}|${n}`;return createHash("sha256").update(s).digest("hex").substring(0,12)}function tt(t,e,r){let n=`${t}|${e.join("|")}|${r}`;return createHash("sha256").update(n).digest("hex").substring(0,12)}var he=class{constructor(e){this.writeErrors=[];this.totalBytesWritten=0;this.reportDir=join(e,da),this.testsDir=join(this.reportDir,"tests"),mkdirSync(this.testsDir,{recursive:true});}getReportDir(){return this.reportDir}getWriteErrors(){return this.writeErrors}writeTestDetail(e,r,n){try{let s=join(this.testsDir,e);mkdirSync(s,{recursive:!0});let a=join(s,"meta.json"),o=a+".tmp",i=JSON.stringify(r);return writeFileSync(o,i,"utf-8"),renameSync(o,a),this.totalBytesWritten+=Buffer.byteLength(i,"utf-8"),n?.networkRequestsFile&&this.moveFile(n.networkRequestsFile,join(s,"network.jsonl")),n?.consoleLogsFile&&this.moveFile(n.consoleLogsFile,join(s,"console.jsonl")),n?.apiCallsFile&&this.moveFile(n.apiCallsFile,join(s,"api-calls.jsonl")),!0}catch(s){let a=s instanceof Error?s.message:String(s);return this.writeErrors.push({testId:e,error:a,timestamp:new Date().toISOString()}),process.stderr.write(`[testrelic] Failed to write test detail for ${e}: ${a}
2
+ `),false}}moveFile(e,r){try{renameSync(e,r);}catch{try{let{copyFileSync:n}=oa("fs");n(e,r);try{unlinkSync(e);}catch{}}catch(n){process.stderr.write(`[testrelic] Failed to move file ${e} \u2192 ${r}: ${n instanceof Error?n.message:String(n)}
3
+ `);}}}writeIndex(e){let r=join(this.reportDir,"index.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}writeCompactIndex(e){let r=join(this.reportDir,"index-compact.json"),n=r+".tmp",s=e.map(({titlePath:o,project:i,...d})=>d),a=JSON.stringify(s);writeFileSync(n,a,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(a,"utf-8");}writeSummary(e){let r=join(this.reportDir,"summary.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}writeManifest(e){let r=join(this.reportDir,"manifest.json"),n=r+".tmp",s=JSON.stringify(e);writeFileSync(n,s,"utf-8"),renameSync(n,r),this.totalBytesWritten+=Buffer.byteLength(s,"utf-8");}computeTotalSize(){return this.totalBytesWritten}dispose(){}};var Lr=".testrelic",ha="testrelic-config.json",ma=".testrelic",va=5,ya=new Set(["__proto__","constructor","prototype"]),Er=Object.freeze({apiKey:null,endpoint:"https://platform.testrelic.ai/api/v1",uploadStrategy:"batch",timeout:3e4,uploadBudgetMs:12e4,projectName:null,queueMaxAge:6048e5,queueDirectory:`${Lr}/queue`,uploadArtifacts:true,artifactMaxSizeMb:50}),ba={s:1e3,m:60*1e3,h:3600*1e3,d:1440*60*1e3};function Nr(t){let e=resolve(t);for(let r=0;r<=va;r++){let n=join(e,Lr,ha);if(existsSync(n))try{if(statSync(n).isFile())return n}catch{}let s=join(e,ma);if(existsSync(s))try{if(statSync(s).isFile())return process.stderr.write(`[testrelic] Deprecation: config file ".testrelic" has moved to ".testrelic/testrelic-config.json". Please migrate your config file to the new location.
4
+ `),s}catch{}let a=dirname(e);if(a===e)break;e=a;}return null}function Dr(t){try{let e=readFileSync(t,"utf-8"),r=JSON.parse(e);return typeof r!="object"||r===null||Array.isArray(r)||!Fr(r)?null:r}catch{return null}}function Fr(t){for(let e of Object.keys(t)){if(ya.has(e))return false;let r=t[e];if(typeof r=="object"&&r!==null&&!Array.isArray(r)&&!Fr(r))return false}return true}function Br(t){let e=/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(t);if(e)return process.env[e[1]]??null;let r=/^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(t);return r?process.env[r[1]]??null:t}function rt(t){let e=Object.create(null);for(let r of Object.keys(t)){let n=t[r];typeof n=="string"&&n.startsWith("$")?e[r]=Br(n):typeof n=="object"&&n!==null&&!Array.isArray(n)?e[r]=rt(n):e[r]=n;}return e}function Pr(t){let e=/^(\d+)\s*([smhd])$/.exec(t.trim());if(!e)return null;let r=parseInt(e[1],10),n=e[2],s=ba[n];return !s||r<=0?null:r*s}function Or(t,e){let r=Object.create(null);if(Object.assign(r,Er),t){let d=t.cloud;d&&typeof d=="object"&&(typeof d.endpoint=="string"&&(r.endpoint=d.endpoint),typeof d.upload=="string"&&(r.uploadStrategy=d.upload),typeof d.timeout=="number"&&(r.timeout=d.timeout),typeof d.uploadBudgetMs=="number"&&(r.uploadBudgetMs=d.uploadBudgetMs),typeof d.apiKey=="string"&&d.apiKey.length>0&&(r.apiKey=d.apiKey));let l=t["testrelic-repo"]??t.project;l&&typeof l=="object"&&typeof l.name=="string"&&(r.projectName=l.name),t.project&&!t["testrelic-repo"]&&process.stderr.write(`[testrelic] Deprecation: "project" key in config is deprecated. Rename it to "testrelic-repo".
5
+ `);let c=t.queue;if(c&&typeof c=="object"){if(typeof c.maxAge=="string"){let u=Pr(c.maxAge);u!==null&&(r.queueMaxAge=u);}typeof c.directory=="string"&&(r.queueDirectory=c.directory);}}if(e){if(typeof e.apiKey=="string"&&e.apiKey.length>0){let d=e.apiKey.startsWith("$")?Br(e.apiKey):e.apiKey;d&&(r.apiKey=d);}if(typeof e.endpoint=="string"&&(r.endpoint=e.endpoint),typeof e.upload=="string"&&(r.uploadStrategy=e.upload),typeof e.timeout=="number"&&(r.timeout=e.timeout),typeof e.uploadBudgetMs=="number"&&(r.uploadBudgetMs=e.uploadBudgetMs),typeof e.projectName=="string"&&(r.projectName=e.projectName),typeof e.queueMaxAge=="string"){let d=Pr(e.queueMaxAge);d!==null&&(r.queueMaxAge=d);}typeof e.queueDirectory=="string"&&(r.queueDirectory=e.queueDirectory),typeof e.uploadArtifacts=="boolean"&&(r.uploadArtifacts=e.uploadArtifacts),typeof e.artifactMaxSizeMb=="number"&&(r.artifactMaxSizeMb=e.artifactMaxSizeMb);}let n=process.env.TESTRELIC_API_KEY;n&&n.length>0&&(r.apiKey=n);let s=process.env.TESTRELIC_CLOUD_ENDPOINT;s&&isValidEndpointUrl(s)&&(r.endpoint=s);let a=process.env.TESTRELIC_UPLOAD_STRATEGY;a&&["batch","realtime","both"].includes(a)&&(r.uploadStrategy=a);let o=process.env.TESTRELIC_CLOUD_TIMEOUT;if(o){let d=parseInt(o,10);!isNaN(d)&&d>=1e3&&d<=12e4&&(r.timeout=d);}let i=Object.freeze(r);return isValidCloudConfig(i)?i:Er}var Ca=[/AKIA[A-Z0-9]{16}/g,/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g,/-----BEGIN\s+(RSA\s+)?PRIVATE\sKEY-----[\s\S]*?-----END/g,/\/\/[^:]+:[^@]+@/g],Ta=["authorization","cookie","set-cookie","x-api-key"],Sa=["password","secret","token","apiKey","api_key"];function qr(t){let e=Object.create(null);return e.trackApiCalls=t?.trackApiCalls??true,e.captureRequestHeaders=t?.captureRequestHeaders??true,e.captureResponseHeaders=t?.captureResponseHeaders??true,e.captureRequestBody=t?.captureRequestBody??true,e.captureResponseBody=t?.captureResponseBody??true,e.captureAssertions=t?.captureAssertions??true,e.redactHeaders=t?.redactHeaders??[...Ta],e.redactBodyFields=t?.redactBodyFields??[...Sa],e.apiIncludeUrls=t?.apiIncludeUrls??[],e.apiExcludeUrls=t?.apiExcludeUrls??[],Object.freeze(e)}function Hr(t){if(t!==void 0&&!isValidConfig(t))throw createError(ErrorCode.CONFIG_INVALID,"Invalid reporter configuration");let e=Object.create(null);e.outputPath=t?.outputPath??"./test-results/analytics-timeline.json",e.includeStackTrace=t?.includeStackTrace??false,e.includeCodeSnippets=t?.includeCodeSnippets??true,e.codeContextLines=t?.codeContextLines??3,e.includeNetworkStats=t?.includeNetworkStats??true,e.captureNavigation=t?.captureNavigation??true,e.navigationTypes=t?.navigationTypes??null,e.redactPatterns=[...Ca,...t?.redactPatterns??[]],e.testRunId=t?.testRunId??null,e.metadata=t?.metadata??null;let r=e.outputPath;e.openReport=t?.openReport??true,e.htmlReportPath=t?.htmlReportPath??r.replace(/\.json$/,".html"),e.includeArtifacts=t?.includeArtifacts??true,e.trackApiCalls=t?.trackApiCalls??true,e.quiet=t?.quiet??false,e.includeActionSteps=t?.includeActionSteps??true,e.captureConsoleLogs=t?.captureConsoleLogs??true;let n=Nr(process.cwd()),s=n?Dr(n):null,a=s?rt(s):null,o=Or(a,t?.cloud);return e.cloud=o.apiKey?o:null,e.reportMode=t?.reportMode??"streaming",e.streamingThreshold=t?.streamingThreshold??0,Object.freeze(e)}var Ce="1.6.0";function Ur(t,e,r){try{let s=readFileSync(t,"utf-8").split(`
6
+ `);if(e<1||e>s.length)return null;let a=Math.max(1,e-r),o=Math.min(s.length,e+r),i=[];for(let d=a;d<=o;d++){let l=d===e?">":" ",c=String(d).padStart(String(o).length," ");i.push(`${l} ${c} | ${s[d-1]}`);}return i.join(`
7
+ `)}catch{return null}}function zr(t){return e=>{let r=e;for(let n of t)if(typeof n=="string"){let s=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");r=r.replace(new RegExp(s,"g"),"[REDACTED]");}else {let s=n.flags.includes("g")?n.flags:n.flags+"g",a=new RegExp(n.source,s);r=r.replace(a,"[REDACTED]");}return r}}function Aa(t){return t.GITHUB_ACTIONS!=="true"?null:{provider:"github-actions",buildId:t.GITHUB_RUN_ID??null,commitSha:t.GITHUB_SHA??null,branch:t.GITHUB_REF_NAME??null,runUrl:t.GITHUB_SERVER_URL&&t.GITHUB_REPOSITORY&&t.GITHUB_RUN_ID?`${t.GITHUB_SERVER_URL}/${t.GITHUB_REPOSITORY}/actions/runs/${t.GITHUB_RUN_ID}`:null}}function _a(t){return t.GITLAB_CI!=="true"?null:{provider:"gitlab-ci",buildId:t.CI_PIPELINE_ID??null,commitSha:t.CI_COMMIT_SHA??null,branch:t.CI_COMMIT_BRANCH??t.CI_COMMIT_REF_NAME??null,runUrl:t.CI_PIPELINE_URL??null}}function Ia(t){if(!t.JENKINS_URL)return null;let e=t.GIT_BRANCH??null;return e?.startsWith("origin/")&&(e=e.slice(7)),{provider:"jenkins",buildId:t.BUILD_ID??null,commitSha:t.GIT_COMMIT??null,branch:e,runUrl:t.BUILD_URL??null}}function Ma(t){return t.CIRCLECI!=="true"?null:{provider:"circleci",buildId:t.CIRCLE_BUILD_NUM??null,commitSha:t.CIRCLE_SHA1??null,branch:t.CIRCLE_BRANCH??null,runUrl:t.CIRCLE_BUILD_URL??null}}function Ea(t){if(!t.BITBUCKET_PIPELINE_UUID)return null;let e=t.BITBUCKET_WORKSPACE&&t.BITBUCKET_REPO_SLUG&&t.BITBUCKET_PIPELINE_UUID?`https://bitbucket.org/${t.BITBUCKET_WORKSPACE}/${t.BITBUCKET_REPO_SLUG}/pipelines/results/${t.BITBUCKET_PIPELINE_UUID}`:null;return {provider:"bitbucket-pipelines",buildId:t.BITBUCKET_BUILD_NUMBER??null,commitSha:t.BITBUCKET_COMMIT??null,branch:t.BITBUCKET_BRANCH??null,runUrl:e}}var Pa=[Aa,_a,Ia,Ma,Ea];function V(t){let e=process.env;for(let r of Pa){let n=r(e);if(n)return n}return null}var nt=`
8
8
  /* Theme Variables */
9
9
  :root,[data-theme="dark"]{
10
10
  --bg:#0f1117;--bg-1:#161b22;--bg-2:#1c2128;--bg-3:#21262d;--bg-code:#13111c;
@@ -567,15 +567,15 @@ body{
567
567
  .detail-error .failure-message{margin-bottom:16px;padding:12px;background:rgba(239,68,68,0.06);border:1px solid rgba(239,68,68,0.15);border-radius:8px;font-size:12px;color:#f87171;text-align:left;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}
568
568
  .detail-error code{font-size:11px;padding:2px 6px;background:var(--bg-2);border-radius:4px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
569
569
  .server-offline-banner{padding:8px 14px;background:rgba(251,191,36,0.08);border:1px solid rgba(251,191,36,0.2);border-radius:6px;font-size:11px;color:#fbbf24;text-align:center;margin-bottom:12px}
570
- `;var Ue=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196 247" fill="none">
570
+ `;var st=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196 247" fill="none">
571
571
  <path fill-rule="evenodd" clip-rule="evenodd" d="M4.75 1.08009C2.034 2.66209 -0.130999 7.63009 0.610001 10.5821C0.969001 12.0121 3.021 15.2131 5.17 17.6971C14.375 28.3321 18 39.7791 18 58.2101V71.0001H12.434C1.16501 71.0001 0 73.4641 0 97.3051C0 106.858 0.298003 128.922 0.662003 146.337L1.323 178H33.606C65.786 178 65.897 178.007 68.544 180.284C72.228 183.453 72.244 189.21 68.579 192.877L65.957 195.5L33.479 195.801L1 196.103V204.551V213H24.577H48.154L51.077 215.923C55.007 219.853 55.007 224.147 51.077 228.077L48.154 231H26.469H4.783L5.41901 233.534C5.76901 234.927 7.143 238.527 8.472 241.534L10.89 247H40.945H71L71.006 241.75C71.017 230.748 76.027 221.606 84.697 216.767C97.854 209.424 114.086 213.895 121.323 226.857C123.659 231.041 124.418 233.833 124.789 239.607L125.263 247H155.187H185.11L187.528 241.534C188.857 238.527 190.231 234.927 190.581 233.534L191.217 231H169.531H147.846L144.923 228.077C142.928 226.082 142 224.152 142 222C142 219.848 142.928 217.918 144.923 215.923L147.846 213H171.423H195V204.551V196.103L162.521 195.801L130.043 195.5L127.421 192.877C123.991 189.445 123.835 183.869 127.074 180.421L129.349 178H162.013H194.677L195.338 146.337C195.702 128.922 196 106.858 196 97.3051C196 73.4641 194.835 71.0001 183.566 71.0001H178V58.2101C178 39.6501 181.397 28.7731 190.538 18.0651C195.631 12.0971 196.572 9.00809 194.511 5.02109C192.672 1.46509 190.197 9.12233e-05 186.028 9.12233e-05C179.761 9.12233e-05 168.713 14.8831 163.388 30.5001C160.975 37.5771 160.608 40.3751 160.213 54.7501L159.765 71.0001H150.732H141.7L142.286 53.2501C142.904 34.5511 144.727 24.3761 148.938 16.1211C151.823 10.4671 151.628 5.90109 148.364 2.63609C145 -0.726907 140.105 -0.887909 136.596 2.25009C133.481 5.03609 128.686 17.0811 126.507 27.5921C125.569 32.1191 124.617 43.0901 124.28 53.2501L123.69 71.0001H115.345H107V38.4231V5.84609L104.077 2.92309C102.082 0.928088 100.152 9.12233e-05 98 9.12233e-05C95.848 9.12233e-05 93.918 0.928088 91.923 2.92309L89 5.84609V38.4231V71.0001H80.655H72.31L71.72 53.2501C71.383 43.0901 70.431 32.1191 69.493 27.5921C67.314 17.0811 62.519 5.03609 59.404 2.25009C55.998 -0.795909 51.059 -0.710905 47.646 2.45209C44.221 5.62609 44.191 9.92109 47.539 17.4911C51.71 26.9241 53.007 34.4791 53.676 53.2501L54.31 71.0001H45.272H36.235L35.787 54.7501C35.392 40.3751 35.025 37.5771 32.612 30.5001C27.194 14.6091 16.228 -0.02891 9.787 0.03009C7.979 0.04709 5.712 0.519093 4.75 1.08009ZM42.687 108.974C33.431 112.591 20.036 125.024 18.408 131.512C17.476 135.223 20.677 140.453 28.253 147.599C37.495 156.319 44.191 159.471 53.5 159.485C59.317 159.494 61.645 158.953 67.274 156.289C77.634 151.385 88.987 139.161 88.996 132.9C89.004 127.304 76.787 114.707 66.745 109.956C59.16 106.368 50.285 106.006 42.687 108.974ZM129.255 109.904C119.151 114.768 106.996 127.33 107.004 132.9C107.013 139.108 118.562 151.475 128.939 156.389C134.338 158.945 136.744 159.496 142.521 159.498C152.526 159.501 160.369 155.502 169.771 145.605C180.444 134.368 180.278 130.975 168.486 119.388C160.043 111.094 152.727 107.595 143 107.201C136.364 106.933 134.78 107.244 129.255 109.904ZM48.5 125.922C46.3 126.969 43.152 128.945 41.505 130.314L38.511 132.802L40.504 135.005C41.601 136.216 44.434 138.342 46.8 139.728C52.577 143.114 57.36 142.466 64.009 137.395L68.978 133.606L66.756 131.24C60.944 125.054 54.357 123.135 48.5 125.922ZM138.386 125.063C136.674 125.571 133.375 127.677 131.057 129.743L126.841 133.5L131.901 137.343C138.65 142.468 143.407 143.124 149.2 139.728C151.566 138.342 154.351 136.269 155.389 135.122C157.684 132.587 156.742 131.097 150.58 127.511C145.438 124.519 142.329 123.895 138.386 125.063ZM91.923 162.923C89.043 165.804 89 166.008 89 176.973C89 184.805 89.435 188.941 90.47 190.941C92.356 194.589 96.918 196.273 101.03 194.84C105.82 193.17 107 189.638 107 176.973C107 166.008 106.957 165.804 104.077 162.923C102.082 160.928 100.152 160 98 160C95.848 160 93.918 160.928 91.923 162.923ZM94.5 232.155C91.026 234.055 90 236.229 90 241.691V247H98H106V242.082C106 235.732 105.37 234.242 101.928 232.463C98.591 230.737 97.197 230.679 94.5 232.155Z" fill="url(#paint0_linear_55_11)"/>
572
572
  <defs><linearGradient id="paint0_linear_55_11" x1="98" y1="0.000244141" x2="98" y2="247" gradientUnits="userSpaceOnUse">
573
573
  <stop stop-color="#03B79C"/><stop offset="0.504808" stop-color="#4EDAA4"/><stop offset="0.865285" stop-color="#84F3AA"/>
574
- </linearGradient></defs></svg>`,He=`<svg width="32" height="40" viewBox="0 0 196 247" fill="none" xmlns="http://www.w3.org/2000/svg">
574
+ </linearGradient></defs></svg>`,at=`<svg width="32" height="40" viewBox="0 0 196 247" fill="none" xmlns="http://www.w3.org/2000/svg">
575
575
  <path fill-rule="evenodd" clip-rule="evenodd" d="M4.75 1.08009C2.034 2.66209 -0.130999 7.63009 0.610001 10.5821C0.969001 12.0121 3.021 15.2131 5.17 17.6971C14.375 28.3321 18 39.7791 18 58.2101V71.0001H12.434C1.16501 71.0001 0 73.4641 0 97.3051C0 106.858 0.298003 128.922 0.662003 146.337L1.323 178H33.606C65.786 178 65.897 178.007 68.544 180.284C72.228 183.453 72.244 189.21 68.579 192.877L65.957 195.5L33.479 195.801L1 196.103V204.551V213H24.577H48.154L51.077 215.923C55.007 219.853 55.007 224.147 51.077 228.077L48.154 231H26.469H4.783L5.41901 233.534C5.76901 234.927 7.143 238.527 8.472 241.534L10.89 247H40.945H71L71.006 241.75C71.017 230.748 76.027 221.606 84.697 216.767C97.854 209.424 114.086 213.895 121.323 226.857C123.659 231.041 124.418 233.833 124.789 239.607L125.263 247H155.187H185.11L187.528 241.534C188.857 238.527 190.231 234.927 190.581 233.534L191.217 231H169.531H147.846L144.923 228.077C142.928 226.082 142 224.152 142 222C142 219.848 142.928 217.918 144.923 215.923L147.846 213H171.423H195V204.551V196.103L162.521 195.801L130.043 195.5L127.421 192.877C123.991 189.445 123.835 183.869 127.074 180.421L129.349 178H162.013H194.677L195.338 146.337C195.702 128.922 196 106.858 196 97.3051C196 73.4641 194.835 71.0001 183.566 71.0001H178V58.2101C178 39.6501 181.397 28.7731 190.538 18.0651C195.631 12.0971 196.572 9.00809 194.511 5.02109C192.672 1.46509 190.197 9.12233e-05 186.028 9.12233e-05C179.761 9.12233e-05 168.713 14.8831 163.388 30.5001C160.975 37.5771 160.608 40.3751 160.213 54.7501L159.765 71.0001H150.732H141.7L142.286 53.2501C142.904 34.5511 144.727 24.3761 148.938 16.1211C151.823 10.4671 151.628 5.90109 148.364 2.63609C145 -0.726907 140.105 -0.887909 136.596 2.25009C133.481 5.03609 128.686 17.0811 126.507 27.5921C125.569 32.1191 124.617 43.0901 124.28 53.2501L123.69 71.0001H115.345H107V38.4231V5.84609L104.077 2.92309C102.082 0.928088 100.152 9.12233e-05 98 9.12233e-05C95.848 9.12233e-05 93.918 0.928088 91.923 2.92309L89 5.84609V38.4231V71.0001H80.655H72.31L71.72 53.2501C71.383 43.0901 70.431 32.1191 69.493 27.5921C67.314 17.0811 62.519 5.03609 59.404 2.25009C55.998 -0.795909 51.059 -0.710905 47.646 2.45209C44.221 5.62609 44.191 9.92109 47.539 17.4911C51.71 26.9241 53.007 34.4791 53.676 53.2501L54.31 71.0001H45.272H36.235L35.787 54.7501C35.392 40.3751 35.025 37.5771 32.612 30.5001C27.194 14.6091 16.228 -0.02891 9.787 0.03009C7.979 0.04709 5.712 0.519093 4.75 1.08009ZM42.687 108.974C33.431 112.591 20.036 125.024 18.408 131.512C17.476 135.223 20.677 140.453 28.253 147.599C37.495 156.319 44.191 159.471 53.5 159.485C59.317 159.494 61.645 158.953 67.274 156.289C77.634 151.385 88.987 139.161 88.996 132.9C89.004 127.304 76.787 114.707 66.745 109.956C59.16 106.368 50.285 106.006 42.687 108.974ZM129.255 109.904C119.151 114.768 106.996 127.33 107.004 132.9C107.013 139.108 118.562 151.475 128.939 156.389C134.338 158.945 136.744 159.496 142.521 159.498C152.526 159.501 160.369 155.502 169.771 145.605C180.444 134.368 180.278 130.975 168.486 119.388C160.043 111.094 152.727 107.595 143 107.201C136.364 106.933 134.78 107.244 129.255 109.904ZM48.5 125.922C46.3 126.969 43.152 128.945 41.505 130.314L38.511 132.802L40.504 135.005C41.601 136.216 44.434 138.342 46.8 139.728C52.577 143.114 57.36 142.466 64.009 137.395L68.978 133.606L66.756 131.24C60.944 125.054 54.357 123.135 48.5 125.922ZM138.386 125.063C136.674 125.571 133.375 127.677 131.057 129.743L126.841 133.5L131.901 137.343C138.65 142.468 143.407 143.124 149.2 139.728C151.566 138.342 154.351 136.269 155.389 135.122C157.684 132.587 156.742 131.097 150.58 127.511C145.438 124.519 142.329 123.895 138.386 125.063ZM91.923 162.923C89.043 165.804 89 166.008 89 176.973C89 184.805 89.435 188.941 90.47 190.941C92.356 194.589 96.918 196.273 101.03 194.84C105.82 193.17 107 189.638 107 176.973C107 166.008 106.957 165.804 104.077 162.923C102.082 160.928 100.152 160 98 160C95.848 160 93.918 160.928 91.923 162.923ZM94.5 232.155C91.026 234.055 90 236.229 90 241.691V247H98H106V242.082C106 235.732 105.37 234.242 101.928 232.463C98.591 230.737 97.197 230.679 94.5 232.155Z" fill="url(#paint0_linear_55_11)"/>
576
576
  <defs><linearGradient id="paint0_linear_55_11" x1="98" y1="0.000244141" x2="98" y2="247" gradientUnits="userSpaceOnUse">
577
577
  <stop stop-color="#03B79C"/><stop offset="0.504808" stop-color="#4EDAA4"/><stop offset="0.865285" stop-color="#84F3AA"/>
578
- </linearGradient></defs></svg>`;var ze=`
578
+ </linearGradient></defs></svg>`;var it=`
579
579
  /* \u2500\u2500 Utilities \u2500\u2500 */
580
580
  function esc(s){if(!s)return '';return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
581
581
  function stripAnsi(s){return s?s.replace(/\\u001b\\[[0-9;]*m/g,'').replace(/\\x1b\\[[0-9;]*m/g,''):''}
@@ -597,11 +597,25 @@ body{
597
597
  tags:t.tags,failure:t.failure,filePath:t.filePath,suiteName:t.suiteName,
598
598
  testType:t.testType,isFlaky:t.isFlaky,retryStatus:t.retryStatus,
599
599
  expectedStatus:t.expectedStatus,actualStatus:t.actualStatus,
600
- artifacts:t.artifacts,networkRequests:t.networkRequests||[],steps:[],apiCalls:[],actions:t.actions||[],consoleLogs:t.consoleLogs||[]
600
+ artifacts:t.artifacts,visual:t.visual||[],networkRequests:t.networkRequests||[],steps:[],apiCalls:[],actions:t.actions||[],consoleLogs:t.consoleLogs||[],
601
+ apiAssertions:t.apiAssertions||[],
602
+ apiCallCount:0,apiStatusMix:{'2xx':0,'3xx':0,'4xx':0,'5xx':0,'error':0},
603
+ assertionsPassed:0,assertionsFailed:0,
604
+ device:t.device||null,aiDefects:t.aiDefects||[]
601
605
  };
606
+ var _as=testMap[t.testId].apiAssertions;
607
+ for(var _ai=0;_ai<_as.length;_ai++){
608
+ if(_as[_ai].status==='failed')testMap[t.testId].assertionsFailed++;
609
+ else testMap[t.testId].assertionsPassed++;
610
+ }
602
611
  }
603
612
  if(entry.type==='api_call'){
604
- testMap[t.testId].apiCalls.push({
613
+ var _mt=testMap[t.testId];
614
+ _mt.apiCallCount++;
615
+ var _code=entry.response&&typeof entry.response.status==='number'?entry.response.status:null;
616
+ var _bucket=_code===null?'error':(_code>=500?'5xx':(_code>=400?'4xx':(_code>=300?'3xx':(_code>=200?'2xx':'error'))));
617
+ _mt.apiStatusMix[_bucket]++;
618
+ _mt.apiCalls.push({
605
619
  callId:entry.callId,method:entry.method,url:entry.url,
606
620
  responseTime:entry.responseTime,timestamp:entry.timestamp,
607
621
  request:entry.request,response:entry.response,assertions:entry.assertions
@@ -649,7 +663,7 @@ body{
649
663
  var st={
650
664
  testId:ix2.id,title:ix2.title,status:ix2.status,duration:ix2.duration,
651
665
  filePath:ix2.filePath,tags:ix2.tags||[],isFlaky:ix2.status==='flaky',
652
- testType:'e2e',retryCount:grp.attempts.length,suiteName:'',
666
+ testType:ix2.testType||'e2e',retryCount:grp.attempts.length,suiteName:'',
653
667
  startedAt:'',completedAt:'',
654
668
  networkRequests:[],steps:[],apiCalls:[],actions:[],consoleLogs:[],
655
669
  artifacts:null,failure:null,retryStatus:null,expectedStatus:null,actualStatus:null,
@@ -657,6 +671,18 @@ body{
657
671
  hasActionSteps:ix2.hasActionSteps,hasArtifacts:ix2.hasArtifacts,
658
672
  errorMessage:ix2.errorMessage,networkCount:ix2.networkCount||0,
659
673
  consoleCount:ix2.consoleCount||0,actionCount:ix2.actionCount||0,
674
+ /* Carried from the index so the grid and the filter bar can count
675
+ visual comparisons without opening every test's detail file. */
676
+ visualCount:ix2.visualCount||0,visualFailedCount:ix2.visualFailedCount||0,
677
+ domChangeCount:ix2.domChangeCount||0,visualDiffPath:ix2.visualDiffPath||null,
678
+ /* API and mobile evidence, same rule: the grid must never open a
679
+ test's detail file to draw a row. */
680
+ apiCallCount:ix2.apiCallCount||0,apiStatusMix:ix2.apiStatusMix||null,
681
+ assertionsPassed:ix2.assertionsPassed||0,assertionsFailed:ix2.assertionsFailed||0,
682
+ firstFailedAssertion:ix2.firstFailedAssertion||null,
683
+ device:ix2.device||null,stepKinds:ix2.stepKinds||null,
684
+ deviceLogErrors:ix2.deviceLogErrors||0,hasCrashLog:!!ix2.hasCrashLog,
685
+ hasVideo:!!ix2.hasVideo,aiDefectCount:ix2.aiDefectCount||0,
660
686
  retryAttempts:grp.attempts.slice().sort(function(a,b){return a.retryIndex-b.retryIndex;}).map(function(a){
661
687
  return {testId:a.id,retryIndex:a.retryIndex,status:a.status,duration:a.duration,errorMessage:a.errorMessage};
662
688
  })
@@ -709,9 +735,7 @@ body{
709
735
  var summaryEl=document.getElementById('summary-strip');
710
736
  var filterEl=document.getElementById('filter-bar');
711
737
  var testGridEl=document.getElementById('test-grid');
712
- var drawerEl=document.getElementById('drawer');
713
738
  var drawerBodyEl=document.getElementById('drawer-body');
714
- var drawerBackdropEl=document.getElementById('drawer-backdrop');
715
739
 
716
740
  /* \u2500\u2500 Render: Run Meta \u2500\u2500 */
717
741
  function renderRunMeta(){
@@ -731,15 +755,81 @@ body{
731
755
  }
732
756
 
733
757
  /* \u2500\u2500 Render: Summary \u2500\u2500 */
734
- function renderSummary(){
735
- var s=data.summary;var h='';
736
- h+='<div class="summary-chip s-total"><div class="s-count">'+s.total+'</div><div class="s-label">Total</div></div>';
737
- h+='<div class="summary-chip s-passed"><div class="s-count">'+s.passed+'</div><div class="s-label">Passed</div></div>';
738
- h+='<div class="summary-chip s-failed"><div class="s-count">'+s.failed+'</div><div class="s-label">Failed</div></div>';
739
- h+='<div class="summary-chip s-flaky"><div class="s-count">'+s.flaky+'</div><div class="s-label">Flaky</div></div>';
740
- h+='<div class="summary-chip s-skipped"><div class="s-count">'+s.skipped+'</div><div class="s-label">Skipped</div></div>';
741
- if(s.timedout!==undefined)h+='<div class="summary-chip s-timedout"><div class="s-count">'+s.timedout+'</div><div class="s-label">Timeout</div></div>';
742
- summaryEl.innerHTML=h;
758
+ /* renderSummary lives in html-js-layout: the six equal stat chips were
759
+ replaced by the verdict band, which collapses empty statuses. */
760
+
761
+ /* Longest test in the run, so each bar reads as "relative to the slowest".
762
+ Recomputed per grid render because filtering changes the set. */
763
+ var _maxDur=1;
764
+ function rowBarPct(d){
765
+ var pct=Math.round(((d||0)/_maxDur)*100);
766
+ /* A bar too short to see says nothing; floor it at something visible. */
767
+ return Math.max(4,Math.min(100,pct));
768
+ }
769
+
770
+ /* Small square icons: what was captured, before you open the test. */
771
+
772
+ /* Both report modes reach these renderers with different shapes: the
773
+ streaming index carries counters, the embedded testMap carries the arrays.
774
+ Reading only the counters made the evidence column, the needs-attention
775
+ chips and the diff thumbnail vanish in embedded mode. */
776
+ function evVisualCount(t){return t.visual?t.visual.length:(t.visualCount||0);}
777
+ function evVisualFailed(t){
778
+ if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}
779
+ return t.visualFailedCount||0;
780
+ }
781
+ function evDomChanges(t){
782
+ if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)n+=(t.visual[i].domChangeCount||0);return n;}
783
+ return t.domChangeCount||0;
784
+ }
785
+ function evHasArtifacts(t){
786
+ if(t.artifacts)return !!(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);
787
+ return !!t.hasArtifacts;
788
+ }
789
+ function evNetworkCount(t){return t.networkRequests?t.networkRequests.length:(t.networkCount||0);}
790
+ function evConsoleCount(t){return t.consoleLogs?t.consoleLogs.length:(t.consoleCount||0);}
791
+ function evDiffPath(t){
792
+ if(t.visualDiffPath)return t.visualDiffPath;
793
+ if(t.visual){for(var i=0;i<t.visual.length;i++){if(t.visual[i].diff)return t.visual[i].diff;}}
794
+ return null;
795
+ }
796
+
797
+ function rowEvidence(t){
798
+ var h='<div class="trow-evidence">';
799
+ var any=false;
800
+ if(typeof apiRowEvidence==='function'){
801
+ var _ae=apiRowEvidence(t);
802
+ if(_ae){h+=_ae;any=true;}
803
+ }
804
+ if(typeof mobileRowEvidence==='function'){
805
+ var _me=mobileRowEvidence(t);
806
+ if(_me){h+=_me;any=true;}
807
+ }
808
+ var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t);
809
+ var _net=evNetworkCount(t),_con=evConsoleCount(t);
810
+ if(_vis>0){
811
+ any=true;
812
+ h+='<span class="tev'+(_visFail>0?' tev-visual':'')+'" title="'+(_visFail>0?'Visual comparison failed':'Visual comparison')+'"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 3v18"/></svg></span>';
813
+ }
814
+ if(_dom>0){
815
+ any=true;
816
+ h+='<span class="tev tev-dom" title="'+_dom+' DOM elements changed"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3"/></svg></span>';
817
+ }
818
+ if(evHasArtifacts(t)){
819
+ any=true;
820
+ h+='<span class="tev" title="Video and trace"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21"/></svg></span>';
821
+ }
822
+ if(_net>0){
823
+ any=true;
824
+ h+='<span class="tev" title="'+_net+' network requests"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h4l3 8 4-16 3 8h4"/></svg></span>';
825
+ }
826
+ if(_con>0){
827
+ any=true;
828
+ h+='<span class="tev" title="'+_con+' console messages"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l6-6-6-6M12 19h8"/></svg></span>';
829
+ }
830
+ /* Keep the column even when empty so the rows stay aligned. */
831
+ if(!any)h+='<span style="width:18px"></span>';
832
+ return h+'</div>';
743
833
  }
744
834
 
745
835
  /* \u2500\u2500 Virtual Grid: progressive chunked rendering \u2500\u2500 */
@@ -771,7 +861,7 @@ body{
771
861
  var t=g.tests[i];
772
862
  h+='<div class="test-row" data-testid="'+esc(t.testId)+'">';
773
863
  h+='<div class="status-indicator si-'+t.status+'"></div>';
774
- h+='<div class="test-row-info">';
864
+ h+='<div class="test-row-info tri-inline">';
775
865
  h+='<div class="test-row-title" title="'+esc(t.title)+'">'+esc(shortTitle(t.title))+'</div>';
776
866
  var hasBadges=(t.testType&&t.testType!=='unknown')||t.isFlaky||(t.tags&&t.tags.length>0);
777
867
  if(hasBadges){
@@ -783,7 +873,9 @@ body{
783
873
  h+='</div>';
784
874
  }
785
875
  h+='</div>';
786
- h+='<span class="test-row-dur">'+fmtDur(t.duration)+'</span>';
876
+ h+=rowEvidence(t);
877
+ h+='<div class="trow-time"><div class="trow-bar"><span class="tb-'+t.status+'" style="width:'+rowBarPct(t.duration)+'%"></span></div>';
878
+ h+='<span class="test-row-dur">'+fmtDur(t.duration)+'</span></div>';
787
879
  h+='<svg class="test-row-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>';
788
880
  h+='</div>';
789
881
  if(t.retryAttempts&&t.retryAttempts.length>0){
@@ -844,6 +936,10 @@ body{
844
936
  function renderTestGrid(){
845
937
  if(_vgObserver){_vgObserver.disconnect();_vgObserver=null;}
846
938
  var filtered=getFilteredTests();
939
+ /* The bar on each row is relative to the slowest test CURRENTLY SHOWN, so
940
+ filtering to the slow ones still spreads them across the full width. */
941
+ _maxDur=1;
942
+ for(var md=0;md<filtered.length;md++){if(filtered[md].duration>_maxDur)_maxDur=filtered[md].duration;}
847
943
  if(filtered.length===0){testGridEl.innerHTML='<div class="no-tests">No tests match your filters</div>';_vgGroups=[];return;}
848
944
  _vgGroups=_vgBuildGroups(filtered);
849
945
  _vgRenderedTo=0;
@@ -872,17 +968,51 @@ body{
872
968
 
873
969
  function renderArtifactColumn(artifacts){
874
970
  if(!artifacts)return '<div class="artifact-empty">No artifacts captured</div>';
875
- var hasS=!!artifacts.screenshot,hasV=!!artifacts.video;
876
- if(!hasS&&!hasV)return '<div class="artifact-empty">No artifacts captured</div>';
971
+ var hasS=!!artifacts.screenshot,hasV=!!artifacts.video,hasT=!!artifacts.trace;
972
+ if(!hasS&&!hasV&&!hasT)return '<div class="artifact-empty">No artifacts captured</div>';
973
+ var tabs=[];
974
+ if(hasS)tabs.push('screenshot');
975
+ if(hasV)tabs.push('video');
976
+ if(hasT)tabs.push('trace');
977
+
877
978
  var h='';
878
- if(hasS&&hasV){
979
+ if(tabs.length>1){
879
980
  h+='<div class="seg-ctrl" data-seg-group="artifacts">';
880
- h+='<button class="seg-btn active" data-seg="screenshot"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>Screenshot</button>';
881
- h+='<button class="seg-btn" data-seg="video"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21"/></svg>Video</button>';
981
+ for(var ti=0;ti<tabs.length;ti++){
982
+ var t=tabs[ti],ico='';
983
+ if(t==='screenshot')ico='<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>';
984
+ else if(t==='video')ico='<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21"/></svg>';
985
+ else ico='<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v6H4zM4 14h16v6H4z"/></svg>';
986
+ var label=t==='screenshot'?'Screenshot':(t==='video'?'Video':'Trace');
987
+ h+='<button class="seg-btn'+(ti===0?' active':'')+'" data-seg="'+t+'">'+ico+label+'</button>';
988
+ }
882
989
  h+='</div>';
883
990
  }
884
- if(hasS){h+='<div class="seg-pane active" data-seg-pane="screenshot"><img class="artifact-col-img" src="'+esc(artifacts.screenshot)+'" loading="lazy" alt="Screenshot"><div class="artifact-col-caption"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>Click to enlarge</div></div>';}
885
- if(hasV){h+='<div class="seg-pane'+(hasS?'':' active')+'" data-seg-pane="video"><div class="artifact-col-video"><video controls preload="metadata" src="'+esc(artifacts.video)+'"></video></div></div>';}
991
+
992
+ /* No lazy loading. A lazily-loaded image inside a display:none pane is
993
+ never fetched by Chrome, and every pane but the first starts hidden \u2014
994
+ the screenshot simply never appeared. The same bug was fixed for the
995
+ visual comparison images and left standing here. */
996
+ if(hasS){
997
+ h+='<div class="seg-pane'+(tabs[0]==='screenshot'?' active':'')+'" data-seg-pane="screenshot">';
998
+ h+='<img class="artifact-col-img" src="'+esc(artifacts.screenshot)+'" alt="Screenshot">';
999
+ h+='<div class="artifact-col-caption"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>Click to enlarge</div></div>';
1000
+ }
1001
+ if(hasV){
1002
+ h+='<div class="seg-pane'+(tabs[0]==='video'?' active':'')+'" data-seg-pane="video">';
1003
+ h+='<div class="artifact-col-video"><video controls preload="metadata" src="'+esc(artifacts.video)+'"></video></div>';
1004
+ h+='<div class="artifact-col-caption">'+esc(artifacts.video.split('/').pop())+'</div></div>';
1005
+ }
1006
+ /* The trace is the thing a failure is actually debugged from, and the
1007
+ report had no way to reach it even though it was captured and copied. */
1008
+ if(hasT){
1009
+ h+='<div class="seg-pane'+(tabs[0]==='trace'?' active':'')+'" data-seg-pane="trace">';
1010
+ h+='<div class="trace-open"><a class="trace-link" href="'+esc(artifacts.trace)+'" download>';
1011
+ h+='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg>';
1012
+ h+='Download '+esc(artifacts.trace.split('/').pop())+'</a>';
1013
+ h+='<div class="trace-hint">Open it with <code>npx playwright show-trace &lt;file&gt;</code>, or drop it on <code>trace.playwright.dev</code>.</div>';
1014
+ h+='</div></div>';
1015
+ }
886
1016
  return h;
887
1017
  }
888
1018
 
@@ -907,11 +1037,9 @@ body{
907
1037
  h+='</div><div class="detail-duration">'+fmtDur(t.duration)+'</div></div></div>';
908
1038
 
909
1039
  /* \u2500\u2500 Failure panel (full width) \u2500\u2500 */
910
- if((t.status==='failed'||t.status==='flaky')&&t.failure)h+=renderFailure(t.failure,t.filePath,t.title,t.tags);
911
- else if(t.status==='failed'&&!t.failure)h+='<div style="margin-bottom:20px;padding:14px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px;font-size:12px;color:var(--fg-2);font-style:italic">No diagnostic information available</div>';
912
-
913
1040
  /* \u2500\u2500 Stacked sections: 1) Artifacts 2) Timeline / Network 3) API Calls \u2500\u2500 */
914
- var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video);
1041
+ var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video||t.artifacts.trace);
1042
+ var hasVisual=t.visual&&t.visual.length>0;
915
1043
  var hasSteps=t.steps&&t.steps.length>0;
916
1044
  var hasApiCalls=t.apiCalls&&t.apiCalls.length>0;
917
1045
  var hasActions=t.actions&&t.actions.length>0;
@@ -922,30 +1050,69 @@ body{
922
1050
  var baseTime=0;
923
1051
  if(hasVideo&&t.startedAt){baseTime=Date.parse(t.startedAt);}
924
1052
  if(!baseTime&&hasVideo&&t.consoleLogs&&t.consoleLogs.length>0&&t.consoleLogs[0].timestamp){baseTime=Date.parse(t.consoleLogs[0].timestamp);}
925
- if(hasArt||hasSteps||hasApiCalls||hasActions||hasConsoleLogs||hasNetworkReqs){
1053
+
1054
+ var hasFailure=(t.status==='failed'||t.status==='flaky')&&t.failure;
1055
+ var hasDom=false;
1056
+ if(t.visual){for(var dv=0;dv<t.visual.length;dv++){if(t.visual[dv].domChangeCount)hasDom=true;}}
1057
+
1058
+ /* What the rail is allowed to offer \u2014 built from what actually rendered,
1059
+ so a jump can never land on a section that is not there. */
1060
+ var present={};
1061
+ if(hasFailure)present.failure={};
1062
+ if(hasVisual)present.visual={n:t.visual.length};
1063
+ if(hasDom)present.dom={n:(function(){var n=0;for(var q=0;q<t.visual.length;q++)n+=t.visual[q].domChangeCount||0;return n;})()};
1064
+ if(hasArt)present.artifacts={};
1065
+ if(hasActions)present.actions={n:t.actions.length};
1066
+ if(hasSteps)present.timeline={n:t.steps.length};
1067
+ var _asserts=t.apiAssertions||[];
1068
+ var hasAssertions=_asserts.length>0;
1069
+ if(hasAssertions){
1070
+ var _af=0;
1071
+ for(var _q=0;_q<_asserts.length;_q++)if(_asserts[_q].status==='failed')_af++;
1072
+ present.assertions={n:_asserts.length,bad:_af>0};
1073
+ }
1074
+ if(hasApiCalls)present.api={n:t.apiCalls.length};
1075
+ if(!hasSteps&&hasConsoleLogs)present.console={n:t.consoleLogs.length};
1076
+ if(!hasSteps&&hasNetworkReqs)present.network={n:t.networkRequests.length};
1077
+
1078
+ var anySection=hasArt||hasVisual||hasSteps||hasApiCalls||hasActions||hasConsoleLogs||hasNetworkReqs||hasAssertions;
1079
+
1080
+ h+='<div class="drawer-layout">';
1081
+ h+='<div class="drawer-rail">'+renderDrawerRail(present,t)+'</div>';
1082
+ h+='<div class="drawer-main">';
1083
+
1084
+ if(hasFailure)h+='<div class="drawer-section" data-section="failure">'+renderFailure(t.failure,t.filePath,t.title,t.tags)+'</div>';
1085
+ else if(t.status==='failed')h+='<div class="drawer-section" data-section="failure" style="padding:14px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px;font-size:12px;color:var(--fg-2);font-style:italic">No diagnostic information available</div>';
1086
+
1087
+ if(anySection){
926
1088
  h+='<div class="drawer-sections">';
927
- if(hasArt)h+='<div class="drawer-section">'+renderArtifactColumn(t.artifacts)+'</div>';
928
- if(hasActions)h+='<div class="drawer-section">'+renderActionsColumn(t.actions,hasVideo)+'</div>';
929
- if(hasSteps){var _pgMeta=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class="drawer-section">'+renderTimelineColumn(t.steps,t.networkRequests,t.consoleLogs,t.testType,hasVideo,baseTime,_pgMeta)+'</div>';}
930
- if(!hasSteps&&hasNetworkReqs&&typeof renderNetworkDevTools==='function'){var _pgMeta2=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class="drawer-section">'+renderNetworkDevTools(t.networkRequests,false,0,_pgMeta2)+'</div>';}
931
- if(!hasSteps&&hasConsoleLogs)h+='<div class="drawer-section">'+renderConsoleColumn(t.consoleLogs,t.testType,hasVideo,baseTime)+'</div>';
932
- if(hasApiCalls)h+='<div class="drawer-section">'+renderApiCallsColumn(t.apiCalls)+'</div>';
1089
+ if(hasVisual)h+='<div class="drawer-section" data-section="visual">'+renderVisualSection(t.visual)+'</div>';
1090
+ if(hasArt)h+='<div class="drawer-section" data-section="artifacts">'+renderArtifactColumn(t.artifacts)+'</div>';
1091
+ if(hasActions)h+='<div class="drawer-section" data-section="actions">'+renderActionsColumn(t.actions,hasVideo)+'</div>';
1092
+ if(hasSteps){var _pgMeta=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class="drawer-section" data-section="timeline">'+renderTimelineColumn(t.steps,t.networkRequests,t.consoleLogs,t.testType,hasVideo,baseTime,_pgMeta)+'</div>';}
1093
+ if(!hasSteps&&hasNetworkReqs&&typeof renderNetworkDevTools==='function'){var _pgMeta2=t._networkTotal?{testId:t._testId,page:t._networkPage||1,total:t._networkTotal,pageSize:t._networkPageSize||500}:null;h+='<div class="drawer-section" data-section="network">'+renderNetworkDevTools(t.networkRequests,false,0,_pgMeta2)+'</div>';}
1094
+ if(!hasSteps&&hasConsoleLogs)h+='<div class="drawer-section" data-section="console">'+renderConsoleColumn(t.consoleLogs,t.testType,hasVideo,baseTime)+'</div>';
1095
+ /* Assertions before the calls: the assertion is what failed, the call
1096
+ is only where it happened. */
1097
+ if(hasAssertions&&typeof renderAssertionsSection==='function')
1098
+ h+='<div class="drawer-section" data-section="assertions">'+renderAssertionsSection(_asserts,t.apiCalls)+'</div>';
1099
+ if(hasApiCalls)h+='<div class="drawer-section" data-section="api">'+renderApiCallsColumn(t.apiCalls,_asserts)+'</div>';
933
1100
  h+='</div>';
934
1101
  }
935
1102
 
1103
+ h+='</div></div>';
1104
+
936
1105
  h+='</div>';
937
1106
  drawerBodyEl.innerHTML=h;
938
- drawerBodyEl.scrollTop=0;
939
1107
  /* Wire up video \u2194 action step sync if both are present */
940
1108
  if(typeof syncActionsWithVideo==='function')syncActionsWithVideo();
941
1109
  }
942
1110
 
943
1111
  /* \u2500\u2500 Drawer open / close \u2500\u2500 */
944
- function openDrawer(testId){
1112
+ /* Fetch (streaming) and render one test into the detail view. The routing
1113
+ around it lives in html-js-layout; this only fills the page. */
1114
+ function loadAndRenderTest(testId){
945
1115
  if(data.reportMode==='streaming'){
946
- drawerEl.classList.add('open');
947
- drawerBackdropEl.classList.add('open');
948
- document.body.style.overflow='hidden';
949
1116
  if(_detailCache[testId]){renderDrawer(testId,_detailCache[testId]);return;}
950
1117
  drawerBodyEl.innerHTML='<div class="detail-loading"><div class="loading-spinner"></div><div class="loading-text">Loading test details...</div></div>';
951
1118
  if(window.__trStreamingFetchDetail){
@@ -963,6 +1130,7 @@ body{
963
1130
  merged.actions=detail.actions||[];
964
1131
  merged.apiAssertions=detail.apiAssertions||[];
965
1132
  merged.artifacts=detail.artifacts||null;
1133
+ merged.visual=detail.visual||[];
966
1134
  merged.failure=detail.failureDiagnostic||detail.failure||null;
967
1135
  merged.steps=detail.navigations||[];
968
1136
  if(detail.startedAt)merged.startedAt=detail.startedAt;
@@ -977,7 +1145,7 @@ body{
977
1145
  merged.apiCalls=[];
978
1146
  cacheDetail(testId,merged);
979
1147
  renderDrawer(testId,merged);
980
- /* Lazy-load heavy data after drawer is visible */
1148
+ /* Lazy-load heavy data once the page is showing */
981
1149
  loadHeavyData(testId,merged);
982
1150
  });
983
1151
  }else{
@@ -988,9 +1156,6 @@ body{
988
1156
  return;
989
1157
  }
990
1158
  renderDrawer(testId);
991
- drawerEl.classList.add('open');
992
- drawerBackdropEl.classList.add('open');
993
- document.body.style.overflow='hidden';
994
1159
  }
995
1160
 
996
1161
  /* Lazy-load heavy data (network, console, API calls) after drawer renders */
@@ -1039,14 +1204,6 @@ body{
1039
1204
  });
1040
1205
  }
1041
1206
  }
1042
- function closeDrawer(){
1043
- drawerEl.classList.remove('open');
1044
- drawerBackdropEl.classList.remove('open');
1045
- document.body.style.overflow='';
1046
- /* Pause any playing video */
1047
- var v=drawerBodyEl.querySelector('video');
1048
- if(v)v.pause();
1049
- }
1050
1207
 
1051
1208
  /* \u2500\u2500 Search (debounced) \u2500\u2500 */
1052
1209
  var _searchTimer=0;
@@ -1054,7 +1211,7 @@ body{
1054
1211
  clearTimeout(_searchTimer);
1055
1212
  _searchTimer=setTimeout(function(){searchQuery=q;applyFilters();},150);
1056
1213
  }
1057
- `;var $e=`
1214
+ `;var ot=`
1058
1215
  /* \u2500\u2500 Network Visualization Helpers \u2500\u2500 */
1059
1216
  var RES_COLORS={xhr:'#60a5fa',document:'#818cf8',script:'#fbbf24',stylesheet:'#a78bfa',image:'#34d399',font:'#f472b6',other:'#6b7280'};
1060
1217
  var RES_LABELS={xhr:'XHR',document:'Doc',script:'JS',stylesheet:'CSS',image:'Img',font:'Font',other:'Other'};
@@ -1518,7 +1675,20 @@ body{
1518
1675
  var sid='s-'+Math.random().toString(36).substr(2,8);
1519
1676
  var cpid='cp-'+Math.random().toString(36).substr(2,8);
1520
1677
  var h='<div class="failure-panel"><div class="failure-panel-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6M9 9l6 6"/></svg>Failure Details<button class="copy-prompt-btn" id="'+cpid+'"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>Copy Prompt</button></div>';
1521
- h+='<div class="failure-message">'+esc(stripAnsi(failure.message))+'</div>';
1678
+ /* Split the call log off the message. It is the longest part by far and
1679
+ almost never what you want first \u2014 leaving it expanded is what pushed
1680
+ the visual comparison two screens down. */
1681
+ var fullMsg=stripAnsi(failure.message||'');
1682
+ var logAt=fullMsg.indexOf('Call log:');
1683
+ var headMsg=logAt>=0?fullMsg.slice(0,logAt).trim():fullMsg;
1684
+ var callLog=logAt>=0?fullMsg.slice(logAt).trim():'';
1685
+ h+='<div class="failure-message">'+esc(headMsg)+'</div>';
1686
+ if(callLog){
1687
+ var lid='cl-'+Math.random().toString(36).substr(2,8);
1688
+ var lines=callLog.split(String.fromCharCode(10)).length-1;
1689
+ h+='<button class="stack-toggle-btn" data-stack="'+lid+'">Show call log ('+lines+' steps)</button>';
1690
+ h+='<div class="stack-trace" id="'+lid+'">'+esc(callLog)+'</div>';
1691
+ }
1522
1692
  if(failure.code)h+=renderCodeSnippet(failure.code);
1523
1693
  if(failure.line!==null&&failure.line!==undefined)h+='<div class="line-marker">Line '+failure.line+'</div>';
1524
1694
  if(failure.stack){h+='<button class="stack-toggle-btn" data-stack="'+sid+'">Show stack trace</button><div class="stack-trace" id="'+sid+'">'+esc(stripAnsi(failure.stack))+'</div>';}
@@ -1579,8 +1749,44 @@ body{
1579
1749
  /* \u2500\u2500 API Calls Column (for API-only tests) \u2500\u2500 */
1580
1750
  var API_METHOD_COLORS={GET:'#60a5fa',POST:'#34d399',PUT:'#fbbf24',PATCH:'#a78bfa',DELETE:'#f87171',HEAD:'#6b7280',OPTIONS:'#6b7280'};
1581
1751
 
1582
- function renderApiCallDetail(ac,idx){
1752
+ /* Response size, derived. ApiCallRecord has no size field \u2014 the body is
1753
+ what we have, so measure it and say so rather than render an empty cell. */
1754
+ function apiBodyBytes(ac){
1755
+ var b=ac.responseBody;
1756
+ if(b===null||b===undefined)return null;
1757
+ var s=(typeof b==='string')?b:JSON.stringify(b);
1758
+ if(!s)return 0;
1759
+ /* base64 bodies decode to 3 bytes per 4 characters. */
1760
+ if(ac.isBinary)return Math.floor(s.length*3/4);
1761
+ return s.length;
1762
+ }
1763
+
1764
+ function renderCookieTable(cookies,emptyLabel){
1765
+ if(!cookies||cookies.length===0)return '<div class="ndt-body-empty">'+esc(emptyLabel)+'</div>';
1766
+ var h='<div class="ck-tbl">';
1767
+ for(var i=0;i<cookies.length;i++){
1768
+ var c=cookies[i];
1769
+ h+='<span class="ck-k">'+esc(c.name)+'</span>';
1770
+ h+='<span class="ck-v">'+esc(c.value);
1771
+ if(c.attributes)h+=' <span class="ck-attr">'+esc(c.attributes)+'</span>';
1772
+ h+='</span>';
1773
+ }
1774
+ return h+'</div>';
1775
+ }
1776
+
1777
+ /* The assertions made against one call. */
1778
+ function assertionsForCall(assertions,callId){
1779
+ var out=[];
1780
+ if(!assertions||!callId)return out;
1781
+ for(var i=0;i<assertions.length;i++){
1782
+ if(assertions[i].callId===callId)out.push(assertions[i]);
1783
+ }
1784
+ return out;
1785
+ }
1786
+
1787
+ function renderApiCallDetail(ac,idx,callAsserts){
1583
1788
  var did='ndt-d-api-'+idx;
1789
+ callAsserts=callAsserts||[];
1584
1790
  var reqHeaders=ac.requestHeaders||null;
1585
1791
  var reqBody=ac.requestBody||null;
1586
1792
  var resHeaders=ac.responseHeaders||null;
@@ -1596,6 +1802,9 @@ body{
1596
1802
  h+='<button class="ndt-detail-tab" data-ndt-tab="req-body" data-ndt-target="'+did+'">Payload</button>';
1597
1803
  h+='<button class="ndt-detail-tab" data-ndt-tab="res-headers" data-ndt-target="'+did+'">Response Headers</button>';
1598
1804
  h+='<button class="ndt-detail-tab" data-ndt-tab="res-body" data-ndt-target="'+did+'">Response</button>';
1805
+ var hasCookies=(ac.requestCookies&&ac.requestCookies.length)||(ac.responseCookies&&ac.responseCookies.length);
1806
+ if(hasCookies)h+='<button class="ndt-detail-tab" data-ndt-tab="cookies" data-ndt-target="'+did+'">Cookies</button>';
1807
+ if(callAsserts.length>0)h+='<button class="ndt-detail-tab" data-ndt-tab="asserts" data-ndt-target="'+did+'">Assertions <span style="opacity:.6">'+callAsserts.length+'</span></button>';
1599
1808
  h+='</div>';
1600
1809
 
1601
1810
  /* General tab */
@@ -1604,21 +1813,61 @@ body{
1604
1813
  h+='<span class="ndt-general-k">Method</span><span class="ndt-general-v">'+esc(ac.method)+'</span>';
1605
1814
  var sc=ac.responseStatusCode!=null?ac.responseStatusCode:0;
1606
1815
  var st=ac.responseStatusText||'';
1607
- h+='<span class="ndt-general-k">Status Code</span><span class="ndt-general-v'+(sc>=400?' ndt-err':'')+'">'+sc+(st?' '+esc(st):'')+'</span>';
1816
+ if(ac.error){
1817
+ /* The call never got a response. Rendering "0" here read as a status. */
1818
+ h+='<span class="ndt-general-k">Result</span><span class="ndt-general-v ndt-err-note">Request failed &mdash; '+esc(String(ac.error))+'</span>';
1819
+ }else{
1820
+ h+='<span class="ndt-general-k">Status Code</span><span class="ndt-general-v'+(sc>=400?' ndt-err':'')+'">'+sc+(st?' '+esc(st):'')+'</span>';
1821
+ }
1608
1822
  h+='<span class="ndt-general-k">Response Time</span><span class="ndt-general-v">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'N/A')+'</span>';
1823
+ var _bz=apiBodyBytes(ac);
1824
+ if(_bz!==null)h+='<span class="ndt-general-k">Response Size</span><span class="ndt-general-v">'+fmtBytes(_bz)+(ac.isBinary?' (binary)':'')+'</span>';
1609
1825
  if(ac.timestamp)h+='<span class="ndt-general-k">Timestamp</span><span class="ndt-general-v">'+fmtDate(ac.timestamp)+'</span>';
1610
1826
  h+='</div></div>';
1611
1827
 
1612
1828
  h+='<div class="ndt-detail-pane" data-ndt-pane="req-headers">'+renderNdtHeaders(reqHeaders,'request','Request headers not available &mdash; only explicitly passed headers are captured for API tests')+'</div>';
1613
1829
  h+='<div class="ndt-detail-pane" data-ndt-pane="req-body">'+renderNdtBody(reqBodyStr,false,false,ct,'No request body')+'</div>';
1614
1830
  h+='<div class="ndt-detail-pane" data-ndt-pane="res-headers">'+renderNdtHeaders(resHeaders,'response')+'</div>';
1615
- h+='<div class="ndt-detail-pane" data-ndt-pane="res-body">'+renderNdtBody(resBodyStr,false,false,ct,'No response body captured')+'</div>';
1831
+ h+='<div class="ndt-detail-pane" data-ndt-pane="res-body">'+renderNdtBody(resBodyStr,false,ac.isBinary,ct,ac.error?'No response \u2014 the request failed':'No response body captured')+'</div>';
1832
+ if(hasCookies){
1833
+ h+='<div class="ndt-detail-pane" data-ndt-pane="cookies">';
1834
+ h+='<div class="ndt-headers-label">Request</div>'+renderCookieTable(ac.requestCookies,'No cookies sent');
1835
+ h+='<div class="ndt-headers-label">Response</div>'+renderCookieTable(ac.responseCookies,'No cookies set');
1836
+ h+='</div>';
1837
+ }
1838
+ if(callAsserts.length>0){
1839
+ h+='<div class="ndt-detail-pane" data-ndt-pane="asserts">'+renderCallAssertions(callAsserts)+'</div>';
1840
+ }
1616
1841
  h+='</div>';
1617
1842
  return h;
1618
1843
  }
1619
1844
 
1620
- function renderApiCallsColumn(apiCalls){
1845
+ /* The assertions on one call, inside that call's detail pane. The run-level
1846
+ section above lists them all; this answers "what did this call fail?". */
1847
+ function renderCallAssertions(list){
1848
+ var h='<div class="asec">';
1849
+ for(var i=0;i<list.length;i++){
1850
+ var a=list[i],failed=a.status==='failed';
1851
+ h+='<div class="arow-item'+(failed?' is-failed':'')+'">';
1852
+ h+=failed
1853
+ ? '<svg style="flex-shrink:0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>'
1854
+ : '<svg style="flex-shrink:0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>';
1855
+ h+='<span class="atype">'+esc(String(a.type||'custom'))+'</span>';
1856
+ h+='<span class="aexpr">'+esc(typeof assertionExpression==='function'?assertionExpression(a):(a.expression||''))+'</span>';
1857
+ h+='</div>';
1858
+ if(failed&&typeof fmtAssertValue==='function'){
1859
+ h+='<div class="adiff">';
1860
+ h+='<span class="adiff-i"><span class="adiff-k">expected</span><span class="adiff-v adiff-exp">'+esc(fmtAssertValue(a.expected))+'</span></span>';
1861
+ h+='<span class="adiff-i"><span class="adiff-k">received</span><span class="adiff-v adiff-act">'+esc(fmtAssertValue(a.actual))+'</span></span>';
1862
+ h+='</div>';
1863
+ }
1864
+ }
1865
+ return h+'</div>';
1866
+ }
1867
+
1868
+ function renderApiCallsColumn(apiCalls,assertions){
1621
1869
  if(!apiCalls||apiCalls.length===0)return '<div class="artifact-empty">No API calls captured</div>';
1870
+ assertions=assertions||[];
1622
1871
  var uid='api-'+Math.random().toString(36).substr(2,6);
1623
1872
  var h='<div class="ndt" id="'+uid+'">';
1624
1873
 
@@ -1640,16 +1889,28 @@ body{
1640
1889
  for(var i=0;i<apiCalls.length;i++){
1641
1890
  var ac=apiCalls[i];
1642
1891
  var sc=ac.responseStatusCode!=null?ac.responseStatusCode:0;
1643
- var isFail=sc>=400||sc===0;
1892
+ var isFail=sc>=400||sc===0||!!ac.error;
1893
+ var callAsserts=assertionsForCall(assertions,ac.id||ac.callId);
1894
+ var aFail=0;
1895
+ for(var aj=0;aj<callAsserts.length;aj++)if(callAsserts[aj].status==='failed')aFail++;
1644
1896
  h+='<div class="ndt-row'+(isFail?' ndt-row--fail':'')+'" data-ndt-idx="api-'+i+'" data-ndt-type="'+esc(ac.method)+'" data-ndt-url="'+esc(ac.url.toLowerCase())+'">';
1645
- h+='<span class="ndt-status '+ndtStatusClass(sc)+'">'+sc+'</span>';
1897
+ h+='<span class="ndt-status '+ndtStatusClass(sc)+'" title="'+(ac.error?esc(String(ac.error)):'')+'">'+(ac.error?'ERR':sc)+'</span>';
1646
1898
  h+='<span class="ndt-method" style="color:'+(API_METHOD_COLORS[ac.method]||'#6b7280')+'">'+esc(ac.method)+'</span>';
1647
1899
  h+='<span class="ndt-url" title="'+esc(ac.url)+'">'+esc(urlPath(ac.url))+'</span>';
1648
- h+='<span class="ndt-type"></span>';
1649
- h+='<span class="ndt-size"></span>';
1900
+ /* Was: two permanently empty cells. Now the two things a reader of an
1901
+ API row actually wants \u2014 did its assertions hold, and how big was it. */
1902
+ if(callAsserts.length===0){
1903
+ h+='<span class="ndt-type"><span class="ndt-assert ndt-assert-none">&mdash;</span></span>';
1904
+ }else if(aFail>0){
1905
+ h+='<span class="ndt-type"><span class="ndt-assert ndt-assert-bad">'+aFail+' \u2717</span></span>';
1906
+ }else{
1907
+ h+='<span class="ndt-type"><span class="ndt-assert ndt-assert-ok">'+callAsserts.length+' \u2713</span></span>';
1908
+ }
1909
+ var bz=apiBodyBytes(ac);
1910
+ h+='<span class="ndt-size">'+(bz===null?'':fmtBytes(bz))+'</span>';
1650
1911
  h+='<span class="ndt-time">'+(ac.responseTimeMs!=null?fmtDur(ac.responseTimeMs):'')+'</span>';
1651
1912
  h+='</div>';
1652
- h+=renderApiCallDetail(ac,i);
1913
+ h+=renderApiCallDetail(ac,i,callAsserts);
1653
1914
  }
1654
1915
  h+='</div></div>';
1655
1916
  return h;
@@ -1685,8 +1946,44 @@ body{
1685
1946
  var countEl=ndt.querySelector('.ndt-count');
1686
1947
  if(countEl)countEl.textContent=visible+' / '+rows.length+' requests';
1687
1948
  }
1688
- `;var je=`
1689
- var _filterState={status:{},type:{},specFile:{}};
1949
+ `;var lt=`
1950
+ var _filterState={status:{},type:{},specFile:{},visual:{},evidence:{}};
1951
+
1952
+ /* Evidence filters: one predicate per chip, so API and mobile runs share the
1953
+ mechanism and a chip only appears when something in the run matches it. */
1954
+ var EVIDENCE_FILTERS=[
1955
+ {v:'api-error',label:'4xx/5xx seen',test:function(t){
1956
+ var m=t.apiStatusMix;
1957
+ if(!m)return false;
1958
+ return (m['4xx']||0)+(m['5xx']||0)+(m['error']||0)>0;
1959
+ }},
1960
+ {v:'assert-failed',label:'Failed assertion',test:function(t){return (t.assertionsFailed||0)>0;}},
1961
+ {v:'crash',label:'Crash logged',test:function(t){return !!t.hasCrashLog;}},
1962
+ {v:'device-error',label:'Device log errors',test:function(t){return (t.deviceLogErrors||0)>0;}},
1963
+ {v:'video',label:'Has recording',test:function(t){return !!t.hasVideo||!!(t.artifacts&&t.artifacts.video);}},
1964
+ {v:'ai-defect',label:'AI defects',test:function(t){return (t.aiDefectCount||0)>0;}}
1965
+ ];
1966
+
1967
+ function _evidenceCounts(){
1968
+ var counts={};
1969
+ for(var f=0;f<EVIDENCE_FILTERS.length;f++)counts[EVIDENCE_FILTERS[f].v]=0;
1970
+ for(var i=0;i<tests.length;i++){
1971
+ for(var j=0;j<EVIDENCE_FILTERS.length;j++){
1972
+ if(EVIDENCE_FILTERS[j].test(tests[i]))counts[EVIDENCE_FILTERS[j].v]++;
1973
+ }
1974
+ }
1975
+ return counts;
1976
+ }
1977
+
1978
+ /* Visual counts reach the grid differently in the two report modes: the
1979
+ embedded report inlines the comparisons on each test, while the streaming
1980
+ index carries only counts so the grid never has to open a detail file.
1981
+ Both are read here so the filter works the same either way. */
1982
+ function _visCount(t){return t.visual?t.visual.length:(t.visualCount||0);}
1983
+ function _visFailed(t){
1984
+ if(t.visual){var n=0;for(var i=0;i<t.visual.length;i++)if(t.visual[i].status==='failed')n++;return n;}
1985
+ return t.visualFailedCount||0;
1986
+ }
1690
1987
  var _totalTests=0;
1691
1988
  var _originalSummary=null;
1692
1989
 
@@ -1695,18 +1992,22 @@ function renderFilterBar(){
1695
1992
  var drawerBodyEl=document.getElementById('filter-drawer-body');
1696
1993
  var statusCounts={passed:0,failed:0,flaky:0,skipped:0,timedout:0};
1697
1994
  var typeCounts={},specCounts={};
1995
+ var visualCounts={has:0,failed:0};
1698
1996
  for(var i=0;i<tests.length;i++){
1699
1997
  var t=tests[i];
1700
1998
  if(statusCounts[t.status]!==undefined)statusCounts[t.status]++;
1701
1999
  var tp=t.testType||'unknown';
1702
2000
  typeCounts[tp]=(typeCounts[tp]||0)+1;
1703
2001
  if(t.filePath)specCounts[t.filePath]=(specCounts[t.filePath]||0)+1;
2002
+ if(_visCount(t)>0){visualCounts.has++;if(_visFailed(t)>0)visualCounts.failed++;}
1704
2003
  }
1705
2004
  var hasStatus=Object.keys(_filterState.status).length>0;
1706
2005
  var hasType=Object.keys(_filterState.type).length>0;
1707
2006
  var hasSpec=Object.keys(_filterState.specFile).length>0;
1708
- var hasAny=hasStatus||hasType||hasSpec||!!searchQuery;
1709
- var hasAdvanced=hasType||hasSpec;
2007
+ var hasVisual=Object.keys(_filterState.visual).length>0;
2008
+ var hasEvidenceSel=Object.keys(_filterState.evidence).length>0;
2009
+ var hasAny=hasStatus||hasType||hasSpec||hasVisual||hasEvidenceSel||!!searchQuery;
2010
+ var hasAdvanced=hasType||hasSpec||hasVisual||hasEvidenceSel;
1710
2011
 
1711
2012
  /* \u2500\u2500 Filter Bar (search + status chips + filter icon) \u2500\u2500 */
1712
2013
  var h='';
@@ -1763,6 +2064,35 @@ function renderFilterBar(){
1763
2064
  d+='</div></div>';
1764
2065
  }
1765
2066
 
2067
+ /* Visual section \u2014 only when the run actually produced comparisons */
2068
+ if(visualCounts.has>0){
2069
+ d+='<div class="filter-drawer-section"><span class="filter-drawer-section-label">Visual</span>';
2070
+ d+='<div class="filter-chips">';
2071
+ var visOpts=[{v:'has',label:'Has visual',cnt:visualCounts.has},{v:'failed',label:'Visual failed',cnt:visualCounts.failed}];
2072
+ for(var vi=0;vi<visOpts.length;vi++){
2073
+ var vo=visOpts[vi];
2074
+ var vcls='filter-chip'+(!!_filterState.visual[vo.v]?' active':'')+(vo.cnt===0?' filter-chip--dimmed':'');
2075
+ d+='<button class="'+vcls+'" data-dimension="visual" data-value="'+vo.v+'">'+vo.label+' <span class="chip-count">'+vo.cnt+'</span></button>';
2076
+ }
2077
+ d+='</div></div>';
2078
+ }
2079
+
2080
+ /* Evidence section \u2014 a chip per predicate that anything in the run meets */
2081
+ var evCounts=_evidenceCounts();
2082
+ var evAny=false;
2083
+ for(var ek in evCounts){if(evCounts.hasOwnProperty(ek)&&evCounts[ek]>0)evAny=true;}
2084
+ if(evAny){
2085
+ d+='<div class="filter-drawer-section"><span class="filter-drawer-section-label">Evidence</span>';
2086
+ d+='<div class="filter-chips">';
2087
+ for(var ei=0;ei<EVIDENCE_FILTERS.length;ei++){
2088
+ var ef=EVIDENCE_FILTERS[ei],ecnt=evCounts[ef.v];
2089
+ if(ecnt===0)continue;
2090
+ var ecls='filter-chip'+(!!_filterState.evidence[ef.v]?' active':'');
2091
+ d+='<button class="'+ecls+'" data-dimension="evidence" data-value="'+ef.v+'">'+ef.label+' <span class="chip-count">'+ecnt+'</span></button>';
2092
+ }
2093
+ d+='</div></div>';
2094
+ }
2095
+
1766
2096
  /* Actions (Clear all + indicator) */
1767
2097
  d+='<div class="filter-drawer-actions">';
1768
2098
  d+='<button class="filter-clear" id="filter-clear" style="display:'+(hasAny?'inline-block':'none')+'">Clear all</button>';
@@ -1774,7 +2104,7 @@ function renderFilterBar(){
1774
2104
  }
1775
2105
 
1776
2106
  function initFilters(){
1777
- _filterState={status:{},type:{},specFile:{}};
2107
+ _filterState={status:{},type:{},specFile:{},visual:{},evidence:{}};
1778
2108
  _totalTests=tests.length;
1779
2109
  _originalSummary={total:data.summary.total,passed:data.summary.passed,
1780
2110
  failed:data.summary.failed,flaky:data.summary.flaky,
@@ -1785,12 +2115,27 @@ function getFilteredTests(){
1785
2115
  var hasStatus=Object.keys(_filterState.status).length>0;
1786
2116
  var hasType=Object.keys(_filterState.type).length>0;
1787
2117
  var hasSpec=Object.keys(_filterState.specFile).length>0;
2118
+ var hasVisual=Object.keys(_filterState.visual).length>0;
2119
+ var hasEvidence=Object.keys(_filterState.evidence).length>0;
1788
2120
  var out=[];
1789
2121
  for(var i=0;i<tests.length;i++){
1790
2122
  var t=tests[i];
1791
2123
  if(hasStatus&&!_filterState.status[t.status])continue;
1792
2124
  if(hasType&&!_filterState.type[t.testType||'unknown'])continue;
1793
2125
  if(hasSpec&&!_filterState.specFile[t.filePath])continue;
2126
+ if(hasVisual){
2127
+ /* OR within the dimension, matching every other filter here. */
2128
+ var visOk=(_filterState.visual.has&&_visCount(t)>0)||(_filterState.visual.failed&&_visFailed(t)>0);
2129
+ if(!visOk)continue;
2130
+ }
2131
+ if(hasEvidence){
2132
+ var evOk=false;
2133
+ for(var ef2=0;ef2<EVIDENCE_FILTERS.length;ef2++){
2134
+ var cand=EVIDENCE_FILTERS[ef2];
2135
+ if(_filterState.evidence[cand.v]&&cand.test(t)){evOk=true;break;}
2136
+ }
2137
+ if(!evOk)continue;
2138
+ }
1794
2139
  if(searchQuery){
1795
2140
  var q=searchQuery.toLowerCase();
1796
2141
  var match=t.title.toLowerCase().indexOf(q)>=0||t.filePath.toLowerCase().indexOf(q)>=0||(t.suiteName&&t.suiteName.toLowerCase().indexOf(q)>=0);
@@ -1813,7 +2158,7 @@ function toggleFilter(dim,val){
1813
2158
  }
1814
2159
 
1815
2160
  function clearAllFilters(){
1816
- _filterState={status:{},type:{},specFile:{}};
2161
+ for(var dim in _filterState){if(_filterState.hasOwnProperty(dim))_filterState[dim]={};}
1817
2162
  searchQuery='';
1818
2163
  var si=document.getElementById('search-input');
1819
2164
  if(si)si.value='';
@@ -1856,14 +2201,14 @@ function _updateCard(cls,val){
1856
2201
  var el=document.querySelector('.'+cls+' .s-count');
1857
2202
  if(el)el.textContent=''+val;
1858
2203
  }
1859
- `;var We=`
2204
+ `;var dt=`
1860
2205
  /* \u2500\u2500 Event Delegation \u2500\u2500 */
1861
2206
  document.addEventListener('click',function(e){
1862
2207
  var target=e.target;
1863
2208
 
1864
2209
  /* Test row click \u2192 open drawer */
1865
2210
  var row=target.closest('.test-row');
1866
- if(row){openDrawer(row.getAttribute('data-testid'));return;}
2211
+ if(row){openTest(row.getAttribute('data-testid'));return;}
1867
2212
 
1868
2213
  /* Filter chip (multi-dimensional) */
1869
2214
  var chip=target.closest('.filter-chip');
@@ -2011,8 +2356,9 @@ function _updateCard(cls,val){
2011
2356
  if(themeBtn){setTheme(themeBtn.getAttribute('data-theme-val'));return;}
2012
2357
 
2013
2358
  /* Drawer close */
2014
- if(target.closest('#drawer-close')){closeDrawer();return;}
2015
- if(target.closest('#drawer-backdrop')){closeDrawer();return;}
2359
+ if(target.closest('#detail-back')){closeTest();return;}
2360
+ if(target.closest('#detail-prev')){stepTest(-1);return;}
2361
+ if(target.closest('#detail-next')){stepTest(1);return;}
2016
2362
  });
2017
2363
 
2018
2364
  /* Search input \u2014 main test search + network filter search */
@@ -2030,7 +2376,7 @@ function _updateCard(cls,val){
2030
2376
 
2031
2377
  /* Keyboard */
2032
2378
  document.addEventListener('keydown',function(e){
2033
- if(e.key==='Escape'){closeLightbox();closeDrawer();closeFilterDrawer();}
2379
+ if(e.key==='Escape'){closeLightbox();closeTest();closeFilterDrawer();}
2034
2380
  });
2035
2381
 
2036
2382
  /* \u2500\u2500 Theme Management \u2500\u2500 */
@@ -2126,7 +2472,7 @@ function _updateCard(cls,val){
2126
2472
  var d=document.getElementById('filter-drawer');if(d)d.classList.remove('open');
2127
2473
  var b=document.getElementById('filter-drawer-backdrop');if(b)b.classList.remove('open');
2128
2474
  }
2129
- `;var Ge=`
2475
+ `;var ct=`
2130
2476
  /* \u2500\u2500 Category badge helpers \u2500\u2500 */
2131
2477
  function actionBadgeClass(cat){
2132
2478
  if(cat==='assertion')return 'action-badge-assertion';
@@ -2172,7 +2518,7 @@ function _updateCard(cls,val){
2172
2518
  h+='</div>';
2173
2519
  return h;
2174
2520
  }
2175
- `;var Ve=`
2521
+ `;var ut=`
2176
2522
  /* \u2500\u2500 Console Log Level Colors & Labels \u2500\u2500 */
2177
2523
  var LOG_COLORS={log:'#9ca3af',warn:'#fbbf24',error:'#f87171',info:'#60a5fa',debug:'#a78bfa',stdout:'#9ca3af',stderr:'#f87171'};
2178
2524
  var LOG_LABELS={log:'LOG',warn:'WARN',error:'ERR',info:'INFO',debug:'DBG',stdout:'OUT',stderr:'ERR'};
@@ -2359,7 +2705,7 @@ function _updateCard(cls,val){
2359
2705
  }
2360
2706
  return h;
2361
2707
  }
2362
- `;var Ke=`
2708
+ `;var pt=`
2363
2709
  /* \u2500\u2500 Artifact Utilities \u2500\u2500 */
2364
2710
  function formatBytes(bytes){
2365
2711
  if(bytes===0)return '0 B';
@@ -2368,41 +2714,1334 @@ function _updateCard(cls,val){
2368
2714
  if(i>=sizes.length)i=sizes.length-1;
2369
2715
  return parseFloat((bytes/Math.pow(k,i)).toFixed(1))+' '+sizes[i];
2370
2716
  }
2371
- `;var bs=`
2717
+ `;var ft=`
2718
+ /* \u2500\u2500 Visual Regression \u2500\u2500 */
2719
+
2720
+ function visStatusLabel(status){
2721
+ if(status==='new')return 'new baseline';
2722
+ if(status==='updated')return 'baseline updated';
2723
+ return status;
2724
+ }
2725
+
2726
+ function visMetricsText(v){
2727
+ var parts=[];
2728
+ if(typeof v.diffPixels==='number')parts.push(v.diffPixels.toLocaleString()+' px');
2729
+ if(typeof v.diffPixelRatio==='number')parts.push((v.diffPixelRatio*100).toFixed(2)+'%');
2730
+ if(v.width&&v.height){
2731
+ var dims=v.width+'x'+v.height;
2732
+ /* Call out a size change explicitly: a comparison can fail purely
2733
+ because the render changed shape, and the pixel count alone reads
2734
+ like a content change when that happens. */
2735
+ if(v.actualWidth&&v.actualHeight&&(v.actualWidth!==v.width||v.actualHeight!==v.height))
2736
+ dims+=' -> '+v.actualWidth+'x'+v.actualHeight;
2737
+ parts.push(dims);
2738
+ }
2739
+ return parts.join(' \\u00b7 ');
2740
+ }
2741
+
2742
+ /* Deliberately no loading="lazy" on any image below. Every pane but the
2743
+ first is built inside a display:none container, and Chrome does not
2744
+ reliably start a lazy load when such an image is later revealed \u2014 the
2745
+ diff pane came up blank every time. A test has a handful of comparisons
2746
+ at most and they are only built when the drawer opens, so eager loading
2747
+ is by far the cheaper mistake. */
2748
+ function visImg(src,alt){
2749
+ return '<img class="artifact-col-img" src="'+esc(src)+'" alt="'+esc(alt)+'">';
2750
+ }
2751
+
2752
+ function visSideBySide(v){
2753
+ var h='<div class="vis-pair">';
2754
+ if(v.baseline)h+='<div><div class="vis-pane-label">Baseline</div>'+visImg(v.baseline,'Baseline')+'</div>';
2755
+ if(v.actual)h+='<div><div class="vis-pane-label">Actual</div>'+visImg(v.actual,'Actual')+'</div>';
2756
+ if(v.previous)h+='<div><div class="vis-pane-label">Previous attempt</div>'+visImg(v.previous,'Previous attempt')+'</div>';
2757
+ return h+'</div>';
2758
+ }
2759
+
2760
+ function visSlider(v,id){
2761
+ var h='<div class="vis-stack" data-vis-stack="'+id+'">';
2762
+ h+='<img src="'+esc(v.baseline)+'" alt="Baseline">';
2763
+ h+='<img class="vis-top" data-vis-wipe="'+id+'" src="'+esc(v.actual)+'" alt="Actual" style="clip-path:inset(0 0 0 50%)">';
2764
+ h+='<div class="vis-handle" data-vis-handle="'+id+'" style="left:50%"></div>';
2765
+ h+='</div>';
2766
+ h+='<div class="vis-range-row"><span>Baseline</span><input type="range" class="vis-range" min="0" max="100" value="50" data-vis-slider="'+id+'" aria-label="Wipe between baseline and actual"><span>Actual</span></div>';
2767
+ return h;
2768
+ }
2769
+
2770
+ function visOnion(v,id){
2771
+ var h='<div class="vis-stack">';
2772
+ h+='<img src="'+esc(v.baseline)+'" alt="Baseline">';
2773
+ h+='<img class="vis-top" data-vis-fade="'+id+'" src="'+esc(v.actual)+'" alt="Actual" style="opacity:0.5">';
2774
+ h+='</div>';
2775
+ h+='<div class="vis-range-row"><span>Baseline</span><input type="range" class="vis-range" min="0" max="100" value="50" data-vis-onion="'+id+'" aria-label="Blend between baseline and actual"><span>Actual</span></div>';
2776
+ return h;
2777
+ }
2778
+
2779
+ function visModes(v){
2780
+ var modes=[];
2781
+ if(v.baseline&&v.actual){
2782
+ modes.push({key:'side',label:'Side by side'});
2783
+ modes.push({key:'wipe',label:'Slider'});
2784
+ modes.push({key:'onion',label:'Onion skin'});
2785
+ }else if(v.baseline||v.actual){
2786
+ modes.push({key:'side',label:'Image'});
2787
+ }
2788
+ if(v.diff)modes.push({key:'diff',label:'Diff'});
2789
+ /* Only when a DOM baseline existed to compare against \u2014 an assertion
2790
+ written with Playwright's own expect compares pixels only. */
2791
+ if(v.domChangeCount!==undefined)modes.push({key:'dom',label:'Elements'});
2792
+ return modes;
2793
+ }
2794
+
2795
+ function visPaneBody(mode,v,id){
2796
+ if(mode==='side')return visSideBySide(v);
2797
+ if(mode==='wipe')return visSlider(v,id);
2798
+ if(mode==='onion')return visOnion(v,id);
2799
+ if(mode==='dom')return renderDomPane(v);
2800
+ return '<div class="vis-diff-wrap">'+visImg(v.diff,'Pixel diff')+'</div>';
2801
+ }
2802
+
2803
+ function renderVisualCard(v,idx){
2804
+ var id='v'+idx;
2805
+ var h='<div class="vis-card">';
2806
+ h+='<div class="vis-head"><span class="vis-name">'+esc(v.name)+'</span>';
2807
+ h+='<span class="vis-badge vb-'+esc(v.status)+'">'+esc(visStatusLabel(v.status))+'</span>';
2808
+ if(v.domChangeCount)h+='<span class="vis-badge vb-dom">'+v.domChangeCount+' element'+(v.domChangeCount===1?'':'s')+'</span>';
2809
+ var metrics=visMetricsText(v);
2810
+ if(metrics)h+='<span class="vis-metrics">'+esc(metrics)+'</span>';
2811
+ h+='</div>';
2812
+
2813
+ var modes=visModes(v);
2814
+ if(modes.length===0){
2815
+ h+='<div class="vis-note">No images were captured for this comparison.</div></div>';
2816
+ return h;
2817
+ }
2818
+
2819
+ if(v.status==='new')h+='<div class="vis-note">No baseline existed, so this render was written as one. Nothing was compared.</div>';
2820
+ else if(v.status==='updated')h+='<div class="vis-note">Run with --update-snapshots: the baseline was rewritten rather than compared.</div>';
2821
+
2822
+ if(modes.length>1){
2823
+ h+='<div class="seg-ctrl" data-seg-group="visual-'+id+'">';
2824
+ for(var m=0;m<modes.length;m++){
2825
+ h+='<button class="seg-btn'+(m===0?' active':'')+'" data-seg="'+id+'-'+modes[m].key+'">'+esc(modes[m].label)+'</button>';
2826
+ }
2827
+ h+='</div>';
2828
+ }
2829
+ for(var p=0;p<modes.length;p++){
2830
+ h+='<div class="seg-pane'+(p===0?' active':'')+'" data-seg-pane="'+id+'-'+modes[p].key+'">'+visPaneBody(modes[p].key,v,id)+'</div>';
2831
+ }
2832
+ return h+'</div>';
2833
+ }
2834
+
2835
+ function renderVisualSection(visual){
2836
+ if(!visual||visual.length===0)return '';
2837
+ var failed=0;
2838
+ for(var f=0;f<visual.length;f++)if(visual[f].status==='failed')failed++;
2839
+ var h='<div class="vis-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 3v18"/></svg>Visual comparisons <span class="vis-count">'+visual.length+'</span>';
2840
+ /* A separator, because two bare adjacent numbers read as one. */
2841
+ if(failed>0)h+='<span class="vis-count vis-count-fail">\xB7 '+failed+' failed</span>';
2842
+ h+='</div><div class="vis-list">';
2843
+ for(var i=0;i<visual.length;i++)h+=renderVisualCard(visual[i],i);
2844
+ return h+'</div>';
2845
+ }
2846
+
2847
+ /* Slider and onion-skin are driven by range inputs, which fire 'input'
2848
+ rather than 'click' \u2014 the delegated click handler cannot see them. One
2849
+ document-level listener covers every card, including cards rendered into
2850
+ the drawer long after this runs. */
2851
+ document.addEventListener('input',function(e){
2852
+ var el=e.target;
2853
+ if(!el||!el.getAttribute)return;
2854
+
2855
+ var wipeId=el.getAttribute('data-vis-slider');
2856
+ if(wipeId){
2857
+ var card=el.closest('.vis-card');
2858
+ if(!card)return;
2859
+ var top=card.querySelector('[data-vis-wipe="'+wipeId+'"]');
2860
+ var handle=card.querySelector('[data-vis-handle="'+wipeId+'"]');
2861
+ if(top)top.style.clipPath='inset(0 0 0 '+el.value+'%)';
2862
+ if(handle)handle.style.left=el.value+'%';
2863
+ return;
2864
+ }
2865
+
2866
+ var fadeId=el.getAttribute('data-vis-onion');
2867
+ if(fadeId){
2868
+ var ocard=el.closest('.vis-card');
2869
+ if(!ocard)return;
2870
+ var otop=ocard.querySelector('[data-vis-fade="'+fadeId+'"]');
2871
+ if(otop)otop.style.opacity=String(el.value/100);
2872
+ }
2873
+ });
2874
+ `;var gt=`
2875
+ /* \u2500\u2500 DOM comparison \u2500\u2500 */
2876
+
2877
+ /* Rank-cycled so a row and its box always share a colour. */
2878
+ var DOM_TONES = ['#03b79c','#f59e0b','#60a5fa','#e879f9','#ef4444','#a3e635'];
2879
+ function domTone(rank){return DOM_TONES[(rank-1)%DOM_TONES.length];}
2880
+
2881
+ function domKindLabel(kind){
2882
+ if(kind==='restyled')return 'restyled';
2883
+ if(kind==='text')return 'text';
2884
+ if(kind==='resized')return 'resized';
2885
+ return kind;
2886
+ }
2887
+
2888
+ /* Percentages of the capture box: the image may be rendered at any size. */
2889
+ function domBoxStyle(rect,box){
2890
+ if(!rect||!box||!box[2]||!box[3])return null;
2891
+ var left=((rect[0]-box[0])/box[2])*100;
2892
+ var top=((rect[1]-box[1])/box[3])*100;
2893
+ var width=(rect[2]/box[2])*100;
2894
+ var height=(rect[3]/box[3])*100;
2895
+ /* An element scrolled out of the captured region has nothing to point at. */
2896
+ if(left>100||top>100||left+width<0||top+height<0)return null;
2897
+ return 'left:'+left.toFixed(3)+'%;top:'+top.toFixed(3)+'%;width:'+width.toFixed(3)+'%;height:'+height.toFixed(3)+'%';
2898
+ }
2899
+
2900
+ function renderDomOverlay(v){
2901
+ var h='<div class="dom-ov">';
2902
+ for(var i=0;i<v.domChanges.length;i++){
2903
+ var c=v.domChanges[i];
2904
+ /* A removed element has no box in this render; show where it used to be. */
2905
+ var style=domBoxStyle(c.rect||c.baselineRect,v.domCaptureBox);
2906
+ if(!style)continue;
2907
+ h+='<div class="dom-box" data-dom-box="'+c.rank+'" style="'+style+';border-color:'+domTone(c.rank)+'"></div>';
2908
+ }
2909
+ return h+'</div>';
2910
+ }
2911
+
2912
+ function renderDomRow(c){
2913
+ var h='<div class="dom-row" data-dom-row="'+c.rank+'">';
2914
+ h+='<span class="dom-swatch" style="background:'+domTone(c.rank)+'"></span>';
2915
+ h+='<span class="dom-label">'+esc(c.label)+'</span>';
2916
+ for(var k=0;k<c.kinds.length;k++){
2917
+ h+='<span class="dom-kind dk-'+esc(c.kinds[k])+'">'+esc(domKindLabel(c.kinds[k]))+'</span>';
2918
+ }
2919
+ h+='<span class="dom-area">'+(c.areaPct*100).toFixed(1)+'%</span>';
2920
+ h+='</div>';
2921
+
2922
+ var detail='';
2923
+ for(var a=0;a<(c.attrs||[]).length;a++){
2924
+ var at=c.attrs[a];
2925
+ detail+='<div class="dom-prop dom-prop-attr"><span class="dom-prop-name">'+esc(at.prop)+'</span>'+
2926
+ '<span class="dom-from">'+esc(at.from)+'</span>'+
2927
+ '<span class="dom-arrow">&rarr;</span>'+
2928
+ '<span class="dom-to">'+esc(at.to)+'</span></div>';
2929
+ }
2930
+ for(var s=0;s<(c.styles||[]).length;s++){
2931
+ var st=c.styles[s];
2932
+ detail+='<div class="dom-prop"><span class="dom-prop-name">'+esc(st.prop)+'</span>'+
2933
+ '<span class="dom-from">'+esc(st.from)+'</span>'+
2934
+ '<span class="dom-arrow">&rarr;</span>'+
2935
+ '<span class="dom-to">'+esc(st.to)+'</span></div>';
2936
+ }
2937
+ if(c.text){
2938
+ detail+='<div class="dom-prop"><span class="dom-prop-name">text</span>'+
2939
+ '<span class="dom-from">'+esc(c.text.from)+'</span>'+
2940
+ '<span class="dom-arrow">&rarr;</span>'+
2941
+ '<span class="dom-to">'+esc(c.text.to)+'</span></div>';
2942
+ }
2943
+ if(c.baselineRect&&c.rect){
2944
+ var moved=(c.baselineRect[0]!==c.rect[0]||c.baselineRect[1]!==c.rect[1]);
2945
+ var sized=(c.baselineRect[2]!==c.rect[2]||c.baselineRect[3]!==c.rect[3]);
2946
+ if(moved||sized){
2947
+ detail+='<div class="dom-prop"><span class="dom-prop-name">rect</span>'+
2948
+ '<span class="dom-from">'+c.baselineRect.join(', ')+'</span>'+
2949
+ '<span class="dom-arrow">&rarr;</span>'+
2950
+ '<span class="dom-to">'+c.rect.join(', ')+'</span></div>';
2951
+ }
2952
+ }
2953
+ if(detail)h+='<div class="dom-detail">'+detail+'</div>';
2954
+ return h;
2955
+ }
2956
+
2957
+ function renderDomPane(v){
2958
+ var changes=v.domChanges||[];
2959
+ if(changes.length===0){
2960
+ return '<div class="vis-note">The DOM matched its baseline exactly \\u2014 every element in the same place, with the same styles and text.</div>';
2961
+ }
2962
+
2963
+ var roots=[],shifted=[];
2964
+ for(var i=0;i<changes.length;i++){
2965
+ if(changes[i].consequence)shifted.push(changes[i]);else roots.push(changes[i]);
2966
+ }
2967
+
2968
+ var h='';
2969
+ var image=v.actual||v.baseline;
2970
+ if(image){
2971
+ h+='<div class="dom-figure">';
2972
+ h+='<img class="dom-img" src="'+esc(image)+'" alt="Changed elements">';
2973
+ h+=renderDomOverlay(v);
2974
+ h+='</div>';
2975
+ }
2976
+
2977
+ h+='<div class="dom-summary">';
2978
+ h+='<strong>'+(v.domChangeCount||changes.length)+'</strong> element'+((v.domChangeCount||changes.length)===1?'':'s')+' differ';
2979
+ if(shifted.length>0){
2980
+ /* Say what we actually know: these only moved. Not that the one above
2981
+ moved them \u2014 the diff does not prove causation and must not imply it. */
2982
+ h+=' \\u00b7 <strong>'+roots.length+'</strong> changed, <strong>'+shifted.length+'</strong> only shifted position';
2983
+ }
2984
+ if(v.domChangeCount&&v.domChangeCount>changes.length){
2985
+ h+=' \\u00b7 showing the top '+changes.length;
2986
+ }
2987
+ if(v.domTruncated)h+=' <span class="dom-truncated">capture truncated</span>';
2988
+ h+='</div>';
2989
+
2990
+ h+='<div class="dom-list">';
2991
+ for(var r=0;r<roots.length;r++)h+=renderDomRow(roots[r]);
2992
+ if(shifted.length>0){
2993
+ h+='<div class="dom-group-label">Moved or resized only</div>';
2994
+ for(var m=0;m<shifted.length;m++)h+=renderDomRow(shifted[m]);
2995
+ }
2996
+ h+='</div>';
2997
+ return h;
2998
+ }
2999
+
3000
+ /* Hover a row, highlight its box. Delegated so it covers cards that are
3001
+ rendered into the drawer long after this runs. */
3002
+ function domSetActive(card,rank,on){
3003
+ var box=card.querySelector('[data-dom-box="'+rank+'"]');
3004
+ if(box)box.classList.toggle('hi',on);
3005
+ var row=card.querySelector('[data-dom-row="'+rank+'"]');
3006
+ if(row)row.classList.toggle('hi',on);
3007
+ }
3008
+
3009
+ document.addEventListener('mouseover',function(e){
3010
+ var row=e.target.closest?e.target.closest('.dom-row'):null;
3011
+ if(!row)return;
3012
+ var card=row.closest('.vis-card');
3013
+ if(card)domSetActive(card,row.getAttribute('data-dom-row'),true);
3014
+ });
3015
+
3016
+ document.addEventListener('mouseout',function(e){
3017
+ var row=e.target.closest?e.target.closest('.dom-row'):null;
3018
+ if(!row)return;
3019
+ var card=row.closest('.vis-card');
3020
+ if(card&&!row.classList.contains('pinned'))domSetActive(card,row.getAttribute('data-dom-row'),false);
3021
+ });
3022
+ `;var ht=`
3023
+ /* \u2500\u2500 Verdict band \u2500\u2500 */
3024
+
3025
+ /* Both report modes hand us a summary; streaming's carries fewer fields. */
3026
+ function vNum(v){return typeof v==='number'?v:0;}
3027
+
3028
+ function renderVerdictBar(s){
3029
+ var order=[
3030
+ {k:'passed',n:vNum(s.passed)},{k:'failed',n:vNum(s.failed)},
3031
+ {k:'flaky',n:vNum(s.flaky)},{k:'timedout',n:vNum(s.timedout)},
3032
+ {k:'skipped',n:vNum(s.skipped)}
3033
+ ];
3034
+ var total=0;for(var i=0;i<order.length;i++)total+=order[i].n;
3035
+ if(total===0)return '<div class="verdict-bar"><span class="verdict-bar-empty"></span></div>';
3036
+ var h='<div class="verdict-bar">';
3037
+ for(var j=0;j<order.length;j++){
3038
+ if(order[j].n===0)continue;
3039
+ h+='<span class="vb-'+order[j].k+'" style="flex:'+order[j].n+'"></span>';
3040
+ }
3041
+ return h+'</div>';
3042
+ }
3043
+
3044
+ function renderVerdictHeadline(s){
3045
+ var failed=vNum(s.failed),total=vNum(s.total),flaky=vNum(s.flaky);
3046
+ if(failed>0)return failed+' of '+total+' test'+(total===1?'':'s')+' failed';
3047
+ if(flaky>0)return flaky+' of '+total+' test'+(total===1?'':'s')+' passed on retry';
3048
+ if(total===0)return 'No tests ran';
3049
+ return 'All '+total+' test'+(total===1?'':'s')+' passed';
3050
+ }
3051
+
3052
+ /* Where the failures are, when they are all in one place \u2014 saves a scan. */
3053
+ function failureLocation(){
3054
+ var files={};var n=0;
3055
+ for(var i=0;i<tests.length;i++){
3056
+ var t=tests[i];
3057
+ if(t.status!=='failed'&&t.status!=='timedout')continue;
3058
+ if(!files[t.filePath]){files[t.filePath]=1;n++;}
3059
+ }
3060
+ if(n!==1)return null;
3061
+ for(var f in files){if(files.hasOwnProperty(f))return f;}
3062
+ return null;
3063
+ }
3064
+
3065
+ function renderSummary(){
3066
+ var s=data.summary||{};
3067
+ var failed=vNum(s.failed),flaky=vNum(s.flaky);
3068
+ var cls=failed>0?'v-failed':(flaky>0?'':'v-passed');
3069
+
3070
+ var h='<div class="verdict"><div class="verdict-main '+cls+'">';
3071
+
3072
+ h+='<div class="verdict-head"><div class="verdict-title">'+esc(renderVerdictHeadline(s))+'</div>';
3073
+ var where=failed>0?failureLocation():null;
3074
+ if(where)h+='<div class="verdict-where">all in <code>'+esc(where.split('/').pop())+'</code></div>';
3075
+ h+='<div class="verdict-dur"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'+fmtDur(data.totalDuration||0)+'</div>';
3076
+ h+='</div>';
3077
+
3078
+ h+=renderVerdictBar(s);
3079
+
3080
+ /* Non-zero statuses get a real count; the rest collapse to one grey line
3081
+ rather than each taking a sixth of the width. */
3082
+ var kinds=[
3083
+ {k:'passed',l:'passed',c:'#22c55e'},{k:'failed',l:'failed',c:'#ef4444'},
3084
+ {k:'flaky',l:'flaky',c:'#f59e0b'},{k:'timedout',l:'timed out',c:'#f97316'},
3085
+ {k:'skipped',l:'skipped',c:'#6b7280'}
3086
+ ];
3087
+ var counts='',zeros=[];
3088
+ for(var m=0;m<kinds.length;m++){
3089
+ var kk=kinds[m],n=vNum(s[kk.k]);
3090
+ if(n>0){
3091
+ counts+='<div class="vc"><span class="vc-dot" style="background:'+kk.c+'"></span><span class="vc-n">'+n+'</span><span class="vc-l">'+kk.l+'</span></div>';
3092
+ }else{
3093
+ zeros.push('0 '+kk.l);
3094
+ }
3095
+ }
3096
+ h+='<div class="verdict-counts">'+counts;
3097
+ if(zeros.length>0)h+='<div class="verdict-zeros">'+esc(zeros.join(' \\u00b7 '))+'</div>';
3098
+ h+='</div>';
3099
+
3100
+ h+='</div>';
3101
+
3102
+ /* What this run actually captured \u2014 only tiles with something in them. */
3103
+ var tiles=[];
3104
+ if(vNum(s.visualChecks)>0)tiles.push({n:vNum(s.visualChecks),l:'visual checks',cls:''});
3105
+ if(vNum(s.visualDomChanges)>0)tiles.push({n:vNum(s.visualDomChanges),l:'DOM changes',cls:' vs-dom'});
3106
+ if(vNum(s.totalActionSteps)>0)tiles.push({n:vNum(s.totalActionSteps),l:'actions',cls:''});
3107
+ if(vNum(s.totalApiCalls)>0)tiles.push({n:vNum(s.totalApiCalls),l:'API calls',cls:''});
3108
+ if(vNum(s.totalNavigations)>0)tiles.push({n:vNum(s.totalNavigations),l:'navigations',cls:''});
3109
+
3110
+ if(tiles.length>0){
3111
+ h+='<div class="verdict-side"><div class="verdict-side-label">Captured this run</div><div class="verdict-side-grid">';
3112
+ for(var q=0;q<Math.min(tiles.length,4);q++){
3113
+ h+='<div><div class="vs-n'+tiles[q].cls+'">'+tiles[q].n.toLocaleString()+'</div><div class="vs-l">'+esc(tiles[q].l)+'</div></div>';
3114
+ }
3115
+ h+='</div></div>';
3116
+ }
3117
+
3118
+ h+='</div>';
3119
+ summaryEl.innerHTML=h;
3120
+ }
3121
+
3122
+ /* \u2500\u2500 Needs attention \u2500\u2500 */
3123
+
3124
+ /* The first line of the error, which is the part that says what happened.
3125
+ Playwright's colour codes are stripped by the reporter before this. */
3126
+ function causeLine(t){
3127
+ if(t.firstFailedAssertion&&typeof assertionSentence==='function'){
3128
+ return '<em>'+assertionSentence(t.firstFailedAssertion)+'</em>';
3129
+ }
3130
+ if(t.assertionsFailed>0&&typeof apiFailureSentence==='function'&&t.apiAssertions){
3131
+ var sentence=apiFailureSentence(t.apiAssertions);
3132
+ if(sentence)return '<em>'+sentence+'</em>';
3133
+ }
3134
+ if(evVisualFailed(t)>0&&t.errorMessage){
3135
+ var px=t.errorMessage.match(/([\\d,]+) pixels \\(ratio ([\\d.]+)/);
3136
+ if(px)return '<em>'+esc(px[1])+' px differ</em>';
3137
+ }
3138
+ if(!t.errorMessage)return '';
3139
+ var first=String(t.errorMessage).split('\\n')[0].trim();
3140
+ if(first.length>150)first=first.slice(0,150)+'\\u2026';
3141
+ return '<em>'+esc(first)+'</em>';
3142
+ }
3143
+
3144
+ function evidenceChips(t){
3145
+ var h='';
3146
+ /* These accessors read both report shapes \u2014 see html-js-render. */
3147
+ var _vis=evVisualCount(t),_visFail=evVisualFailed(t),_dom=evDomChanges(t),_net=evNetworkCount(t);
3148
+ if(_vis>0){
3149
+ h+='<span class="nchip'+(_visFail>0?' nchip-visual':'')+'"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 3v18"/></svg>'+(_visFail>0?'Visual diff':'Visual')+'</span>';
3150
+ }
3151
+ if(_dom>0){
3152
+ h+='<span class="nchip nchip-dom"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3"/></svg>'+_dom+' element'+(_dom===1?'':'s')+'</span>';
3153
+ }
3154
+ if(evHasArtifacts(t)){
3155
+ h+='<span class="nchip"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21"/></svg>Video</span>';
3156
+ }
3157
+ if(_net>0){
3158
+ h+='<span class="nchip"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h4l3 8 4-16 3 8h4"/></svg>'+_net+' requests</span>';
3159
+ }
3160
+ if(t.apiCallCount>0){
3161
+ h+='<span class="nchip">'+t.apiCallCount+' API call'+(t.apiCallCount===1?'':'s')+'</span>';
3162
+ }
3163
+ if(t.assertionsFailed>0){
3164
+ h+='<span class="nchip nchip-visual">'+t.assertionsFailed+' failed assertion'+(t.assertionsFailed===1?'':'s')+'</span>';
3165
+ }else if(t.assertionsPassed>0){
3166
+ h+='<span class="nchip">'+t.assertionsPassed+' assertion'+(t.assertionsPassed===1?'':'s')+'</span>';
3167
+ }
3168
+ if(typeof mobileEvidenceChips==='function')h+=mobileEvidenceChips(t);
3169
+ return h;
3170
+ }
3171
+
3172
+ function renderNeedsAttention(){
3173
+ var el=document.getElementById('needs-attention');
3174
+ if(!el)return;
3175
+
3176
+ var bad=[];
3177
+ for(var i=0;i<tests.length;i++){
3178
+ var st=tests[i].status;
3179
+ if(st==='failed'||st==='timedout'||st==='flaky')bad.push(tests[i]);
3180
+ }
3181
+ if(bad.length===0){el.innerHTML='';return;}
3182
+
3183
+ /* Hard failures first, then flakes; biggest problems at the top. */
3184
+ bad.sort(function(a,b){
3185
+ var rank=function(x){return x.status==='flaky'?1:0;};
3186
+ return rank(a)-rank(b);
3187
+ });
3188
+
3189
+ var h='<div class="needs"><div class="needs-head"><span class="needs-title">Needs attention</span><span class="needs-count">'+bad.length+'</span></div><div class="needs-list">';
3190
+
3191
+ for(var j=0;j<Math.min(bad.length,8);j++){
3192
+ var t=bad[j];
3193
+ h+='<div class="needs-card'+(t.status==='flaky'?' nc-flaky':'')+'" data-testid="'+esc(t.testId)+'">';
3194
+
3195
+ h+='<div class="needs-body">';
3196
+ h+='<div class="needs-row1"><span class="needs-name">'+esc(t.title)+'</span>';
3197
+ if(t.testType&&t.testType!=='unknown')h+='<span class="trb trb-type">'+esc(t.testType)+'</span>';
3198
+ if(t.status==='flaky')h+='<span class="trb trb-flaky">passed on retry</span>';
3199
+ h+='<span class="needs-file">'+esc(t.filePath)+'</span></div>';
3200
+
3201
+ var cause=causeLine(t);
3202
+ if(cause)h+='<div class="needs-cause">'+cause+'</div>';
3203
+
3204
+ var chips=evidenceChips(t);
3205
+ if(chips)h+='<div class="needs-chips">'+chips+'</div>';
3206
+ h+='</div>';
3207
+
3208
+ /* Show the failure, do not only describe it. */
3209
+ var _diff=evDiffPath(t);
3210
+ if(_diff){
3211
+ h+='<div class="needs-thumb"><img src="'+esc(_diff)+'" alt="Pixel diff"><div class="needs-thumb-cap">pixel diff</div></div>';
3212
+ }
3213
+
3214
+ h+='<div class="needs-right"><div class="needs-dur">'+fmtDur(t.duration)+'</div>';
3215
+ h+='<div class="needs-open">Open<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M9 18l6-6-6-6"/></svg></div></div>';
3216
+
3217
+ h+='</div>';
3218
+ }
3219
+
3220
+ if(bad.length>8)h+='<div class="needs-thumb-cap" style="text-align:left;padding:4px 2px">and '+(bad.length-8)+' more below</div>';
3221
+
3222
+ el.innerHTML=h+'</div></div>';
3223
+ }
3224
+
3225
+ /* \u2500\u2500 Detail section rail \u2500\u2500 */
3226
+
3227
+ var RAIL_SECTIONS=[
3228
+ {id:'failure',label:'Why it failed',fail:true},
3229
+ {id:'visual',label:'Visual'},
3230
+ {id:'dom',label:'Elements',dom:true},
3231
+ {id:'assertions',label:'Assertions'},
3232
+ {id:'artifacts',label:'Video & trace'},
3233
+ {id:'actions',label:'Actions'},
3234
+ {id:'timeline',label:'Timeline'},
3235
+ {id:'api',label:'API calls'},
3236
+ {id:'console',label:'Console'},
3237
+ {id:'network',label:'Network'}
3238
+ ];
3239
+
3240
+ function railIcon(id){
3241
+ if(id==='failure')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>';
3242
+ if(id==='visual')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M12 3v18"/></svg>';
3243
+ if(id==='dom')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 00-2 2v3M16 3h3a2 2 0 012 2v3M8 21H5a2 2 0 01-2-2v-3M16 21h3a2 2 0 002-2v-3"/></svg>';
3244
+ if(id==='artifacts')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21"/></svg>';
3245
+ if(id==='actions')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20V10M18 20V4M6 20v-4"/></svg>';
3246
+ if(id==='assertions')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>';
3247
+ if(id==='console')return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17l6-6-6-6M12 19h8"/></svg>';
3248
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12h4l3 8 4-16 3 8h4"/></svg>';
3249
+ }
3250
+
3251
+ /* Built from what the drawer actually rendered, so the rail can never
3252
+ offer a jump to a section that is not there. */
3253
+ function renderDrawerRail(present,t){
3254
+ var h='<div class="rail-group"><div class="rail-label">Evidence</div>';
3255
+ var first=true;
3256
+ for(var i=0;i<RAIL_SECTIONS.length;i++){
3257
+ var sec=RAIL_SECTIONS[i];
3258
+ var info=present[sec.id];
3259
+ if(!info)continue;
3260
+ h+='<button class="rail-item'+(sec.fail?' rail-fail':'')+(first?' active':'')+'" data-rail="'+sec.id+'">';
3261
+ h+=railIcon(sec.id);
3262
+ h+='<span class="rail-name">'+esc(sec.label)+'</span>';
3263
+ if(info.n)h+='<span class="rail-n'+(sec.dom?' rail-n-dom':'')+'">'+info.n+'</span>';
3264
+ h+='</button>';
3265
+ first=false;
3266
+ }
3267
+ h+='</div>';
3268
+
3269
+ h+='<div class="rail-meta"><div class="rail-label">Test</div><div class="rail-meta-rows">';
3270
+ if(t.testId)h+='<div class="rail-meta-row"><span class="rail-meta-k">ID</span><span class="rail-meta-v mono">'+esc(String(t.testId).substring(0,12))+'</span></div>';
3271
+ if(t.suiteName)h+='<div class="rail-meta-row"><span class="rail-meta-k">Suite</span><span class="rail-meta-v">'+esc(t.suiteName)+'</span></div>';
3272
+ h+='<div class="rail-meta-row"><span class="rail-meta-k">Retry</span><span class="rail-meta-v">'+(t.retryCount>0?t.retryCount+'':'none')+'</span></div>';
3273
+ h+='</div></div>';
3274
+ return h;
3275
+ }
3276
+
3277
+ /* Scroll the page so a section's top is just under the viewport top.
3278
+ A smooth scrollIntoView was silently dropped here: the rail
3279
+ item is a <button>, and a smooth scroll requested from inside its click
3280
+ handler never started \u2014 the section stayed off-screen and the rail entry
3281
+ did nothing. Instant scrolling from a computed offset always lands, and
3282
+ smooth is attempted first with a check that it actually moved. */
3283
+ function jumpToSection(el){
3284
+ if(!el)return;
3285
+ var pageY=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);
3286
+ var y=Math.max(0,el.getBoundingClientRect().top+pageY-12);
3287
+ try{
3288
+ window.scrollTo({top:y,behavior:'smooth'});
3289
+ }catch(e){
3290
+ window.scrollTo(0,y);
3291
+ }
3292
+ setTimeout(function(){
3293
+ var now=window.scrollY!==undefined?window.scrollY:(window.pageYOffset||0);
3294
+ if(Math.abs(now-y)>40)window.scrollTo(0,y);
3295
+ },350);
3296
+ }
3297
+
3298
+ /* Jump, rather than scroll past everything above it. */
3299
+ document.addEventListener('click',function(e){
3300
+ var btn=e.target.closest?e.target.closest('.rail-item'):null;
3301
+ if(!btn)return;
3302
+ var id=btn.getAttribute('data-rail');
3303
+
3304
+ /* Elements is not a section of its own \u2014 it is a pane inside Visual. Jump
3305
+ to Visual and switch the pane, so the rail entry lands where it says. */
3306
+ var sectionId=id==='dom'?'visual':id;
3307
+ var target=document.querySelector('[data-section="'+sectionId+'"]');
3308
+ if(id==='dom'&&target){
3309
+ var btns=target.querySelectorAll('.seg-btn');
3310
+ for(var b2=0;b2<btns.length;b2++){
3311
+ if(btns[b2].textContent==='Elements'){btns[b2].click();break;}
3312
+ }
3313
+ }
3314
+ jumpToSection(target);
3315
+ var all=document.querySelectorAll('.rail-item');
3316
+ for(var i=0;i<all.length;i++)all[i].classList.remove('active');
3317
+ btn.classList.add('active');
3318
+ });
3319
+
3320
+ /* A failing test opens on its failure, not on the top of a long page. */
3321
+ document.addEventListener('click',function(e){
3322
+ var card=e.target.closest?e.target.closest('.needs-card'):null;
3323
+ if(!card)return;
3324
+ openTest(card.getAttribute('data-testid'));
3325
+ });
3326
+ `,mt=`
3327
+ var overviewViewEl=document.getElementById('view-overview');
3328
+ var detailViewEl=document.getElementById('view-detail');
3329
+ var detailNavEl=document.getElementById('detail-nav');
3330
+ var _baseTitle=document.title;
3331
+ var _overviewScroll=0;
3332
+ var _currentTestId=null;
3333
+ /* Set while we are the ones changing the URL, so our own write does not
3334
+ bounce back through the router as if the user had pressed Back. */
3335
+ var _suppressRoute=false;
3336
+
3337
+ function testHash(id){return '#test/'+encodeURIComponent(id);}
3338
+
3339
+ function parseTestHash(){
3340
+ var h=String(location.hash||'');
3341
+ if(h.indexOf('#test/')!==0)return null;
3342
+ try{return decodeURIComponent(h.slice(6));}catch(e){return h.slice(6);}
3343
+ }
3344
+
3345
+ /* Switch the view FIRST, then record it in the URL.
3346
+ Driving the switch off the hashchange event made the whole navigation
3347
+ hostage to an event that is not always delivered \u2014 the view simply never
3348
+ changed. The URL still updates, so deep links and Back keep working, but
3349
+ nothing user-visible depends on the event arriving. */
3350
+ function setHashQuietly(value){
3351
+ try{
3352
+ _suppressRoute=true;
3353
+ if(history&&history.replaceState){
3354
+ history.pushState(null,'',value||location.pathname+location.search);
3355
+ }else{
3356
+ location.hash=value||'';
3357
+ }
3358
+ }catch(e){
3359
+ /* pushState is refused on file:// \u2014 the hash still works there. */
3360
+ try{location.hash=value||'';}catch(e2){/* no URL update; the view is right */}
3361
+ }
3362
+ setTimeout(function(){_suppressRoute=false;},0);
3363
+ }
3364
+
3365
+ /* Whether the detail view we are showing came from a push of ours, and so
3366
+ can be closed by unwinding history instead of pushing again. */
3367
+ var _pushedDetailEntry=false;
3368
+
3369
+ function openTest(testId){
3370
+ if(!testId||!testMap[testId])return;
3371
+ showTest(testId);
3372
+ setHashQuietly(testHash(testId));
3373
+ _pushedDetailEntry=true;
3374
+ }
3375
+
3376
+ function closeTest(){
3377
+ if(!_currentTestId)return;
3378
+ /* Leave the detail entry behind rather than stacking another one on top of
3379
+ it. setHashQuietly pushes, so closing this way left #test/<id> in
3380
+ history *behind* the overview \u2014 the browser Back button then replayed it
3381
+ and re-opened the very test the reader had just closed.
3382
+ history.back() unwinds our own push; routeFromHash handles the
3383
+ resulting popstate and switches the view. */
3384
+ if(_pushedDetailEntry&&history&&history.back){
3385
+ _pushedDetailEntry=false;
3386
+ history.back();
3387
+ return;
3388
+ }
3389
+ showOverview();
3390
+ setHashQuietly('');
3391
+ }
3392
+
3393
+ /* Prev/next walk the CURRENTLY FILTERED list, so stepping through a
3394
+ "Failed" filter visits the failures and nothing else. */
3395
+ function testNeighbours(testId){
3396
+ var list=getFilteredTests();
3397
+ for(var i=0;i<list.length;i++){
3398
+ if(list[i].testId===testId)return {list:list,idx:i};
3399
+ }
3400
+ return {list:list,idx:-1};
3401
+ }
3402
+
3403
+ function stepTest(delta){
3404
+ if(!_currentTestId)return;
3405
+ var n=testNeighbours(_currentTestId);
3406
+ if(n.idx<0)return;
3407
+ var next=n.list[n.idx+delta];
3408
+ if(next)openTest(next.testId);
3409
+ }
3410
+
3411
+ function renderDetailNav(testId){
3412
+ var t=testMap[testId]||{};
3413
+ var n=testNeighbours(testId);
3414
+ var pos=n.idx>=0?(n.idx+1)+' of '+n.list.length:'';
3415
+
3416
+ var h='<button class="detail-back" id="detail-back"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>Back to results</button>';
3417
+ if(t.filePath)h+='<span class="detail-crumb">'+esc(t.filePath)+'</span>';
3418
+
3419
+ h+='<div class="detail-steps">';
3420
+ var noPrev=n.idx<=0;
3421
+ var noNext=n.idx<0||n.idx>=n.list.length-1;
3422
+ h+='<button class="detail-step'+(noPrev?' is-off':'')+'" id="detail-prev" title="Previous test"'+(noPrev?' disabled':'')+'><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg></button>';
3423
+ if(pos)h+='<span class="detail-pos">'+esc(pos)+'</span>';
3424
+ h+='<button class="detail-step'+(noNext?' is-off':'')+'" id="detail-next" title="Next test"'+(noNext?' disabled':'')+'><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg></button>';
3425
+ h+='</div>';
3426
+
3427
+ detailNavEl.innerHTML=h;
3428
+ }
3429
+
3430
+ function showOverview(){
3431
+ _currentTestId=null;
3432
+ var v=drawerBodyEl.querySelector('video');
3433
+ if(v)v.pause();
3434
+ detailViewEl.hidden=true;
3435
+ overviewViewEl.hidden=false;
3436
+ document.title=_baseTitle;
3437
+ /* Put the reader back where they were in the list, not at the top. */
3438
+ window.scrollTo(0,_overviewScroll);
3439
+ }
3440
+
3441
+ function showTest(testId){
3442
+ if(_currentTestId===null)_overviewScroll=window.scrollY||window.pageYOffset||0;
3443
+ _currentTestId=testId;
3444
+ overviewViewEl.hidden=true;
3445
+ detailViewEl.hidden=false;
3446
+ window.scrollTo(0,0);
3447
+ var t=testMap[testId];
3448
+ document.title=(t&&t.title?t.title+' \u2014 ':'')+_baseTitle;
3449
+ renderDetailNav(testId);
3450
+ loadAndRenderTest(testId);
3451
+ }
3452
+
3453
+ /* Back/forward, and the initial load of a pasted link. Idempotent: a
3454
+ re-entrant call for the view already showing does nothing. */
3455
+ function routeFromHash(){
3456
+ if(_suppressRoute)return;
3457
+ var id=parseTestHash();
3458
+ if(id&&testMap[id]){
3459
+ if(_currentTestId!==id)showTest(id);
3460
+ }else if(_currentTestId!==null){
3461
+ showOverview();
3462
+ }
3463
+ }
3464
+
3465
+ window.addEventListener('hashchange',routeFromHash);
3466
+ window.addEventListener('popstate',routeFromHash);
3467
+ `;var vt=`
3468
+ var API_METHOD_COLOR={GET:'#60a5fa',POST:'#34d399',PUT:'#fbbf24',PATCH:'#a78bfa',DELETE:'#f87171',HEAD:'#6b7280',OPTIONS:'#6b7280'};
3469
+ var API_STATUS_ORDER=['2xx','3xx','4xx','5xx','error'];
3470
+ var API_STATUS_COLOR={'2xx':'#22c55e','3xx':'#60a5fa','4xx':'#fb923c','5xx':'#ef4444','error':'#ef4444'};
3471
+
3472
+ function apiMethodColor(m){return API_METHOD_COLOR[String(m||'').toUpperCase()]||'#8b949e';}
3473
+
3474
+ /* A value from an assertion. These are arbitrary JSON, so keep them short
3475
+ enough to sit on one line and let the reader open the call for the rest. */
3476
+ function fmtAssertValue(v){
3477
+ if(v===undefined)return 'undefined';
3478
+ if(v===null)return 'null';
3479
+ var s;
3480
+ if(typeof v==='string'){s=v;}
3481
+ else{try{s=JSON.stringify(v);}catch(e){s=String(v);}}
3482
+ if(s===undefined||s===null)s=String(v);
3483
+ if(s.length>120)s=s.slice(0,120)+'\\u2026';
3484
+ return s;
3485
+ }
3486
+
3487
+ function statusMixTotal(mix){
3488
+ if(!mix)return 0;
3489
+ var n=0;
3490
+ for(var i=0;i<API_STATUS_ORDER.length;i++)n+=(mix[API_STATUS_ORDER[i]]||0);
3491
+ return n;
3492
+ }
3493
+
3494
+ /* \u2500\u2500 Run-level API band \u2500\u2500 */
3495
+
3496
+ function renderApiMethodCard(byMethod,total){
3497
+ var keys=[];
3498
+ for(var k in byMethod){if(byMethod.hasOwnProperty(k)&&byMethod[k]>0)keys.push(k);}
3499
+ if(keys.length===0)return '';
3500
+ keys.sort(function(a,b){return byMethod[b]-byMethod[a];});
3501
+ var max=byMethod[keys[0]]||1;
3502
+ var h='<div class="tcard"><div class="tcard-h">By method<span class="tcard-sub">'+total.toLocaleString()+' calls</span></div><div style="display:flex;flex-direction:column;gap:8px">';
3503
+ for(var i=0;i<keys.length;i++){
3504
+ var m=keys[i],c=apiMethodColor(m),pct=Math.max(2,Math.round((byMethod[m]/max)*100));
3505
+ h+='<div class="mrow"><span class="mrow-k" style="color:'+c+'">'+esc(m)+'</span>';
3506
+ h+='<span class="mrow-track"><span class="mrow-fill" style="width:'+pct+'%;background:'+c+'"></span></span>';
3507
+ h+='<span class="mrow-n">'+byMethod[m].toLocaleString()+'</span></div>';
3508
+ }
3509
+ return h+'</div></div>';
3510
+ }
3511
+
3512
+ function renderApiStatusCard(mix){
3513
+ var total=statusMixTotal(mix);
3514
+ if(total===0)return '';
3515
+ var h='<div class="tcard"><div class="tcard-h">By status</div><div class="sbar">';
3516
+ for(var i=0;i<API_STATUS_ORDER.length;i++){
3517
+ var k=API_STATUS_ORDER[i],n=mix[k]||0;
3518
+ if(n===0)continue;
3519
+ h+='<span style="flex:'+n+';background:'+API_STATUS_COLOR[k]+'"></span>';
3520
+ }
3521
+ h+='</div><div class="sgrid">';
3522
+ for(var j=0;j<API_STATUS_ORDER.length;j++){
3523
+ var kk=API_STATUS_ORDER[j],nn=mix[kk]||0;
3524
+ h+='<div class="scell"><span class="spill sp-'+kk+'">'+esc(kk)+'</span>';
3525
+ h+='<span class="scell-n'+(nn===0?' is-zero':'')+'">'+nn.toLocaleString()+'</span></div>';
3526
+ }
3527
+ return h+'</div></div>';
3528
+ }
3529
+
3530
+ function renderAssertionsCard(s){
3531
+ var total=vNum(s.totalAssertions);
3532
+ if(total===0)return '';
3533
+ var pass=vNum(s.passedAssertions),fail=vNum(s.failedAssertions);
3534
+ var h='<div class="tcard"><div class="tcard-h">Assertions</div>';
3535
+ h+='<div class="tbig"><span class="tbig-n">'+total.toLocaleString()+'</span><span class="tbig-l">total</span></div>';
3536
+ h+='<div class="tsplit">';
3537
+ h+='<span class="tsplit-i tsplit-ok"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>'+pass.toLocaleString()+'</span>';
3538
+ h+='<span class="tsplit-i '+(fail>0?'tsplit-bad':'')+'" style="'+(fail>0?'':'color:var(--fg-2)')+'"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>'+fail.toLocaleString()+'</span>';
3539
+ h+='</div></div>';
3540
+ return h;
3541
+ }
3542
+
3543
+ function renderApiTimingCard(rt,uniqueUrls){
3544
+ if(!rt)return '';
3545
+ var h='<div class="tcard"><div class="tcard-h">Response time</div><div class="tpair">';
3546
+ h+='<div class="tpair-i"><span class="tpair-n">'+Math.round(rt.avg)+'<small>ms</small></span><span class="tpair-l">average</span></div>';
3547
+ h+='<div class="tpair-i"><span class="tpair-n">'+Math.round(rt.p95)+'<small>ms</small></span><span class="tpair-l">p95</span></div>';
3548
+ h+='<div class="tpair-i"><span class="tpair-n">'+Math.round(rt.max)+'<small>ms</small></span><span class="tpair-l">slowest</span></div>';
3549
+ if(uniqueUrls>0)h+='<div class="tpair-i"><span class="tpair-n">'+uniqueUrls.toLocaleString()+'</span><span class="tpair-l">endpoints</span></div>';
3550
+ return h+'</div></div>';
3551
+ }
3552
+
3553
+ /* The band only exists when the run actually produced the evidence it
3554
+ describes \u2014 a pure-UI run renders nothing here rather than four zeroes. */
3555
+ function renderTypeBand(){
3556
+ var el=document.getElementById('type-band');
3557
+ if(!el)return;
3558
+ var s=data.summary||{};
3559
+ var cards=[];
3560
+
3561
+ if(vNum(s.totalApiCalls)>0){
3562
+ var mc=renderApiMethodCard(s.apiCallsByMethod||{},vNum(s.totalApiCalls));
3563
+ if(mc)cards.push(mc);
3564
+ var sc=renderApiStatusCard(s.apiCallsByStatusRange||{});
3565
+ if(sc)cards.push(sc);
3566
+ var tc=renderApiTimingCard(s.apiResponseTime,vNum(s.uniqueApiUrls));
3567
+ if(tc)cards.push(tc);
3568
+ }
3569
+ var ac=renderAssertionsCard(s);
3570
+ if(ac)cards.push(ac);
3571
+
3572
+ if(typeof renderMobileCards==='function'){
3573
+ var mob=renderMobileCards(s);
3574
+ for(var mi=0;mi<mob.length;mi++)cards.push(mob[mi]);
3575
+ }
3576
+
3577
+ if(cards.length===0){el.innerHTML='';return;}
3578
+ var cls=cards.length>=3?'tband-3':'tband-2';
3579
+ /* Four cards wrap to two rows of two rather than shrinking to slivers. */
3580
+ if(cards.length===4)cls='tband-2';
3581
+ el.innerHTML='<div class="tband '+cls+'">'+cards.join('')+'</div>';
3582
+ }
3583
+
3584
+ /* \u2500\u2500 Per-row evidence \u2500\u2500 */
3585
+
3586
+ /* Status chips plus a single pass/fail mark. A row has roughly 110px for
3587
+ this, so it shows the shape of the calls, not the calls themselves. */
3588
+ function apiRowEvidence(t){
3589
+ if(!t)return '';
3590
+ var mix=t.apiStatusMix,h='';
3591
+ if(mix&&statusMixTotal(mix)>0){
3592
+ for(var i=0;i<API_STATUS_ORDER.length;i++){
3593
+ var k=API_STATUS_ORDER[i],n=mix[k]||0;
3594
+ if(n===0)continue;
3595
+ h+='<span class="achip sp-'+k+'" title="'+n+' '+esc(k)+' response'+(n===1?'':'s')+'">'+n+'</span>';
3596
+ }
3597
+ }else if(t.apiCallCount>0){
3598
+ h+='<span class="achip" style="background:rgba(255,255,255,.04);color:var(--fg-1)">'+t.apiCallCount+'</span>';
3599
+ }
3600
+ var pass=t.assertionsPassed||0,fail=t.assertionsFailed||0;
3601
+ if(pass>0||fail>0){
3602
+ if(fail>0){
3603
+ h+='<span class="amark amark-bad" title="'+fail+' assertion'+(fail===1?'':'s')+' failed"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg></span>';
3604
+ }else{
3605
+ h+='<span class="amark amark-ok" title="'+pass+' assertion'+(pass===1?'':'s')+' passed"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg></span>';
3606
+ }
3607
+ }
3608
+ return h?'<span class="arow">'+h+'</span>':'';
3609
+ }
3610
+
3611
+ /* \u2500\u2500 Detail: the assertions section \u2500\u2500 */
3612
+
3613
+ function findApiCall(apiCalls,callId){
3614
+ if(!apiCalls||!callId)return null;
3615
+ for(var i=0;i<apiCalls.length;i++){
3616
+ var c=apiCalls[i];
3617
+ if(c.id===callId||c.callId===callId)return c;
3618
+ }
3619
+ return null;
3620
+ }
3621
+
3622
+ function assertionExpression(a){
3623
+ if(a.expression)return a.expression;
3624
+ /* No expression was captured; say what the assertion checked instead of
3625
+ leaving the row blank. */
3626
+ return 'expect('+String(a.type||'value')+') \\u2192 '+fmtAssertValue(a.expected);
3627
+ }
3628
+
3629
+ function renderAssertionsSection(assertions,apiCalls){
3630
+ if(!assertions||assertions.length===0)return '';
3631
+ var pass=0,fail=0,i;
3632
+ for(i=0;i<assertions.length;i++){
3633
+ if(assertions[i].status==='failed')fail++;else pass++;
3634
+ }
3635
+
3636
+ var h='<div class="apisec"><div class="apisec-head">';
3637
+ h+='<span class="apisec-title">Assertions</span>';
3638
+ if(pass>0)h+='<span class="apisec-badge ab-ok">'+pass+' passed</span>';
3639
+ if(fail>0)h+='<span class="apisec-badge ab-bad">'+fail+' failed</span>';
3640
+ h+='</div><div class="asec">';
3641
+
3642
+ /* Failures first \u2014 the reason anyone opened this page. */
3643
+ var ordered=assertions.slice().sort(function(a,b){
3644
+ return (a.status==='failed'?0:1)-(b.status==='failed'?0:1);
3645
+ });
3646
+
3647
+ for(i=0;i<ordered.length;i++){
3648
+ var a=ordered[i],failed=a.status==='failed';
3649
+ h+='<div class="arow-item'+(failed?' is-failed':'')+'">';
3650
+ h+=failed
3651
+ ? '<svg style="flex-shrink:0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>'
3652
+ : '<svg style="flex-shrink:0" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>';
3653
+ h+='<span class="atype">'+esc(String(a.type||'custom'))+'</span>';
3654
+ h+='<span class="aexpr">'+esc(assertionExpression(a))+'</span>';
3655
+ if(a.location&&a.location.line)h+='<span class="aloc">:'+a.location.line+'</span>';
3656
+ h+='</div>';
3657
+
3658
+ if(failed){
3659
+ h+='<div class="adiff">';
3660
+ h+='<span class="adiff-i"><span class="adiff-k">expected</span><span class="adiff-v adiff-exp">'+esc(fmtAssertValue(a.expected))+'</span></span>';
3661
+ h+='<span class="adiff-i"><span class="adiff-k">received</span><span class="adiff-v adiff-act">'+esc(fmtAssertValue(a.actual))+'</span></span>';
3662
+ h+='</div>';
3663
+ var call=findApiCall(apiCalls,a.callId);
3664
+ if(call){
3665
+ var code=call.responseStatusCode!=null?call.responseStatusCode:(call.response&&call.response.status);
3666
+ h+='<div class="acall">on '+esc(String(call.method||''))+' '+esc(urlPath(String(call.url||'')))+(code?' \\u2192 '+code:'')+'</div>';
3667
+ }
3668
+ }
3669
+ }
3670
+ return h+'</div></div>';
3671
+ }
3672
+
3673
+ /* The same sentence, built from the flattened summary the index carries \u2014
3674
+ so the run overview reads identically in embedded and streaming mode. */
3675
+ function assertionSentence(fa){
3676
+ if(!fa)return '';
3677
+ return 'A <code>'+esc(String(fa.type||'custom'))+'</code> assertion expected <code>'+
3678
+ esc(String(fa.expected))+'</code> but received <code>'+esc(String(fa.actual))+'</code>.';
3679
+ }
3680
+
3681
+ /* The headline for an API failure: the assertion, not the matcher wrapper. */
3682
+ function apiFailureSentence(assertions){
3683
+ if(!assertions||assertions.length===0)return '';
3684
+ for(var i=0;i<assertions.length;i++){
3685
+ var a=assertions[i];
3686
+ if(a.status!=='failed')continue;
3687
+ return 'A <code>'+esc(String(a.type||'custom'))+'</code> assertion expected <code>'+
3688
+ esc(fmtAssertValue(a.expected))+'</code> but received <code>'+
3689
+ esc(fmtAssertValue(a.actual))+'</code>.';
3690
+ }
3691
+ return '';
3692
+ }
3693
+ `;var yt=`
3694
+ .vis-header{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:12px;display:flex;align-items:center;gap:6px}
3695
+ .vis-header svg{opacity:.6}
3696
+ .vis-count{font-weight:400;color:var(--fg-2);font-size:10px}
3697
+ .vis-count-fail{color:#ef4444}
3698
+ .vis-list{display:flex;flex-direction:column;gap:20px}
3699
+ .vis-card{border:1px solid var(--bd);border-radius:10px;padding:14px;background:var(--bg-2);min-width:0}
3700
+ .vis-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}
3701
+ .vis-name{font-size:12px;font-weight:600;color:var(--fg-1);word-break:break-all}
3702
+ .vis-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;white-space:nowrap}
3703
+ .vis-badge.vb-passed{background:rgba(3,183,156,0.14);color:#2dd4a8}
3704
+ .vis-badge.vb-failed{background:rgba(239,68,68,0.14);color:#ef4444}
3705
+ .vis-badge.vb-new{background:rgba(59,130,246,0.14);color:#60a5fa}
3706
+ .vis-badge.vb-updated{background:rgba(234,179,8,0.14);color:#eab308}
3707
+ .vis-badge.vb-dom{background:rgba(232,121,249,0.14);color:#e879f9}
3708
+ .vis-metrics{font-size:11px;color:var(--fg-2);margin-left:auto;white-space:nowrap;font-variant-numeric:tabular-nums}
3709
+ .vis-note{font-size:11px;color:var(--fg-2);font-style:italic;padding:6px 0}
3710
+
3711
+ /* Side-by-side: wraps to a single column when the drawer is narrow. */
3712
+ .vis-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}
3713
+ .vis-pane-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px}
3714
+
3715
+ /* Layered comparison surface shared by the slider and onion-skin modes. */
3716
+ .vis-stack{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg)}
3717
+ .vis-stack img{display:block;width:100%;height:auto}
3718
+ .vis-stack .vis-top{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain}
3719
+ .vis-handle{position:absolute;top:0;bottom:0;width:2px;background:#03b79c;pointer-events:none;box-shadow:0 0 0 1px rgba(0,0,0,0.35)}
3720
+ .vis-range{width:100%;margin-top:10px;accent-color:#03b79c;cursor:ew-resize}
3721
+ .vis-range-row{display:flex;align-items:center;gap:10px;font-size:10px;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em}
3722
+ .vis-range-row .vis-range{margin-top:0}
3723
+
3724
+ /* A checkerboard behind the diff so fully transparent regions stay legible. */
3725
+ .vis-diff-wrap{background-image:linear-gradient(45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(-45deg,var(--bd-xs) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,var(--bd-xs) 75%),linear-gradient(-45deg,transparent 75%,var(--bd-xs) 75%);background-size:16px 16px;background-position:0 0,0 8px,8px -8px,-8px 0;border-radius:8px}
3726
+ `,bt=`
3727
+ .dom-figure{position:relative;line-height:0;border:1px solid var(--bd);border-radius:8px;overflow:hidden;margin-bottom:12px}
3728
+ .dom-img{display:block;width:100%;height:auto}
3729
+ .dom-ov{position:absolute;top:0;left:0;right:0;bottom:0;pointer-events:none}
3730
+ .dom-box{position:absolute;border:2px solid #03b79c;border-radius:2px;opacity:.55;transition:opacity .12s,box-shadow .12s}
3731
+ .dom-box.hi{opacity:1;box-shadow:0 0 0 2px rgba(0,0,0,.45),0 0 12px rgba(255,255,255,.25)}
3732
+
3733
+ .dom-summary{font-size:11px;color:var(--fg-2);margin-bottom:10px}
3734
+ .dom-summary strong{color:var(--fg-1);font-variant-numeric:tabular-nums}
3735
+ .dom-truncated{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:999px;background:rgba(234,179,8,.14);color:#eab308;font-size:10px;font-weight:600}
3736
+
3737
+ .dom-list{display:flex;flex-direction:column;gap:2px}
3738
+ .dom-group-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin:12px 0 4px}
3739
+ .dom-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:default;flex-wrap:wrap}
3740
+ .dom-row:hover,.dom-row.hi{background:var(--bd-xs)}
3741
+ .dom-swatch{width:8px;height:8px;border-radius:2px;flex:none}
3742
+ .dom-label{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--fg-1);word-break:break-all}
3743
+ .dom-kind{font-size:9px;font-weight:600;padding:1px 6px;border-radius:999px;text-transform:uppercase;letter-spacing:.04em;background:var(--bd-xs);color:var(--fg-2)}
3744
+ .dom-kind.dk-restyled{background:rgba(232,121,249,.14);color:#e879f9}
3745
+ .dom-kind.dk-text{background:rgba(96,165,250,.14);color:#60a5fa}
3746
+ .dom-kind.dk-attrs{background:rgba(251,146,60,.14);color:#fb923c}
3747
+ .dom-kind.dk-added{background:rgba(3,183,156,.14);color:#2dd4a8}
3748
+ .dom-kind.dk-removed{background:rgba(239,68,68,.14);color:#ef4444}
3749
+ .dom-area{margin-left:auto;font-size:10px;color:var(--fg-2);font-variant-numeric:tabular-nums}
3750
+
3751
+ .dom-detail{padding:0 8px 8px 24px;display:flex;flex-direction:column;gap:3px}
3752
+ .dom-prop{display:flex;align-items:baseline;gap:8px;font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex-wrap:wrap}
3753
+ .dom-prop-name{color:var(--fg-2);min-width:130px}
3754
+ .dom-prop-attr .dom-prop-name{color:#fb923c;font-weight:600}
3755
+ .dom-from{color:#ef4444;word-break:break-all}
3756
+ .dom-arrow{color:var(--fg-2)}
3757
+ .dom-to{color:#2dd4a8;word-break:break-all}
3758
+ `;var xt=`
3759
+ /* \u2500\u2500 Wider shell \u2500\u2500
3760
+ 960px was sized for a list. The row now carries evidence and timing, and
3761
+ the detail panel shows two screenshots side by side; both were cramped. */
3762
+ .wrap{max-width:1400px;padding:28px 40px 64px}
3763
+
3764
+ /* \u2500\u2500 Verdict band (replaces the six equal stat chips) \u2500\u2500 */
3765
+ .verdict{display:flex;gap:16px;align-items:stretch;margin-bottom:22px}
3766
+ .verdict-main{flex:1;min-width:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 22px;display:flex;flex-direction:column;gap:14px}
3767
+ .verdict-main.v-failed{border-color:rgba(239,68,68,0.22)}
3768
+ .verdict-main.v-passed{border-color:rgba(34,197,94,0.2)}
3769
+ .verdict-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
3770
+ .verdict-title{font-size:21px;font-weight:700;letter-spacing:-0.01em;color:var(--fg)}
3771
+ .verdict-where{font-size:12px;color:var(--fg-1)}
3772
+ .verdict-where code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--fg)}
3773
+ .verdict-dur{margin-left:auto;display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-1);white-space:nowrap}
3774
+
3775
+ /* Proportion bar: one glance at the shape of the run. */
3776
+ .verdict-bar{display:flex;height:8px;gap:2px}
3777
+ .verdict-bar span{border-radius:999px;min-width:3px}
3778
+ .vb-passed{background:#22c55e}.vb-failed{background:#ef4444}
3779
+ .vb-flaky{background:#f59e0b}.vb-skipped{background:#6b7280}.vb-timedout{background:#f97316}
3780
+ .verdict-bar-empty{flex:1;background:var(--bd-m);border-radius:999px}
3781
+
3782
+ .verdict-counts{display:flex;align-items:center;gap:20px;flex-wrap:wrap}
3783
+ .vc{display:flex;align-items:center;gap:7px}
3784
+ .vc-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
3785
+ .vc-n{font-size:14px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}
3786
+ .vc-l{font-size:12px;color:var(--fg-1)}
3787
+ /* Statuses with nothing in them stop taking a sixth of the width. */
3788
+ .verdict-zeros{margin-left:auto;font-size:11px;color:var(--fg-2)}
3789
+
3790
+ .verdict-side{width:268px;flex-shrink:0;background:var(--bg-1);border:1px solid var(--bd);border-radius:12px;padding:18px 20px;display:flex;flex-direction:column;gap:12px}
3791
+ .verdict-side-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase}
3792
+ .verdict-side-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 10px}
3793
+ .vs-n{font-size:17px;font-weight:700;color:var(--fg);font-variant-numeric:tabular-nums}
3794
+ .vs-n.vs-dom{color:#e879f9}
3795
+ .vs-l{font-size:10px;color:var(--fg-1)}
3796
+
3797
+ /* \u2500\u2500 Needs attention \u2500\u2500 */
3798
+ .needs{display:flex;flex-direction:column;gap:10px;margin-bottom:22px}
3799
+ .needs-head{display:flex;align-items:center;gap:8px;padding:0 2px}
3800
+ .needs-title{font-size:11px;font-weight:700;color:var(--fg);letter-spacing:.06em;text-transform:uppercase}
3801
+ .needs-count{font-size:10px;font-weight:700;color:#ef4444;background:rgba(239,68,68,0.12);padding:1px 7px;border-radius:4px}
3802
+ .needs-list{display:flex;flex-direction:column;gap:8px}
3803
+ .needs-card{background:var(--bg-1);border:1px solid rgba(239,68,68,0.2);border-left:3px solid #ef4444;border-radius:10px;padding:14px 18px;display:flex;gap:16px;align-items:flex-start;cursor:pointer;transition:border-color .15s,background .1s}
3804
+ .needs-card:hover{background:var(--hvr);border-color:rgba(239,68,68,0.4)}
3805
+ .needs-card.nc-flaky{border-color:rgba(245,158,11,0.2);border-left-color:#f59e0b}
3806
+ .needs-card.nc-flaky:hover{border-color:rgba(245,158,11,0.4)}
3807
+ .needs-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:8px}
3808
+ .needs-row1{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
3809
+ .needs-name{font-size:14px;font-weight:600;color:var(--fg)}
3810
+ .needs-file{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
3811
+ .needs-cause{font-size:12px;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;line-height:1.6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3812
+ .needs-cause em{font-style:normal;color:var(--fg-err)}
3813
+ .needs-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
3814
+ .needs-thumb{width:148px;flex-shrink:0;display:flex;flex-direction:column;gap:5px}
3815
+ .needs-thumb img{width:100%;height:92px;object-fit:cover;object-position:top center;border-radius:7px;border:1px solid rgba(239,68,68,0.35);background:var(--bg)}
3816
+ .needs-thumb-cap{font-size:9px;color:var(--fg-2);text-align:center}
3817
+ .needs-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0}
3818
+ .needs-dur{font-size:13px;font-weight:700;color:var(--fg-1);font-variant-numeric:tabular-nums}
3819
+ .needs-open{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#2dd4a8}
3820
+
3821
+ /* Pill chips used in the needs cards. */
3822
+ .nchip{display:flex;align-items:center;gap:4px;font-size:10px;font-weight:500;color:var(--fg-1);background:var(--bg-2);border:1px solid var(--bd);padding:2px 8px;border-radius:999px}
3823
+ .nchip-visual{color:#2dd4a8;background:rgba(3,183,156,0.1);border-color:rgba(3,183,156,0.22);font-weight:600}
3824
+ .nchip-dom{color:#e879f9;background:rgba(232,121,249,0.1);border-color:rgba(232,121,249,0.2);font-weight:600}
3825
+
3826
+ /* \u2500\u2500 Enriched test row \u2500\u2500
3827
+ Same height as before. Two regions added: what was captured, and how long
3828
+ it took relative to the slowest test in the run. */
3829
+ .trow-evidence{display:flex;gap:4px;align-items:center;flex-shrink:0}
3830
+ .tev{width:18px;height:18px;border-radius:4px;background:var(--bg-2);display:flex;align-items:center;justify-content:center;color:var(--fg-1);flex-shrink:0}
3831
+ .tev-visual{background:rgba(3,183,156,0.12);color:#2dd4a8}
3832
+ .tev-dom{background:rgba(232,121,249,0.12);color:#e879f9}
3833
+ .tev-err{background:rgba(239,68,68,0.12);color:#ef4444}
3834
+ .trow-time{width:120px;flex-shrink:0;display:flex;align-items:center;gap:8px}
3835
+ .trow-bar{flex:1;height:4px;border-radius:999px;background:var(--bd-m);overflow:hidden}
3836
+ .trow-bar span{display:block;height:100%;border-radius:999px}
3837
+ .tb-passed{background:#22c55e}.tb-failed{background:#ef4444}
3838
+ .tb-flaky{background:#f59e0b}.tb-skipped{background:#6b7280}.tb-timedout{background:#f97316}
3839
+ .test-row-dur{font-variant-numeric:tabular-nums}
3840
+ /* The title and its badges sit on one line now that the row has columns. */
3841
+ .test-row-info.tri-inline{flex-direction:row;align-items:center;gap:10px}
3842
+ .test-row-info.tri-inline .test-row-badges{flex-shrink:0}
3843
+
3844
+ /* \u2500\u2500 Detail panel \u2500\u2500
3845
+ Was 50vw, which left the comparison about 330px a side and a dead half-
3846
+ screen of grid behind it. */
3847
+ .drawer{width:min(1240px,94vw);max-width:94vw}
3848
+ .drawer-layout{display:flex;gap:28px;align-items:flex-start}
3849
+ .drawer-rail{width:206px;flex-shrink:0;position:sticky;top:0;display:flex;flex-direction:column;gap:18px}
3850
+ .drawer-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:24px}
3851
+ .rail-group{display:flex;flex-direction:column;gap:2px}
3852
+ .rail-label{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:0 10px 7px}
3853
+ .rail-item{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:7px;color:var(--fg-1);cursor:pointer;transition:background .12s,color .12s;border:none;background:transparent;font-family:inherit;text-align:left;width:100%}
3854
+ .rail-item:hover{background:var(--bd-xs);color:var(--fg)}
3855
+ .rail-item.active{background:rgba(3,183,156,0.12);color:#2dd4a8}
3856
+ .rail-item.active .rail-name{font-weight:600}
3857
+ .rail-item.rail-fail{color:var(--fg-err)}
3858
+ .rail-item.rail-fail.active{background:rgba(239,68,68,0.1);color:var(--fg-err)}
3859
+ .rail-name{font-size:12.5px;font-weight:500;flex:1;min-width:0}
3860
+ .rail-n{font-size:10px;font-weight:700;background:var(--bd-xs);padding:1px 6px;border-radius:4px;font-variant-numeric:tabular-nums}
3861
+ .rail-item.active .rail-n{background:rgba(3,183,156,0.18)}
3862
+ .rail-n.rail-n-dom{color:#e879f9;background:rgba(232,121,249,0.12)}
3863
+ .rail-meta{border-top:1px solid var(--bd);padding-top:16px;display:flex;flex-direction:column;gap:9px}
3864
+ .rail-meta-rows{padding:0 10px;display:flex;flex-direction:column;gap:7px;font-size:11px}
3865
+ .rail-meta-row{display:flex;gap:8px}
3866
+ .rail-meta-k{color:var(--fg-2);width:44px;flex-shrink:0}
3867
+ .rail-meta-v{color:var(--fg-1);min-width:0;overflow:hidden;text-overflow:ellipsis}
3868
+ .rail-meta-v.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
3869
+
3870
+ /* Scroll-margin so a rail jump does not tuck the heading under the bar. */
3871
+ .drawer-section{scroll-margin-top:12px}
3872
+
3873
+ @media (max-width:1100px){
3874
+ .drawer{width:96vw;max-width:96vw}
3875
+ .drawer-layout{flex-direction:column;gap:18px}
3876
+ .drawer-rail{width:100%;position:static;flex-direction:row;flex-wrap:wrap;gap:6px}
3877
+ .rail-group{flex-direction:row;flex-wrap:wrap;gap:4px}
3878
+ .rail-label,.rail-meta{display:none}
3879
+ .rail-item{width:auto}
3880
+ .verdict{flex-direction:column}
3881
+ .verdict-side{width:100%}
3882
+ }
3883
+ `,wt=`
3884
+ /* The overlay is gone. Anything still carrying .drawer must not float. */
3885
+ .drawer,.drawer-backdrop{display:none}
3886
+
3887
+ .detail-page{display:flex;flex-direction:column;gap:20px}
3888
+ #view-detail[hidden],#view-overview[hidden]{display:none}
3889
+
3890
+ .detail-nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding-bottom:14px;border-bottom:1px solid var(--bd)}
3891
+ .detail-back{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:var(--fg-1);background:transparent;border:1px solid var(--bd-m);border-radius:8px;padding:6px 12px 6px 9px;cursor:pointer;font-family:inherit;transition:background .15s,color .15s,border-color .15s}
3892
+ .detail-back:hover{background:var(--hvr);color:var(--fg);border-color:var(--bd-l)}
3893
+ .detail-crumb{font-size:11px;color:var(--fg-2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
3894
+
3895
+ .detail-steps{margin-left:auto;display:inline-flex;align-items:center;border:1px solid var(--bd-m);border-radius:8px;overflow:hidden}
3896
+ .detail-step{display:flex;align-items:center;justify-content:center;width:32px;height:30px;background:transparent;border:none;color:var(--fg-1);cursor:pointer;font-family:inherit;transition:background .15s,color .15s}
3897
+ .detail-step:hover:not(.is-off){background:var(--hvr);color:var(--fg)}
3898
+ .detail-step.is-off{color:var(--fg-2);cursor:default}
3899
+ .detail-pos{font-size:11px;font-weight:600;color:var(--fg-1);padding:0 10px;border-left:1px solid var(--bd-m);border-right:1px solid var(--bd-m);line-height:30px;font-variant-numeric:tabular-nums}
3900
+
3901
+ /* The page scrolls now, so the old panel body must not scroll inside it. */
3902
+ #view-detail .drawer-body{flex:none;overflow:visible;padding:0}
3903
+ .detail-page .detail-rail,.detail-page .drawer-rail{top:16px}
3904
+ `;var kt=`
3905
+ /* \u2500\u2500 Run-level type band \u2500\u2500 */
3906
+ .tband{display:grid;gap:16px;margin-bottom:20px}
3907
+ .tband-3{grid-template-columns:1.1fr 1.1fr .9fr}
3908
+ .tband-2{grid-template-columns:1fr 1fr}
3909
+ .tcard{background:var(--bg-1);border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px 20px;display:flex;flex-direction:column;gap:12px;min-width:0}
3910
+ .tcard-h{font-size:10px;font-weight:600;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;display:flex;align-items:center;gap:8px}
3911
+ .tcard-h .tcard-sub{margin-left:auto;font-weight:500;letter-spacing:0;text-transform:none;font-size:10px}
3912
+
3913
+ /* Method distribution */
3914
+ .mrow{display:flex;align-items:center;gap:10px}
3915
+ .mrow-k{width:54px;font-size:11px;font-weight:700;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3916
+ .mrow-track{display:block;flex:1;height:6px;border-radius:999px;background:rgba(255,255,255,.06);overflow:hidden}
3917
+ .mrow-fill{display:block;height:100%;border-radius:999px;min-width:3px}
3918
+ .mrow-n{font-size:11px;color:var(--fg-1);width:34px;text-align:right}
3919
+
3920
+ /* Status distribution */
3921
+ .sbar{display:flex;height:8px;gap:2px}
3922
+ .sbar>span{border-radius:999px}
3923
+ .sgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px}
3924
+ .scell{display:flex;align-items:center;gap:8px}
3925
+ .spill{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}
3926
+ .scell-n{font-size:12px;font-weight:700}
3927
+ .scell-n.is-zero{font-weight:400;color:var(--fg-2)}
3928
+ .sp-2xx{background:rgba(34,197,94,.12);color:#22c55e}
3929
+ .sp-3xx{background:rgba(96,165,250,.12);color:#60a5fa}
3930
+ .sp-4xx{background:rgba(251,146,60,.12);color:#fb923c}
3931
+ .sp-5xx{background:rgba(239,68,68,.12);color:#ef4444}
3932
+ .sp-error{background:rgba(239,68,68,.12);color:#ef4444}
3933
+
3934
+ /* Big-number card (assertions, timings) */
3935
+ .tbig{display:flex;align-items:baseline;gap:8px}
3936
+ .tbig-n{font-size:28px;font-weight:800;line-height:1}
3937
+ .tbig-l{font-size:11px;color:var(--fg-1)}
3938
+ .tsplit{display:flex;gap:14px;flex-wrap:wrap}
3939
+ .tsplit-i{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:700}
3940
+ .tsplit-ok{color:#22c55e}
3941
+ .tsplit-bad{color:#ef4444}
3942
+ .tpair{display:flex;gap:18px;flex-wrap:wrap}
3943
+ .tpair-i{display:flex;flex-direction:column;gap:2px}
3944
+ .tpair-n{font-size:17px;font-weight:700}
3945
+ .tpair-n small{font-size:11px;font-weight:500;color:var(--fg-1)}
3946
+ .tpair-l{font-size:10px;color:var(--fg-1)}
3947
+
3948
+ /* \u2500\u2500 Row evidence in the grid \u2500\u2500 */
3949
+ .arow{display:flex;gap:5px;align-items:center;flex-shrink:0}
3950
+ .achip{font-size:10px;font-weight:700;padding:1px 6px;border-radius:4px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3951
+ .amark{width:18px;height:18px;border-radius:4px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
3952
+ .amark-ok{background:rgba(34,197,94,.12);color:#22c55e}
3953
+ .amark-bad{background:rgba(239,68,68,.12);color:#ef4444}
3954
+
3955
+ /* \u2500\u2500 Assertions section (detail page) \u2500\u2500 */
3956
+ .apisec{border:1px solid var(--bd);border-radius:11px;background:var(--bg-1);overflow:hidden}
3957
+ .apisec-head{display:flex;align-items:center;gap:10px;padding:12px 20px;border-bottom:1px solid rgba(255,255,255,.06)}
3958
+ .apisec-title{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}
3959
+ .apisec-badge{font-size:10px;font-weight:700;padding:1px 7px;border-radius:4px}
3960
+ .apisec-badge.ab-ok{background:rgba(34,197,94,.12);color:#22c55e}
3961
+ .apisec-badge.ab-bad{background:rgba(239,68,68,.12);color:#ef4444}
3962
+ .asec{display:flex;flex-direction:column}
3963
+ .arow-item{display:flex;align-items:center;gap:12px;padding:10px 20px;border-bottom:1px solid rgba(255,255,255,.04)}
3964
+ .arow-item:last-child{border-bottom:none}
3965
+ .arow-item.is-failed{background:rgba(239,68,68,.04)}
3966
+ .atype{font-size:9px;font-weight:700;padding:1px 6px;border-radius:999px;letter-spacing:.04em;text-transform:uppercase;flex-shrink:0;background:rgba(255,255,255,.04);color:var(--fg-1)}
3967
+ .arow-item.is-failed .atype{background:rgba(239,68,68,.14);color:#ef4444}
3968
+ .aexpr{font-size:11.5px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3969
+ .aloc{font-size:10px;color:var(--fg-2);flex-shrink:0;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3970
+ .adiff{display:flex;gap:22px;flex-wrap:wrap;padding:0 20px 12px 52px}
3971
+ .adiff-i{display:flex;gap:8px;align-items:baseline}
3972
+ .adiff-k{font-size:10px;color:var(--fg-2);width:58px}
3973
+ .adiff-v{font-size:11px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);word-break:break-all}
3974
+ .adiff-exp{color:#2dd4a8}
3975
+ .adiff-act{color:#ef4444}
3976
+ .acall{font-size:10px;color:var(--fg-2);padding:0 20px 10px 52px;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3977
+
3978
+ /* \u2500\u2500 API call row: the two columns that used to render empty \u2500\u2500 */
3979
+ .ndt-assert{display:inline-flex;align-items:center;justify-content:center;width:100%;font-size:10px;font-weight:700}
3980
+ .ndt-assert-ok{color:#22c55e}
3981
+ .ndt-assert-bad{color:#ef4444}
3982
+ .ndt-assert-none{color:var(--fg-2);font-weight:400}
3983
+ .ndt-err-note{color:#ef4444;font-size:11px}
3984
+
3985
+ /* Trace pane \u2014 a link out, plus how to open the thing */
3986
+ .trace-open{display:flex;flex-direction:column;gap:10px;padding:18px;background:var(--bg-2);border:1px solid var(--bd);border-radius:10px}
3987
+ .trace-link{display:inline-flex;align-items:center;gap:8px;align-self:flex-start;font-size:12.5px;font-weight:600;color:#2dd4a8;text-decoration:none;border:1px solid rgba(3,183,156,.3);background:rgba(3,183,156,.08);padding:8px 14px;border-radius:8px}
3988
+ .trace-link:hover{background:rgba(3,183,156,.14)}
3989
+ .trace-hint{font-size:11.5px;color:var(--fg-1);line-height:1.6}
3990
+ .trace-hint code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;background:var(--bg-code);padding:1px 5px;border-radius:4px;color:var(--fg)}
3991
+
3992
+ /* Cookie table */
3993
+ .ndt-headers-label{font-size:9px;font-weight:700;color:var(--fg-2);letter-spacing:.06em;text-transform:uppercase;padding:10px 14px 0}
3994
+ .ck-tbl{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;padding:12px 14px;font-size:11.5px}
3995
+ .ck-k{color:var(--fg-1);font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3996
+ .ck-v{color:var(--fg);word-break:break-all;font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace)}
3997
+ .ck-attr{color:var(--fg-2);font-size:10px}
3998
+
3999
+ @media (max-width:1100px){
4000
+ .tband-3,.tband-2{grid-template-columns:1fr}
4001
+ }
4002
+ `;var La=`
2372
4003
  (function(){
2373
4004
  var data=JSON.parse(document.getElementById('report-data').textContent);
2374
- ${ze}
2375
- ${Ge}
2376
- ${Ve}
2377
- ${$e}
2378
- ${je}
2379
- ${Ke}
2380
- ${We}
4005
+ ${it}
4006
+ ${ct}
4007
+ ${ut}
4008
+ ${ot}
4009
+ ${lt}
4010
+ ${pt}
4011
+ ${gt}
4012
+ ${ft}
4013
+ ${dt}
4014
+ ${ht}
4015
+ ${vt}
4016
+ ${mt}
2381
4017
  applyTheme();
2382
4018
  renderRunMeta();
2383
4019
  renderSummary();
4020
+ renderTypeBand();
2384
4021
  initFilters();
2385
4022
  renderFilterBar();
2386
4023
  renderTestGrid();
4024
+ renderNeedsAttention();
4025
+ routeFromHash();
2387
4026
  var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();
2388
- })();`;function cr(t,e,r){let n=t.replace(/<\//g,"<\\/"),s=r?r.replace(/<\//g,"<\\/"):null;return `<!-- TestRelic AI Analytics Report \u2014 self-contained, no external dependencies -->
4027
+ })();`;function jr(t,e,r){let n=t.replace(/<\//g,"<\\/"),s=r?r.replace(/<\//g,"<\\/"):null;return `<!-- TestRelic AI Analytics Report \u2014 self-contained, no external dependencies -->
2389
4028
  <!DOCTYPE html>
2390
4029
  <html lang="en" data-theme="">
2391
4030
  <head>
2392
4031
  <meta charset="UTF-8">
2393
4032
  <meta name="viewport" content="width=device-width,initial-scale=1">
2394
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob: 'self'; media-src blob: 'self'; connect-src 'self' http://127.0.0.1:*;">
4033
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;">
2395
4034
  <title>TestRelic AI Analytics Report</title>
2396
4035
  ${""}
2397
- <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${Buffer.from(Ue).toString("base64")}">
2398
- <style>${qe}</style>
4036
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${Buffer.from(st).toString("base64")}">
4037
+ <style>${nt}${yt}${bt}${xt}${wt}${kt}</style>
2399
4038
  <script>(function(){var p=localStorage.getItem('tr-theme')||'system';var t=p==='system'?(window.matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):p;document.documentElement.setAttribute('data-theme',t);})()</script>
2400
4039
  </head>
2401
4040
  <body>
2402
4041
  <div class="wrap">
2403
4042
  <div class="top-bar">
2404
4043
  <div class="brand">
2405
- ${He}
4044
+ ${at}
2406
4045
  <div class="brand-text">
2407
4046
  <span class="brand-title">TestRelic AI</span>
2408
4047
  <span class="brand-sub">Analytics Report</span>
@@ -2416,19 +4055,19 @@ ${""}
2416
4055
  </div>
2417
4056
  <a class="cta-btn" href="https://testrelic.ai" target="_blank" rel="noopener noreferrer"><svg class="cta-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.545 4.955L14.5 7.5l-4.955 1.545L8 14l-1.545-4.955L1.5 7.5l4.955-1.545L8 1z" fill="currentColor"/></svg>Advanced Insights with AI<span class="cta-sep">\u2013</span><strong>Get Started</strong><svg class="cta-arrow" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2.5 6h7M6.5 3l3 3-3 3"/></svg></a>
2418
4057
  </div>
2419
- <div id="summary-strip" class="summary-strip"></div>
2420
- <div id="filter-bar" class="filter-bar"></div>
2421
- <div id="test-grid"></div>
4058
+ <div id="view-overview">
4059
+ <div id="summary-strip"></div>
4060
+ <div id="type-band"></div>
4061
+ <div id="needs-attention"></div>
4062
+ <div id="filter-bar" class="filter-bar"></div>
4063
+ <div id="test-grid"></div>
4064
+ </div>
4065
+ <div id="view-detail" class="detail-page" hidden>
4066
+ <div class="detail-nav" id="detail-nav"></div>
4067
+ <div class="drawer-body" id="drawer-body"></div>
4068
+ </div>
2422
4069
  </div>
2423
4070
  <div class="loading-overlay" id="loading-overlay"><div class="loading-spinner"></div><div class="loading-text">Loading report...</div></div>
2424
- <div class="drawer-backdrop" id="drawer-backdrop"></div>
2425
- <aside class="drawer" id="drawer">
2426
- <div class="drawer-bar">
2427
- <span class="drawer-bar-title">Test Details</span>
2428
- <button class="drawer-close" id="drawer-close">&times;</button>
2429
- </div>
2430
- <div class="drawer-body" id="drawer-body"></div>
2431
- </aside>
2432
4071
  <div class="filter-drawer-backdrop" id="filter-drawer-backdrop"></div>
2433
4072
  <aside class="filter-drawer" id="filter-drawer">
2434
4073
  <div class="filter-drawer-bar">
@@ -2439,9 +4078,9 @@ ${""}
2439
4078
  </aside>
2440
4079
  <script id="report-data" type="application/json">${n}</script>
2441
4080
  ${s?`<script id="artifact-manifest-data" type="application/json">${s}</script>`:""}
2442
- <script>${bs}</script>
4081
+ <script>${La}</script>
2443
4082
  </body>
2444
- </html>`}function Je(t,e,r,n){let s=t.replace(/<\//g,"<\\/"),a=e.replace(/<\//g,"<\\/"),l=n?n.replace(/<\//g,"<\\/"):null,i={};try{i=JSON.parse(t);}catch{}let d=`{
4083
+ </html>`}function Ct(t,e,r,n){let s=t.replace(/<\//g,"<\\/"),a=e.replace(/<\//g,"<\\/"),o=n?n.replace(/<\//g,"<\\/"):null,i={};try{i=JSON.parse(t);}catch{}let d=`{
2445
4084
  "schemaVersion":"2.0",
2446
4085
  "reportMode":"streaming",
2447
4086
  "serverPort":${r??"null"},
@@ -2455,7 +4094,7 @@ ${s?`<script id="artifact-manifest-data" type="application/json">${s}</script>`:
2455
4094
  "ci":${i.ci?JSON.stringify(i.ci).replace(/<\//g,"<\\/"):"null"},
2456
4095
  "metadata":${i.metadata?JSON.stringify(i.metadata).replace(/<\//g,"<\\/"):"null"},
2457
4096
  "shardRunIds":${i.shardRunIds?JSON.stringify(i.shardRunIds).replace(/<\//g,"<\\/"):"null"}
2458
- }`,o=`
4097
+ }`,l=`
2459
4098
  (function(){
2460
4099
  var data=JSON.parse(document.getElementById('report-data').textContent);
2461
4100
  var _serverPort=data.serverPort||null;
@@ -2525,20 +4164,30 @@ ${s?`<script id="artifact-manifest-data" type="application/json">${s}</script>`:
2525
4164
  window.__trServerPort=_serverPort;
2526
4165
  window.__trIsStreaming=true;
2527
4166
 
2528
- ${ze}
2529
- ${Ge}
2530
- ${Ve}
2531
- ${$e}
2532
- ${je}
2533
- ${Ke}
2534
- ${We}
4167
+ ${it}
4168
+ ${ct}
4169
+ ${ut}
4170
+ ${ot}
4171
+ ${lt}
4172
+ ${pt}
4173
+ ${gt}
4174
+ ${ft}
4175
+ ${dt}
4176
+ ${ht}
4177
+ ${vt}
4178
+ ${mt}
2535
4179
  applyTheme();
2536
4180
  renderRunMeta();
2537
4181
  renderSummary();
4182
+ renderTypeBand();
2538
4183
  initFilters();
2539
4184
  renderFilterBar();
2540
4185
  renderTestGrid();
2541
- checkServer(function(ok){});
4186
+ renderNeedsAttention();
4187
+ /* Route only once the health check has answered. Routing first meant a
4188
+ pasted link straight to a test always rendered "Server unavailable" \u2014
4189
+ the fetch ran while _serverAvailable was still false. */
4190
+ checkServer(function(ok){routeFromHash();});
2542
4191
  var _lo=document.getElementById('loading-overlay');if(_lo)_lo.remove();
2543
4192
  })();`;return `<!-- TestRelic AI Analytics Report \u2014 streaming mode -->
2544
4193
  <!DOCTYPE html>
@@ -2546,19 +4195,19 @@ ${s?`<script id="artifact-manifest-data" type="application/json">${s}</script>`:
2546
4195
  <head>
2547
4196
  <meta charset="UTF-8">
2548
4197
  <meta name="viewport" content="width=device-width,initial-scale=1">
2549
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob: 'self'; media-src blob: 'self'; connect-src 'self' http://127.0.0.1:*;">
4198
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob: file: 'self'; media-src blob: data: file: 'self'; connect-src 'self' http://127.0.0.1:*;">
2550
4199
  <title>TestRelic AI Analytics Report</title>
2551
4200
  ${r?`<meta name="report-server-port" content="${r}">`:""}
2552
4201
  ${r?`<meta name="artifact-server-port" content="${r}">`:""}
2553
- <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${Buffer.from(Ue).toString("base64")}">
2554
- <style>${qe}</style>
4202
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,${Buffer.from(st).toString("base64")}">
4203
+ <style>${nt}${yt}${bt}${xt}${wt}${kt}</style>
2555
4204
  <script>(function(){var p=localStorage.getItem('tr-theme')||'system';var t=p==='system'?(window.matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):p;document.documentElement.setAttribute('data-theme',t);})()</script>
2556
4205
  </head>
2557
4206
  <body>
2558
4207
  <div class="wrap">
2559
4208
  <div class="top-bar">
2560
4209
  <div class="brand">
2561
- ${He}
4210
+ ${at}
2562
4211
  <div class="brand-text">
2563
4212
  <span class="brand-title">TestRelic AI</span>
2564
4213
  <span class="brand-sub">Analytics Report</span>
@@ -2572,19 +4221,19 @@ ${r?`<meta name="artifact-server-port" content="${r}">`:""}
2572
4221
  </div>
2573
4222
  <a class="cta-btn" href="https://testrelic.ai" target="_blank" rel="noopener noreferrer"><svg class="cta-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.545 4.955L14.5 7.5l-4.955 1.545L8 14l-1.545-4.955L1.5 7.5l4.955-1.545L8 1z" fill="currentColor"/></svg>Advanced Insights with AI<span class="cta-sep">\u2013</span><strong>Get Started</strong><svg class="cta-arrow" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2.5 6h7M6.5 3l3 3-3 3"/></svg></a>
2574
4223
  </div>
2575
- <div id="summary-strip" class="summary-strip"></div>
2576
- <div id="filter-bar" class="filter-bar"></div>
2577
- <div id="test-grid"></div>
4224
+ <div id="view-overview">
4225
+ <div id="summary-strip"></div>
4226
+ <div id="type-band"></div>
4227
+ <div id="needs-attention"></div>
4228
+ <div id="filter-bar" class="filter-bar"></div>
4229
+ <div id="test-grid"></div>
4230
+ </div>
4231
+ <div id="view-detail" class="detail-page" hidden>
4232
+ <div class="detail-nav" id="detail-nav"></div>
4233
+ <div class="drawer-body" id="drawer-body"></div>
4234
+ </div>
2578
4235
  </div>
2579
4236
  <div class="loading-overlay" id="loading-overlay"><div class="loading-spinner"></div><div class="loading-text">Loading report...</div></div>
2580
- <div class="drawer-backdrop" id="drawer-backdrop"></div>
2581
- <aside class="drawer" id="drawer">
2582
- <div class="drawer-bar">
2583
- <span class="drawer-bar-title">Test Details</span>
2584
- <button class="drawer-close" id="drawer-close">&times;</button>
2585
- </div>
2586
- <div class="drawer-body" id="drawer-body"></div>
2587
- </aside>
2588
4237
  <div class="filter-drawer-backdrop" id="filter-drawer-backdrop"></div>
2589
4238
  <aside class="filter-drawer" id="filter-drawer">
2590
4239
  <div class="filter-drawer-bar">
@@ -2594,78 +4243,81 @@ ${r?`<meta name="artifact-server-port" content="${r}">`:""}
2594
4243
  <div class="filter-drawer-body" id="filter-drawer-body"></div>
2595
4244
  </aside>
2596
4245
  <script id="report-data" type="application/json">${d}</script>
2597
- ${l?`<script id="artifact-manifest-data" type="application/json">${l}</script>`:""}
2598
- <script>${o}</script>
4246
+ ${o?`<script id="artifact-manifest-data" type="application/json">${o}</script>`:""}
4247
+ <script>${l}</script>
2599
4248
  </body>
2600
- </html>`}function Ye(t){try{let e=process.platform,r,n;e==="darwin"?(r="open",n=[t]):e==="win32"?(r="cmd",n=["/c","start","",t]):(r="xdg-open",n=[t]),execFile(r,n,s=>{s&&process.stderr.write(`[testrelic] Failed to open browser: ${s.message}
2601
- `);});}catch{}}var ur=join(tmpdir(),"testrelic-data"),P=class{constructor(e){this.count=0;this.closed=false;mkdirSync(ur,{recursive:true}),this.filePath=join(ur,`${e}-${randomUUID().substring(0,8)}.jsonl`),this.fd=openSync(this.filePath,"w");}append(e){if(this.closed)return;let r=JSON.stringify(e)+`
2602
- `;writeSync(this.fd,r),this.count++;}getPath(){return this.filePath}getCount(){return this.count}close(){if(!this.closed){this.closed=true;try{closeSync(this.fd);}catch{}}}cleanup(){try{unlinkSync(this.filePath);}catch{}}};async function fr(t,e,r,n){let s=(e-1)*r,a=[],l=0,i=createInterface({input:createReadStream(t,{encoding:"utf-8"}),crlfDelay:1/0});for await(let c of i)if(c.length!==0){if(l>=s&&a.length<r)try{a.push(JSON.parse(c));}catch{}if(l++,a.length>=r&&n!==void 0)break}let d=n??l,o=Math.max(1,Math.ceil(d/r));return {items:a,total:d,page:e,pageSize:r,totalPages:o}}var mr=/^[a-f0-9]{12}$/,Qe=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,yr=500;function x(t,e,r){t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(r));}function vr(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 he(t){try{return existsSync(t)?JSON.parse(readFileSync(t,"utf-8")):null}catch{return null}}function me(t){let e=0;try{let r=readdirSync(t,{withFileTypes:!0});for(let n of r){let s=join(t,n.name);n.isFile()?e+=statSync(s).size:n.isDirectory()&&(e+=me(s));}}catch{}return e}function br(t,e,r,n){let s=he(join(r,"index.json"));x(e,200,{status:"ok",reportMode:"streaming",testCount:s?.length??0,uptime:Math.floor((Date.now()-n)/1e3)});}function wr(t,e,r){let n=he(join(r,"summary.json"));if(!n){x(e,404,{error:"Summary not found"});return}x(e,200,n);}function xr(t,e,r){let n=he(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,l=Math.max(1,parseInt(a.get("page")??"1",10)||1),i=Math.min(yr,Math.max(1,parseInt(a.get("pageSize")??"100",10)||100)),d=a.get("status")?.split(",").filter(Boolean)??null,o=a.get("file")??null,c=a.get("search")?.toLowerCase()??null,p=a.get("tag")?.split(",").filter(Boolean)??null,h=a.get("sort")??"file",u=a.get("order")==="desc"?-1:1,f=n;d&&d.length>0&&(f=f.filter(v=>d.includes(v.status))),o&&(f=f.filter(v=>v.filePath===o||v.filePath.startsWith(o+"/"))),c&&(f=f.filter(v=>v.title.toLowerCase().includes(c)||v.filePath.toLowerCase().includes(c))),p&&p.length>0&&(f=f.filter(v=>p.some(k=>v.tags.includes(k)))),f=[...f].sort((v,k)=>{let C=0;switch(h){case "duration":C=v.duration-k.duration;break;case "status":C=v.status.localeCompare(k.status);break;case "title":C=v.title.localeCompare(k.title);break;default:C=v.filePath.localeCompare(k.filePath);break}return C*u});let g=f.length,m=Math.max(1,Math.ceil(g/i)),y=(l-1)*i,w=f.slice(y,y+i);x(e,200,{tests:w,pagination:{page:l,pageSize:i,totalItems:g,totalPages:m},filters:{status:d,file:o,search:c,tag:p}});}function kr(t,e,r,n){if(!mr.test(n)){x(e,400,{error:"Invalid test ID format"});return}let s=join(r,"tests",n,"meta.json"),a=join(r,"tests",`${n}.json`),l=existsSync(s)?s:existsSync(a)?a:null;if(!l){x(e,404,{error:`Test not found: ${n}`});return}try{let i=readFileSync(l,"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 Tr(t,e,r,n,s){if(!mr.test(n)){x(e,400,{error:"Invalid test ID format"});return}let l=join(r,"tests",n,{network:"network.jsonl",console:"console.jsonl","api-calls":"api-calls.jsonl"}[s]);if(!existsSync(l)){x(e,200,{items:[],total:0,page:1,pageSize:50,totalPages:0});return}try{let d=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,o=Math.max(1,parseInt(d.get("page")??"1",10)||1),c=Math.min(yr,Math.max(1,parseInt(d.get("pageSize")??"50",10)||50)),p,h=join(r,"tests",n,"meta.json");if(existsSync(h))try{let f=JSON.parse(readFileSync(h,"utf-8"));switch(s){case "network":p=f.networkRequestsCount;break;case "console":p=f.consoleLogsCount;break;case "api-calls":p=f.apiCallsCount;break}}catch{}let u=await fr(l,o,c,p);x(e,200,u);}catch(i){x(e,500,{error:i instanceof Error?i.message:String(i)});}}function Cr(t,e,r){let n=he(join(r,"index.json"));if(!n){x(e,404,{error:"Test index not found"});return}let s=new Map;for(let l of n){if(l.isRetry)continue;let i=s.get(l.filePath);switch(i||(i={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0},s.set(l.filePath,i)),i.total++,l.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(([l,i])=>({filePath:l,...i})).sort((l,i)=>l.filePath.localeCompare(i.filePath));x(e,200,{files:a});}function Sr(t,e,r){if(!existsSync(r)){x(e,200,{runs:[],totalSizeBytes:0});return}try{let n=[],s=0,a=readdirSync(r,{withFileTypes:!0});for(let l of a){if(!l.isDirectory()||!Qe.test(l.name))continue;let i=join(r,l.name),d=me(i),o=readdirSync(i,{withFileTypes:!0}).filter(c=>c.isDirectory());n.push({folderName:l.name,totalSizeBytes:d,testCount:o.length}),s+=d;}n.sort((l,i)=>i.folderName.localeCompare(l.folderName)),x(e,200,{runs:n,totalSizeBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Rr(t,e,r){try{let n=0,s=0;if(existsSync(r)){let a=readdirSync(r,{withFileTypes:!0});for(let l of a){if(!l.isDirectory()||!Qe.test(l.name))continue;let i=join(r,l.name),d=me(i);rmSync(i,{recursive:!0,force:!0}),s+=d,n++;}}x(e,200,{deletedCount:n,freedBytes:s});}catch(n){x(e,500,{error:n instanceof Error?n.message:String(n)});}}function Ar(t,e,r,n){if(!Qe.test(n)){x(e,400,{error:"Invalid folder name"});return}let s=join(r,n);try{if(!statSync(s).isDirectory()){x(e,404,{error:"Not found"});return}}catch{x(e,404,{error:"Not found"});return}try{let a=me(s);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 _r(t,e,r){x(e,200,{status:"shutting_down"}),r.close();}function Ze(t,e,r){if(!existsSync(r)){x(e,404,{error:"File not found"});return}try{let n=readFileSync(r),s=extname(r).toLowerCase(),a=Ls[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 Ls={".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 Fs=9323,Ds=10,Bs=1800*1e3;function ae(t,e){return new Promise((r,n)=>{let s=e?.port??Fs,a=Date.now(),l,i=0,d=existsSync(join(t,"artifacts"))?join(t,"artifacts"):existsSync(join(t,"..","artifacts"))?join(t,"..","artifacts"):join(t,"artifacts");function o(){clearTimeout(l),l=setTimeout(()=>{p.close();},Bs);}let c=e?.htmlPath??null;if(!c){let u=dirname(t);try{let f=readdirSync(u).find(g=>g.endsWith(".html"));f&&(c=join(u,f));}catch{}}let p=createServer((u,f)=>{if(o(),vr(f),u.method==="OPTIONS"){f.writeHead(204),f.end();return}let g;try{g=new URL(u.url??"/",`http://${u.headers.host??"localhost"}`).pathname;}catch{x(f,400,{error:"Invalid URL"});return}if(u.method==="GET"&&(g==="/"||g==="/index.html")){if(c&&existsSync(c)){Ze(u,f,c);return}x(f,404,{error:"HTML report not found"});return}if(u.method==="GET"&&g==="/api/health"){br(u,f,t,a);return}if(u.method==="GET"&&g==="/api/summary"){wr(u,f,t);return}if(u.method==="GET"&&g==="/api/tests"){xr(u,f,t);return}if(u.method==="GET"&&g==="/api/files"){Cr(u,f,t);return}let m=g.match(/^\/api\/tests\/([a-f0-9]+)\/(network|console|api-calls)$/);if(u.method==="GET"&&m){Tr(u,f,t,m[1],m[2]);return}let y=g.match(/^\/api\/tests\/([a-f0-9]+)$/);if(u.method==="GET"&&y){kr(u,f,t,y[1]);return}if(u.method==="GET"&&g==="/api/artifacts"){Sr(u,f,d);return}if(u.method==="DELETE"&&g==="/api/artifacts"){Rr(u,f,d);return}let w=g.match(/^\/api\/artifacts\/(.+)$/);if(u.method==="DELETE"&&w){Ar(u,f,d,decodeURIComponent(w[1]));return}if(u.method==="GET"&&g.startsWith("/artifacts/")){let v=decodeURIComponent(g.slice(11));if(v.includes("..")||v.includes("\0")){x(f,400,{error:"Invalid path"});return}Ze(u,f,join(d,v));return}if(u.method==="POST"&&g==="/api/shutdown"){_r(u,f,p);return}x(f,404,{error:"Not found"});});function h(u){let f=g=>{g.code==="EADDRINUSE"&&i<Ds?(i++,h(u+1)):n(g);};p.once("error",f),p.listen(u,"127.0.0.1",()=>{p.removeListener("error",f);let g=p.address();if(!g||typeof g=="string"){n(new Error("Failed to get server address"));return}o(),r({port:g.port,dispose:()=>new Promise(m=>{clearTimeout(l),p.close(()=>m());})});});}h(s);})}async function Ir(t){let e=await ae(t);return {port:e.port,dispose:e.dispose}}function Us(t,e,r){let n=JSON.stringify(t),s=r?JSON.stringify(r):null;return cr(n,e,s)}async function et(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,l,i=e.htmlReportPath,d=dirname(i);mkdirSync(d,{recursive:!0});let o="",c="[]",p=null;if(a){o=JSON.stringify({...t.summary,testRunId:t.testRunId,startedAt:t.startedAt,completedAt:t.completedAt,totalDuration:t.totalDuration,ci:t.ci,metadata:t.metadata,shardRunIds:t.shardRunIds});let u=dirname(e.outputPath),f=join(u,".testrelic-report");try{let g=join(f,"index-compact.json"),m=join(f,"index.json");existsSync(g)?c=readFileSync(g,"utf-8"):existsSync(m)&&(c=readFileSync(m,"utf-8"));}catch{}p=r?JSON.stringify(r):null,l=Je(o,c,null,p);}else l=Us(t,null,r);let h=i+".tmp";if(writeFileSync(h,l,"utf-8"),renameSync(h,i),a){if(t.ci===null){let u=dirname(e.outputPath),f=join(u,".testrelic-report"),g=resolve(i);try{if(await zs(),n=await $s(f),!n){s=await ae(f,{htmlPath:g}),n=s.port;let m=setInterval(()=>{},6e4),y=()=>{clearInterval(m),s?.dispose();};process.on("SIGINT",y),process.on("SIGTERM",y),setTimeout(y,1800*1e3).unref();}if(n){process.stderr.write(`
4249
+ </html>`}function Tt(t){try{let e=process.platform,r,n;e==="darwin"?(r="open",n=[t]):e==="win32"?(r="cmd",n=["/c","start","",t]):(r="xdg-open",n=[t]),execFile(r,n,s=>{s&&process.stderr.write(`[testrelic] Failed to open browser: ${s.message}
4250
+ `);});}catch{}}var Vr=join(tmpdir(),"testrelic-data"),q=class{constructor(e){this.count=0;this.closed=false;mkdirSync(Vr,{recursive:true}),this.filePath=join(Vr,`${e}-${randomUUID().substring(0,8)}.jsonl`),this.fd=openSync(this.filePath,"w");}append(e){if(this.closed)return;let r=JSON.stringify(e)+`
4251
+ `;writeSync(this.fd,r),this.count++;}getPath(){return this.filePath}getCount(){return this.count}close(){if(!this.closed){this.closed=true;try{closeSync(this.fd);}catch{}}}cleanup(){try{unlinkSync(this.filePath);}catch{}}};async function Wr(t,e,r,n){let s=(e-1)*r,a=[],o=0,i=createInterface({input: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 d=n??o,l=Math.max(1,Math.ceil(d/r));return {items:a,total:d,page:e,pageSize:r,totalPages:l}}var Yr=/^[a-f0-9]{12}$/,At=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,Xr=500;function R(t,e,r){t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(r));}function Zr(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 Se(t){try{return existsSync(t)?JSON.parse(readFileSync(t,"utf-8")):null}catch{return null}}function Re(t){let e=0;try{let r=readdirSync(t,{withFileTypes:!0});for(let n of r){let s=join(t,n.name);n.isFile()?e+=statSync(s).size:n.isDirectory()&&(e+=Re(s));}}catch{}return e}function Qr(t,e,r,n){let s=Se(join(r,"index.json"));R(e,200,{status:"ok",reportMode:"streaming",testCount:s?.length??0,uptime:Math.floor((Date.now()-n)/1e3)});}function en(t,e,r){let n=Se(join(r,"summary.json"));if(!n){R(e,404,{error:"Summary not found"});return}R(e,200,n);}function tn(t,e,r){let n=Se(join(r,"index.json"));if(!n){R(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(Xr,Math.max(1,parseInt(a.get("pageSize")??"100",10)||100)),d=a.get("status")?.split(",").filter(Boolean)??null,l=a.get("file")??null,c=a.get("search")?.toLowerCase()??null,u=a.get("tag")?.split(",").filter(Boolean)??null,f=a.get("sort")??"file",p=a.get("order")==="desc"?-1:1,g=n;d&&d.length>0&&(g=g.filter(b=>d.includes(b.status))),l&&(g=g.filter(b=>b.filePath===l||b.filePath.startsWith(l+"/"))),c&&(g=g.filter(b=>b.title.toLowerCase().includes(c)||b.filePath.toLowerCase().includes(c))),u&&u.length>0&&(g=g.filter(b=>u.some(k=>b.tags.includes(k)))),g=[...g].sort((b,k)=>{let A=0;switch(f){case "duration":A=b.duration-k.duration;break;case "status":A=b.status.localeCompare(k.status);break;case "title":A=b.title.localeCompare(k.title);break;default:A=b.filePath.localeCompare(k.filePath);break}return A*p});let m=g.length,h=Math.max(1,Math.ceil(m/i)),v=(o-1)*i,y=g.slice(v,v+i);R(e,200,{tests:y,pagination:{page:o,pageSize:i,totalItems:m,totalPages:h},filters:{status:d,file:l,search:c,tag:u}});}function rn(t,e,r,n){if(!Yr.test(n)){R(e,400,{error:"Invalid test ID format"});return}let s=join(r,"tests",n,"meta.json"),a=join(r,"tests",`${n}.json`),o=existsSync(s)?s:existsSync(a)?a:null;if(!o){R(e,404,{error:`Test not found: ${n}`});return}try{let i=readFileSync(o,"utf-8");e.writeHead(200,{"Content-Type":"application/json"}),e.end(i);}catch(i){R(e,500,{error:i instanceof Error?i.message:String(i)});}}async function nn(t,e,r,n,s){if(!Yr.test(n)){R(e,400,{error:"Invalid test ID format"});return}let o=join(r,"tests",n,{network:"network.jsonl",console:"console.jsonl","api-calls":"api-calls.jsonl"}[s]);if(!existsSync(o)){R(e,200,{items:[],total:0,page:1,pageSize:50,totalPages:0});return}try{let d=new URL(t.url??"/",`http://${t.headers.host}`).searchParams,l=Math.max(1,parseInt(d.get("page")??"1",10)||1),c=Math.min(Xr,Math.max(1,parseInt(d.get("pageSize")??"50",10)||50)),u,f=join(r,"tests",n,"meta.json");if(existsSync(f))try{let g=JSON.parse(readFileSync(f,"utf-8"));switch(s){case "network":u=g.networkRequestsCount;break;case "console":u=g.consoleLogsCount;break;case "api-calls":u=g.apiCallsCount;break}}catch{}let p=await Wr(o,l,c,u);R(e,200,p);}catch(i){R(e,500,{error:i instanceof Error?i.message:String(i)});}}function sn(t,e,r){let n=Se(join(r,"index.json"));if(!n){R(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));R(e,200,{files:a});}function an(t,e,r){if(!existsSync(r)){R(e,200,{runs:[],totalSizeBytes:0});return}try{let n=[],s=0,a=readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!At.test(o.name))continue;let i=join(r,o.name),d=Re(i),l=readdirSync(i,{withFileTypes:!0}).filter(c=>c.isDirectory());n.push({folderName:o.name,totalSizeBytes:d,testCount:l.length}),s+=d;}n.sort((o,i)=>i.folderName.localeCompare(o.folderName)),R(e,200,{runs:n,totalSizeBytes:s});}catch(n){R(e,500,{error:n instanceof Error?n.message:String(n)});}}function on(t,e,r){try{let n=0,s=0;if(existsSync(r)){let a=readdirSync(r,{withFileTypes:!0});for(let o of a){if(!o.isDirectory()||!At.test(o.name))continue;let i=join(r,o.name),d=Re(i);rmSync(i,{recursive:!0,force:!0}),s+=d,n++;}}R(e,200,{deletedCount:n,freedBytes:s});}catch(n){R(e,500,{error:n instanceof Error?n.message:String(n)});}}function ln(t,e,r,n){if(!At.test(n)){R(e,400,{error:"Invalid folder name"});return}let s=join(r,n);try{if(!statSync(s).isDirectory()){R(e,404,{error:"Not found"});return}}catch{R(e,404,{error:"Not found"});return}try{let a=Re(s);rmSync(s,{recursive:!0,force:!0}),R(e,200,{deleted:n,freedBytes:a});}catch(a){R(e,500,{error:a instanceof Error?a.message:String(a)});}}function dn(t,e,r){R(e,200,{status:"shutting_down"}),r.close();}function _t(t,e,r){if(!existsSync(r)){R(e,404,{error:"File not found"});return}try{let n=statSync(r).size,s=extname(r).toLowerCase(),a=Wa[s]??"application/octet-stream",o=$a(t.headers.range,n);if(o==="unsatisfiable"){e.writeHead(416,{"Content-Range":`bytes */${n}`,"Accept-Ranges":"bytes"}),e.end();return}if(o){e.writeHead(206,{"Content-Type":a,"Content-Length":o.end-o.start+1,"Content-Range":`bytes ${o.start}-${o.end}/${n}`,"Accept-Ranges":"bytes"}),Kr(createReadStream(r,{start:o.start,end:o.end}),e);return}e.writeHead(200,{"Content-Type":a,"Content-Length":n,"Accept-Ranges":"bytes"}),Kr(createReadStream(r),e);}catch(n){R(e,500,{error:n instanceof Error?n.message:String(n)});}}function Kr(t,e){t.on("error",()=>{t.destroy(),e.destroy();}),e.on("close",()=>{t.destroy();}),t.pipe(e);}function $a(t,e){if(!t)return null;let r=/^bytes=(\d*)-(\d*)$/.exec(t.trim());if(!r)return null;let[,n,s]=r;if(n===""&&s==="")return null;let a,o;if(n===""){let i=Number(s);if(i<=0)return "unsatisfiable";a=Math.max(0,e-i),o=e-1;}else a=Number(n),o=s===""?e-1:Math.min(Number(s),e-1);return !Number.isFinite(a)||!Number.isFinite(o)?null:e===0||a>=e||a>o?"unsatisfiable":{start:a,end:o}}var Wa={".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 Ya=9323,Xa=10,Za=1800*1e3;function me(t,e){return new Promise((r,n)=>{let s=e?.port??Ya,a=Date.now(),o,i=0,d=existsSync(join(t,"artifacts"))?join(t,"artifacts"):existsSync(join(t,"..","artifacts"))?join(t,"..","artifacts"):join(t,"artifacts");function l(){clearTimeout(o),o=setTimeout(()=>{u.close();},Za);}let c=e?.htmlPath??null;if(!c){let p=dirname(t);try{let g=readdirSync(p).find(m=>m.endsWith(".html"));g&&(c=join(p,g));}catch{}}let u=createServer((p,g)=>{if(l(),Zr(g),p.method==="OPTIONS"){g.writeHead(204),g.end();return}let m;try{m=new URL(p.url??"/",`http://${p.headers.host??"localhost"}`).pathname;}catch{R(g,400,{error:"Invalid URL"});return}if(p.method==="GET"&&(m==="/"||m==="/index.html")){if(c&&existsSync(c)){_t(p,g,c);return}R(g,404,{error:"HTML report not found"});return}if(p.method==="GET"&&m==="/api/health"){Qr(p,g,t,a);return}if(p.method==="GET"&&m==="/api/summary"){en(p,g,t);return}if(p.method==="GET"&&m==="/api/tests"){tn(p,g,t);return}if(p.method==="GET"&&m==="/api/files"){sn(p,g,t);return}let h=m.match(/^\/api\/tests\/([a-f0-9]+)\/(network|console|api-calls)$/);if(p.method==="GET"&&h){nn(p,g,t,h[1],h[2]);return}let v=m.match(/^\/api\/tests\/([a-f0-9]+)$/);if(p.method==="GET"&&v){rn(p,g,t,v[1]);return}if(p.method==="GET"&&m==="/api/artifacts"){an(p,g,d);return}if(p.method==="DELETE"&&m==="/api/artifacts"){on(p,g,d);return}let y=m.match(/^\/api\/artifacts\/(.+)$/);if(p.method==="DELETE"&&y){ln(p,g,d,decodeURIComponent(y[1]));return}if(p.method==="GET"&&m.startsWith("/artifacts/")){let b=decodeURIComponent(m.slice(11));if(b.includes("..")||b.includes("\0")){R(g,400,{error:"Invalid path"});return}_t(p,g,join(d,b));return}if(p.method==="POST"&&m==="/api/shutdown"){dn(p,g,u);return}R(g,404,{error:"Not found"});});function f(p){let g=m=>{m.code==="EADDRINUSE"&&i<Xa?(i++,f(p+1)):n(m);};u.once("error",g),u.listen(p,"127.0.0.1",()=>{u.removeListener("error",g);let m=u.address();if(!m||typeof m=="string"){n(new Error("Failed to get server address"));return}l(),r({port:m.port,dispose:()=>new Promise(h=>{clearTimeout(o),u.close(()=>h());})});});}f(s);})}async function cn(t){let e=await me(t);return {port:e.port,dispose:e.dispose}}function ti(t,e,r){let n=JSON.stringify(t),s=r?JSON.stringify(r):null;return jr(n,e,s)}async function Mt(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,d=dirname(i);mkdirSync(d,{recursive:!0});let l="",c="[]",u=null;if(a){l=JSON.stringify({...t.summary,testRunId:t.testRunId,startedAt:t.startedAt,completedAt:t.completedAt,totalDuration:t.totalDuration,ci:t.ci,metadata:t.metadata,shardRunIds:t.shardRunIds});let p=dirname(e.outputPath),g=join(p,".testrelic-report");try{let m=join(g,"index-compact.json"),h=join(g,"index.json");existsSync(m)?c=readFileSync(m,"utf-8"):existsSync(h)&&(c=readFileSync(h,"utf-8"));}catch{}u=r?JSON.stringify(r):null,o=Ct(l,c,null,u);}else o=ti(t,null,r);let f=i+".tmp";if(writeFileSync(f,o,"utf-8"),renameSync(f,i),a){if(t.ci===null){let p=dirname(e.outputPath),g=join(p,".testrelic-report"),m=resolve(i);try{if(await ni(),n=await si(g),!n){s=await me(g,{htmlPath:m}),n=s.port;let h=setInterval(()=>{},6e4),v=()=>{clearInterval(h),s?.dispose();};process.on("SIGINT",v),process.on("SIGTERM",v),setTimeout(v,1800*1e3).unref();}if(n){process.stderr.write(`
2603
4252
  Report server: http://127.0.0.1:${n}
2604
- `);try{let m=Je(o,c,n,p),y=i+".tmp";writeFileSync(y,m,"utf-8"),renameSync(y,i);}catch{}}}catch{}}}else if(e.openReport&&t.ci===null&&e.includeArtifacts){let u=dirname(e.outputPath),f=join(u,"artifacts");if(existsSync(f))try{let g=await Ir(f);n=g.port,process.on("exit",()=>{g.dispose();});}catch{}}if(e.openReport&&t.ci===null)if(a&&n)Ye(`http://127.0.0.1:${n}`);else {let u=resolve(i);Ye(u);}}catch(n){process.stderr.write(`[testrelic] Failed to write HTML report: ${n instanceof Error?n.message:String(n)}
2605
- `);}}var be=9323,Nr=10;async function Hs(){for(let t=be;t<be+Nr;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 zs(){for(let t=be;t<be+Nr;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 $s(t){let r=[join(__dirname,"cli.cjs"),join(__dirname,"cli.js"),join(__dirname,"..","dist","cli.cjs"),join(__dirname,"..","dist","cli.js")].find(n=>existsSync(n));if(!r){let n=await ae(t);return process.on("exit",()=>{n?.dispose();}),n.port}return new Promise(n=>{let s=spawn(process.execPath,[r,"serve",t],{detached:true,stdio:["ignore","ignore","pipe"],env:{...process.env}}),a="",l=false,i=setTimeout(()=>{l||(l=true,s.stderr?.removeAllListeners(),Hs().then(n));},5e3);s.stderr?.on("data",d=>{a+=d.toString();let o=a.match(/127\.0\.0\.1:(\d+)/);o&&!l&&(l=true,clearTimeout(i),s.stderr?.removeAllListeners(),s.unref(),n(Number(o[1])));}),s.on("error",()=>{l||(l=true,clearTimeout(i),n(null));}),s.on("exit",()=>{l||(l=true,clearTimeout(i),n(null));}),s.unref();})}var Gs=20,rt=0,nt=[];async function st(t,e){rt>=Gs&&await new Promise(r=>nt.push(r)),rt++;try{return await copyFile(t,e),!0}catch{return false}finally{rt--,nt.length>0&&nt.shift()();}}var K=[];async function Dr(){K.length!==0&&(await Promise.allSettled(K),K.length=0);}function Br(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(!existsSync(join(t,n)))return n;let s=1;for(;existsSync(join(t,`${n}-${s}`));)s++;return `${n}-${s}`}function Fr(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"}var Vs=8;function Ks(t,e,r){let n=Fr(t),s=r?Fr(r).substring(0,Vs):"";return s&&(n+=`--${s}`),e>0&&(n+=`--retry-${e}`),n}function Or(t,e,r,n,s,a){let l=t.find(f=>f.name==="screenshot"&&f.path),i=t.find(f=>f.name==="video"&&f.path),d=t.find(f=>f.name==="trace"&&f.path);if(!l&&!i&&!d)return null;let o=Ks(e,r,a),c=s?["artifacts",s,o]:["artifacts",o],p=join(n,...c),h=c,u={};try{mkdirSync(p,{recursive:!0});}catch{return null}if(l?.path&&existsSync(l.path)){let g=`screenshot${extname(l.path)||".png"}`,m=join(p,g);K.push(st(l.path,m).then(()=>{})),u.screenshot=`${h.join("/")}/${g}`;}if(i?.path&&existsSync(i.path)){let g=`video${extname(i.path)||".webm"}`,m=join(p,g);K.push(st(i.path,m).then(()=>{})),u.video=`${h.join("/")}/${g}`;}if(d?.path&&existsSync(d.path)){let g=`trace${extname(d.path)||".zip"}`,m=join(p,g);K.push(st(d.path,m).then(()=>{})),u.trace=`${h.join("/")}/${g}`;}return !u.screenshot&&!u.video&&!u.trace?null:u}var Zs=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,Xs="1.0";function ea(t){let e=extname(t).toLowerCase();return [".png",".jpg",".jpeg",".gif",".bmp",".webp"].includes(e)?"screenshot":[".webm",".mp4",".avi",".mov"].includes(e)?"video":e===".zip"&&basename(t,e)==="trace"?"trace":"other"}function qr(t,e,r){let n=[];try{let s=readdirSync(t,{withFileTypes:!0});for(let a of s){if(!a.isFile())continue;let l=join(t,a.name),i=statSync(l);n.push({name:a.name,type:ea(a.name),relativePath:`artifacts/${e}/${r}/${a.name}`,sizeBytes:i.size});}}catch{}return {testName:r,files:n}}function ta(t,e){let r=join(t,e),n=[],s=0;try{let l=readdirSync(r,{withFileTypes:!0});for(let i of l){if(!i.isDirectory())continue;let d=qr(join(r,i.name),e,i.name);n.push(d);for(let o of d.files)s+=o.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((l,i)=>l.testName.localeCompare(i.testName)),{folderName:e,timestamp:a,totalSizeBytes:s,testCount:n.length,tests:n,isCurrentRun:false}}function Ur(t,e){let r=join(t,"artifacts"),n=[],s=[],a=0;try{let i=readdirSync(r,{withFileTypes:!0});for(let d of i)if(d.isDirectory())if(Zs.test(d.name)){let o=ta(r,d.name);n.push({...o,isCurrentRun:d.name===e});}else {let o=qr(join(r,d.name),d.name,d.name);s.push(o);for(let c of o.files)a+=c.sizeBytes;}}catch{}n.sort((i,d)=>d.timestamp.localeCompare(i.timestamp)),s.length>0&&(s.sort((i,d)=>i.testName.localeCompare(d.testName)),n.push({folderName:"__legacy__",timestamp:"1970-01-01T00:00:00",totalSizeBytes:a,testCount:s.length,tests:s,isCurrentRun:false}));let l=n.reduce((i,d)=>i+d.totalSizeBytes,0);return {schemaVersion:Xs,generatedAt:new Date().toISOString(),artifactBaseDir:"artifacts",totalSizeBytes:l,runs:n,serverPort:null}}var oa=256;function la(t){let e=resolve(t);for(let r=0;r<oa;r++){if(existsSync(join(e,".git")))return e;let n=dirname(e);if(n===e)return null;e=n;}return null}function $r(t){try{let e=la(t);if(!e)return;let r=join(e,".gitignore"),n=relative(e,t).replace(/\\/g,"/"),s=n.endsWith("/")?n:`${n}/`;if(existsSync(r)&&readFileSync(r,"utf-8").split(`
4253
+ `);try{let h=Ct(l,c,n,u),v=i+".tmp";writeFileSync(v,h,"utf-8"),renameSync(v,i);}catch{}}}catch{}}}else if(e.openReport&&t.ci===null&&e.includeArtifacts){let p=dirname(e.outputPath),g=join(p,"artifacts");if(existsSync(g))try{let m=await cn(g);n=m.port,process.on("exit",()=>{m.dispose();});}catch{}}if(e.openReport&&t.ci===null)if(a&&n)Tt(`http://127.0.0.1:${n}`);else {let p=resolve(i);Tt(p);}}catch(n){process.stderr.write(`[testrelic] Failed to write HTML report: ${n instanceof Error?n.message:String(n)}
4254
+ `);}}var Ie=9323,hn=10;async function ri(){for(let t=Ie;t<Ie+hn;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 ni(){for(let t=Ie;t<Ie+hn;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 si(t){let r=[join(__dirname,"cli.cjs"),join(__dirname,"cli.js"),join(__dirname,"..","dist","cli.cjs"),join(__dirname,"..","dist","cli.js")].find(n=>existsSync(n));if(!r){let n=await me(t);return process.on("exit",()=>{n?.dispose();}),n.port}return new Promise(n=>{let s=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(),ri().then(n));},5e3);s.stderr?.on("data",d=>{a+=d.toString();let l=a.match(/127\.0\.0\.1:(\d+)/);l&&!o&&(o=true,clearTimeout(i),s.stderr?.removeAllListeners(),s.unref(),n(Number(l[1])));}),s.on("error",()=>{o||(o=true,clearTimeout(i),n(null));}),s.on("exit",()=>{o||(o=true,clearTimeout(i),n(null));}),s.unref();})}var mn=Buffer.from([137,80,78,71,13,10,26,10]),ai=16,ii=20,Me=24;function vn(t){if(t.length<Me||!t.subarray(0,mn.length).equals(mn))return null;let e=t.readUInt32BE(ai),r=t.readUInt32BE(ii);return e===0||r===0?null:{width:e,height:r}}var oi=/(\d+) pixels \(ratio [\d.]+ of all image pixels\) are different/,yn=/Expected an image (\d+)px by (\d+)px, received (\d+)px by (\d+)px/;function Ee(t){return t?yn.test(t):false}var li=/^\s*Snapshot:\s*(.+?)\s*$/m;function bn(t){if(!t)return {};let e={},r=oi.exec(t);if(r){let a=Number.parseInt(r[1],10);Number.isFinite(a)&&(e.diffPixels=a);}let n=yn.exec(t);if(n){let[,a,o,i,d]=n,l=[a,o,i,d].map(c=>Number.parseInt(c,10));l.every(c=>Number.isFinite(c))&&(e.expectedWidth=l[0],e.expectedHeight=l[1],e.actualWidth=l[2],e.actualHeight=l[3]);}let s=li.exec(t);return s&&(e.snapshotName=s[1]),e}function xn(t,e){if(t===void 0||!e)return;let r=e.width*e.height;if(!(r<=0))return t/r}var gi=20,Et=0,Pt=[];async function ve(t,e){Et>=gi&&await new Promise(r=>Pt.push(r)),Et++;try{return await copyFile(t,e),!0}catch{return false}finally{Et--,Pt.length>0&&Pt.shift()();}}var W=[];async function kn(){W.length!==0&&(await Promise.allSettled(W),W.length=0);}function Cn(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(!existsSync(join(t,n)))return n;let s=1;for(;existsSync(join(t,`${n}-${s}`));)s++;return `${n}-${s}`}function Lt(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"}var hi=8;function Tn(t,e,r){let n=Lt(t),s=r?Lt(r).substring(0,hi):"";return s&&(n+=`--${s}`),e>0&&(n+=`--retry-${e}`),n}function Sn(t,e,r,n,s,a){let o=t.find(g=>g.name==="screenshot"&&g.path),i=t.find(g=>g.name==="video"&&g.path),d=t.find(g=>g.name==="trace"&&g.path);if(!o&&!i&&!d)return null;let l=Tn(e,r,a),c=s?["artifacts",s,l]:["artifacts",l],u=join(n,...c),f=c,p={};try{mkdirSync(u,{recursive:!0});}catch{return null}if(o?.path&&existsSync(o.path)){let m=`screenshot${extname(o.path)||".png"}`,h=join(u,m);W.push(ve(o.path,h).then(()=>{})),p.screenshot=`${f.join("/")}/${m}`;}if(i?.path&&existsSync(i.path)){let m=`video${extname(i.path)||".webm"}`,h=join(u,m);W.push(ve(i.path,h).then(()=>{})),p.video=`${f.join("/")}/${m}`;}if(d?.path&&existsSync(d.path)){let m=`trace${extname(d.path)||".zip"}`,h=join(u,m);W.push(ve(d.path,h).then(()=>{})),p.trace=`${f.join("/")}/${m}`;}return !p.screenshot&&!p.video&&!p.trace?null:p}var Nt="visual",mi=6;function vi(t){let e=createHash("sha256").update(t).digest("hex").substring(0,mi);return `${Lt(t)}--${e}`}function wn(t){let e=null;try{e=openSync(t,"r");let r=Buffer.alloc(Me),n=readSync(e,r,0,Me,0);return vn(r.subarray(0,n))}catch{return null}finally{if(e!==null)try{closeSync(e);}catch{}}}var yi=[{key:"expectedPath",dest:"baseline",field:"baseline"},{key:"actualPath",dest:"actual",field:"actual"},{key:"diffPath",dest:"diff",field:"diff"},{key:"previousPath",dest:"previous",field:"previous"}],bi=[{key:"baselineDomPath",dest:"baseline.dom.json",field:"baselineDom"},{key:"actualDomPath",dest:"actual.dom.json",field:"actualDom"}];function xi(t,e,r){let n=vi(t.name),s=join(e,n);try{mkdirSync(s,{recursive:!0});}catch{return null}let a={},o=false;for(let c of yi){let u=t[c.key];if(!u||!existsSync(u))continue;let f=extname(u)||".png",p=`${c.dest}${f}`;W.push(ve(u,join(s,p)).then(()=>{})),a[c.field]=`${r}/${Nt}/${n}/${p}`,o=true;}let i=t.dom;if(i)for(let c of bi){let u=i[c.key];!u||!existsSync(u)||(W.push(ve(u,join(s,c.dest)).then(()=>{})),a[c.field]=`${r}/${Nt}/${n}/${c.dest}`);}if(!o)return null;let d=t.expectedPath?wn(t.expectedPath):t.actualPath?wn(t.actualPath):null,l=xn(t.diffPixels,d);return {name:t.name,status:t.status,source:t.source,...a,...d?{width:d.width,height:d.height}:{},...t.actualWidth!==void 0?{actualWidth:t.actualWidth}:{},...t.actualHeight!==void 0?{actualHeight:t.actualHeight}:{},...t.diffPixels!==void 0?{diffPixels:t.diffPixels}:{},...l!==void 0?{diffPixelRatio:l}:{},...i?{domChanges:i.changes,domChangeCount:i.total,domRootCauses:i.rootCauses,domTruncated:i.truncated,domCaptureBox:i.captureBox}:{}}}function Rn(t,e,r,n,s,a){if(t.length===0)return null;let o=Tn(e,r,a),i=s?["artifacts",s,o]:["artifacts",o],d=join(n,...i,Nt),l=i.join("/");try{mkdirSync(d,{recursive:!0});}catch{return null}let c=[];for(let u of t){let f=xi(u,d,l);f&&c.push(f);}return c.length>0?c:null}function An(t){let{styleProps:e,defaults:r,inherited:n,maxNodes:s,maxText:a,maskBoxes:o,viewportBox:i,fullPage:d}=t,l={};for(let T=0;T<n.length;T++)l[n[T]]=true;let c=window.scrollX||window.pageXOffset||0,u=window.scrollY||window.pageYOffset||0,f=[],p=0,g=[];for(let T=0;T<o.length;T++){let w=o[T];g.push([w[0]+c,w[1]+u,w[2],w[3]]);}function m(T,w,S,x){for(let C=0;C<g.length;C++){let _=g[C];if(T<_[0]+_[2]&&T+S>_[0]&&w<_[1]+_[3]&&w+x>_[1])return true}return false}function h(T){let w=T.parentElement;if(!w)return 0;let S=w.children,x=0;for(let C=0;C<S.length;C++){let _=S[C];if(_===T)break;_.tagName===T.tagName&&x++;}return x}function v(T){let w="",S=T.childNodes;for(let x=0;x<S.length;x++){let C=S[x];C.nodeType===3&&(w+=C.nodeValue??"");}return w=w.replace(/\s+/g," ").trim(),w.length>a?w.slice(0,a):w}function y(T,w){let S;for(let x=0;x<e.length;x++){let C=e[x],_=T.getPropertyValue(C);if(_){if(l[C]){if(w&&w.getPropertyValue(C)===_)continue}else if(r[C]===_)continue;S||(S={}),S[C]=_;}}return S}let b=[{el:document.body,parentPath:"",parentComputed:null}];for(;b.length>0;){let T=b.pop(),w=T.el,S=window.getComputedStyle(w);if(S.display==="none")continue;let x=w.tagName.toLowerCase(),C=w.id?"#"+w.id:T.parentPath?T.parentPath+">"+x+"["+h(w)+"]":x,_=w.children;for(let P=_.length-1;P>=0;P--)b.push({el:_[P],parentPath:C,parentComputed:S});if(S.visibility==="hidden")continue;let L=w.getBoundingClientRect();if(L.width<=0||L.height<=0)continue;if(f.length>=s){p++;continue}let z=Math.round(L.left+c),ce=Math.round(L.top+u),ue=Math.round(L.width),pe=Math.round(L.height),I={p:C,t:x,r:[z,ce,ue,pe]};w.id&&(I.i=w.id);let M=w.getAttribute("class");M&&(I.c=M.replace(/\s+/g," ").trim());let N=v(w);N&&(I.x=N);let j=y(S,T.parentComputed);j&&(I.s=j),m(z,ce,ue,pe)&&(I.m=1),f.push(I);}let k=Math.max(document.documentElement.scrollWidth,window.innerWidth),A=Math.max(document.documentElement.scrollHeight,window.innerHeight),E;return i?E=[Math.round(i[0]+c),Math.round(i[1]+u),Math.round(i[2]),Math.round(i[3])]:d?E=[0,0,k,A]:E=[Math.round(c),Math.round(u),window.innerWidth,window.innerHeight],{version:"1.0",url:location.href,viewport:{width:window.innerWidth,height:window.innerHeight},content:{width:k,height:A},captureBox:E,nodes:f,truncatedNodes:p}}var Ri=["display","position","color","background-color","background-image","border-top-width","border-right-width","border-bottom-width","border-left-width","border-color","border-style","border-radius","box-shadow","opacity","transform","margin-top","margin-right","margin-bottom","margin-left","padding-top","padding-right","padding-bottom","padding-left","font-family","font-size","font-weight","line-height","letter-spacing","text-align","text-decoration-line","text-transform","flex-direction","justify-content","align-items","gap","z-index","overflow"],Ai={position:"static","background-color":"rgba(0, 0, 0, 0)","background-image":"none","border-top-width":"0px","border-right-width":"0px","border-bottom-width":"0px","border-left-width":"0px","border-style":"none","border-radius":"0px","box-shadow":"none",opacity:"1",transform:"none","margin-top":"0px","margin-right":"0px","margin-bottom":"0px","margin-left":"0px","padding-top":"0px","padding-right":"0px","padding-bottom":"0px","padding-left":"0px","text-decoration-line":"none","flex-direction":"row","justify-content":"normal","align-items":"normal",gap:"normal","z-index":"auto",overflow:"visible"},_i=["color","font-family","font-size","font-weight","line-height","letter-spacing","text-align","text-transform"],Ii=1024*1024,Mi=1500,Ei=200;async function Pi(t){let e=[];for(let r of t)try{let n=await r.boundingBox();n&&e.push([n.x,n.y,n.width,n.height]);}catch{}return e}async function _n(t){let e=t.config??{};try{let r=t.maskLocators?.length?await Pi(t.maskLocators):[],n=null;if(t.bounded){let a=await t.bounded.boundingBox();a&&(n=[a.x,a.y,a.width,a.height]);}let s={styleProps:Ri,defaults:Ai,inherited:_i,maxNodes:e.maxNodes??Mi,maxText:e.maxText??Ei,maskBoxes:r,viewportBox:n,fullPage:t.fullPage===!0};return await t.target.evaluate(An,s)}catch{return null}}function In(t,e=Ii){let r=JSON.stringify(t);if(Buffer.byteLength(r,"utf-8")<=e)return {json:r,truncatedBytes:false};let n=t.nodes.slice(),s=0;for(;n.length>0&&Buffer.byteLength(r,"utf-8")>e;){let a=Math.max(1,Math.floor(n.length*.1));n.splice(-a,a),s+=a,r=JSON.stringify({...t,nodes:n,truncatedNodes:t.truncatedNodes+s});}return {json:r,truncatedBytes:true}}function Mn(t){return t.replace(/\.[A-Za-z0-9]+$/,"")+".dom.json"}function En(t){try{if(!existsSync(t))return null;let e=JSON.parse(readFileSync(t,"utf-8"));return Li(e)?e:null}catch{return null}}function Pn(t,e){try{return mkdirSync(dirname(t),{recursive:!0}),writeFileSync(t,e,"utf-8"),!0}catch{return false}}function Li(t){if(typeof t!="object"||t===null)return false;let e=t;return typeof e.version=="string"&&Array.isArray(e.nodes)&&Array.isArray(e.captureBox)&&e.captureBox.length===4}var Ni=new Set(["moved","resized"]);function Le(t){return t.some(e=>!Ni.has(e))}function Ft(t){let e=t.t;if(t.i&&(e+="#"+t.i),t.c){let r=t.c.split(" ").filter(Boolean).slice(0,3);r.length>0&&(e+="."+r.join("."));}return e}function Di(t,e){let r=[],n=Math.abs(t.r[0]-e.r[0]),s=Math.abs(t.r[1]-e.r[1]),a=Math.abs(t.r[2]-e.r[2]),o=Math.abs(t.r[3]-e.r[3]);return (n>1||s>1)&&r.push("moved"),(a>1||o>1)&&r.push("resized"),r}function Fi(t,e){let r=t.s??{},n=e.s??{},s=new Set([...Object.keys(r),...Object.keys(n)]),a=[];for(let o of s){let i=r[o],d=n[o];i!==d&&a.push({prop:o,from:i??"(initial)",to:d??"(initial)"});}return a.sort((o,i)=>o.prop.localeCompare(i.prop)),a}function Ln(t){return t.i?"i:"+t.t+"#"+t.i:t.c?"c:"+t.t+"."+t.c:null}function Bi(t,e){let r=new Map;for(let u of t)r.set(u.p,u);let n=[],s=[],a=new Set;for(let u of e){let f=r.get(u.p);if(f&&f.t!==u.t){s.push(u);continue}f&&!a.has(f)?(a.add(f),n.push({before:f,after:u})):s.push(u);}let o=t.filter(u=>!a.has(u)),i=new Map;for(let u of o){let f=Ln(u);if(!f)continue;let p=i.get(f);p?p.push(u):i.set(f,[u]);}let d=[],l=new Set;for(let u of s){let f=Ln(u),g=(f?i.get(f):void 0)?.find(m=>!l.has(m));g?(l.add(g),n.push({before:g,after:u})):d.push(u);}let c=o.filter(u=>!l.has(u));return {pairs:n,removed:c,added:d}}function Oi(t,e){let r=[];return (t.i??"")!==(e.i??"")&&r.push({prop:"id",from:t.i??"(none)",to:e.i??"(none)"}),(t.c??"")!==(e.c??"")&&r.push({prop:"class",from:t.c??"(none)",to:e.c??"(none)"}),r}function Bt(t,e){let r=(e[2]??0)*(e[3]??0);return r<=0?0:Math.min(1,t.r[2]*t.r[3]/r)}function Nn(t,e,r={}){let n=r.maxChanges??50,s=r.includeText!==false,a=r.includeStyles!==false,o=t.nodes.filter(g=>g.m!==1),i=e.nodes.filter(g=>g.m!==1),{pairs:d,removed:l,added:c}=Bi(o,i),u=[];for(let{before:g,after:m}of d){let h=Di(g,m),v=a?Fi(g,m):[];v.length>0&&h.push("restyled");let y=Oi(g,m);y.length>0&&h.push("attrs");let b=s&&(g.x??"")!==(m.x??"");if(b&&h.push("text"),h.length===0)continue;let k=Bt(m,e.captureBox);u.push({score:k,change:{path:m.p,label:Ft(m),kinds:h,rect:m.r,baselineRect:g.r,areaPct:k,...y.length>0?{attrs:y}:{},...v.length>0?{styles:v}:{},...b?{text:{from:g.x??"",to:m.x??""}}:{}}});}for(let g of c){let m=Bt(g,e.captureBox);u.push({score:m,change:{path:g.p,label:Ft(g),kinds:["added"],rect:g.r,areaPct:m}});}for(let g of l){let m=Bt(g,t.captureBox);u.push({score:m,change:{path:g.p,label:Ft(g),kinds:["removed"],baselineRect:g.r,areaPct:m}});}let f=u.filter(g=>Le(g.change.kinds)).length;return u.sort((g,m)=>{if(f>0){let h=Le(g.change.kinds)?1:0,v=Le(m.change.kinds)?1:0;if(h!==v)return v-h}return m.score-g.score}),{changes:u.slice(0,n).map((g,m)=>({rank:m+1,...g.change,...f>0&&!Le(g.change.kinds)?{consequence:true}:{}})),total:u.length,rootCauses:f}}var Dn="__testrelic_snapshot_path_probe__.png",Fn=Symbol.for("testrelic.snapshotPathWarned");function qi(){let t=globalThis,e=t[Fn];return e||(e={warned:false},t[Fn]=e),e}function Ne(t){try{let e=t.snapshotPath(Dn,{kind:"screenshot"});return typeof e=="string"&&e.endsWith(Dn)}catch{return false}}function De(t){let e=qi();e.warned||(e.warned=true,process.stderr.write(`[testrelic] ${t} needs Playwright >= 1.51 for testInfo.snapshotPath(name, { kind }); this project's Playwright resolves that call the older way. Everything else in the reporter is unaffected \u2014 upgrade Playwright to enable it.
4255
+ `));}var Ot="testrelic-visual-dom",Vi=new Set(["toHaveScreenshot","toMatchVisualBaseline"]);function Bn(t){if(typeof t!="object"||t===null)return false;let e=t;return typeof e.boundingBox=="function"&&typeof e.page=="function"}function $i(t){if(typeof t!="object"||t===null)return false;let e=t;return typeof e.evaluate=="function"&&typeof e.screenshot=="function"}function Wi(t){let e=t[0];if(typeof e=="string")return e;if(Array.isArray(e))return e.join("/");if(typeof e=="object"&&e!==null){let r=e.name;if(typeof r=="string")return r}return null}function Gi(t){for(let e of t)if(typeof e=="object"&&e!==null&&!Array.isArray(e))return e;return {}}function Ki(t){let e=t.mask;return Array.isArray(e)?e.filter(r=>typeof r=="object"&&r!==null&&typeof r.boundingBox=="function"):[]}function Ji(t){return /\.(png|jpg|jpeg)$/i.test(t)?t:`${t}.png`}var Yi=/^(.+)-(expected|actual|diff|previous)\.[A-Za-z0-9]+$/;function Xi(t,e,r){let n=t.attachments;for(let s=e;s<n.length;s++){let a=Yi.exec(n[s]?.name??"");if(a)return a[1]}return r}async function Zi(t){let e=Gi(t.args),r=Bn(t.received)?t.received:null,n=r?r.page():t.received;return _n({target:n,bounded:r,maskLocators:Ki(e),fullPage:e.fullPage===true})}function Qi(t){let e=t.config?.updateSnapshots;return e==="all"||e==="changed"}async function eo(t,e){let r=Wi(t.args);if(!r)return;if(!Ne(t.testInfo)){De("DOM comparison");return}let n=t.testInfo.snapshotPath(Ji(r),{kind:"screenshot"}),s=Mn(n),a=Xi(t.testInfo,t.attachedBefore,basename(n,extname(n))),o=In(e),d=Qi(t.testInfo)?null:En(s);if(!d){Pn(s,o.json);return}let{changes:l,total:c,rootCauses:u}=Nn(d,e),f;try{f=t.testInfo.outputPath(`${a}.actual.dom.json`),writeFileSync(f,o.json,"utf-8");}catch{f=void 0;}let p={stem:a,changes:l,total:c,rootCauses:u,truncated:o.truncatedBytes||e.truncatedNodes>0,captureBox:e.captureBox,...existsSync(s)?{baselineDomPath:s}:{},...f?{actualDomPath:f}:{}};await t.testInfo.attach(Ot,{body:Buffer.from(JSON.stringify([p])),contentType:"application/json"});}function On(t,e){let r=function(o,...i){let d=t(o,...i);return !(Bn(o)||$i(o))||typeof d!="object"||d===null?d:new Proxy(d,{get(c,u,f){let p=Reflect.get(c,u,f);if(typeof u!="string"||!Vi.has(u)||typeof p!="function")return p;let g=p;return async function(...h){let v=e(),y=null,b=v?.attachments.length??0;if(v)try{y=await Zi({received:o,args:h,testInfo:v,attachedBefore:b});}catch{}try{return await g.apply(c,h)}finally{if(v&&y)try{await eo({received:o,args:h,testInfo:v,attachedBefore:b},y);}catch{}}}}})},n=t,s=r;for(let a of Object.keys(n))s[a]=n[a];return r}var qt="testrelic-visual",to=/^(.+)-(expected|actual|diff|previous)(\.[A-Za-z0-9]+)$/;function ro(t){let e=new Map;for(let r of t)if(!(r.name!==qt||!r.body))try{let n=JSON.parse(r.body.toString("utf-8"));if(!Array.isArray(n))continue;for(let s of n){let a=so(s);a&&e.set(a.stem,a);}}catch{}return e}var no=["passed","failed","new","updated"];function so(t){if(typeof t!="object"||t===null)return null;let e=t;return typeof e.stem!="string"||!e.stem||typeof e.name!="string"||!e.name||typeof e.status!="string"||!no.includes(e.status)?null:{stem:e.stem,name:e.name,status:e.status}}function ao(t){let e=new Map;for(let r of t)if(!(r.name!==Ot||!r.body))try{let n=JSON.parse(r.body.toString("utf-8"));if(!Array.isArray(n))continue;for(let s of n){if(typeof s!="object"||s===null)continue;let a=s;typeof a.stem!="string"||!Array.isArray(a.changes)||e.set(a.stem,s);}}catch{}return e}function io(t){let e=new Map;for(let r of t){if(!r.path)continue;let n=to.exec(r.name);if(!n)continue;let[,s,a]=n,o=e.get(s)??{};o[a]=r.path,e.set(s,o);}return e}function oo(t){return !!(t.actual&&(t.expected||t.diff))}function lo(t,e,r){return t.diff||Ee(r)?"failed":e?"updated":"new"}function co(t,e){if(t.length===0)return null;if(t.length===1)return t[0];if(!e)return null;let r=e.replace(/\.[A-Za-z0-9]+$/,""),n=t.filter(s=>s===r||s.endsWith(`/${r}`));return n.length===1?n[0]:null}function Hn(t,e={}){if(t.length===0)return [];let r=ro(t),n=ao(t),s=io(t);if(s.size===0)return [];let a=e.updatingSnapshots===true,o=[];for(let[i,d]of s){let l=r.get(i);!l&&!oo(d)||o.push({name:l?.name??i,status:l?.status??lo(d,a,e.failureMessage),source:l?"toMatchVisualBaseline":"toHaveScreenshot",...d.expected?{expectedPath:d.expected}:{},...d.actual?{actualPath:d.actual}:{},...d.diff?{diffPath:d.diff}:{},...d.previous?{previousPath:d.previous}:{},...n.has(i)?{dom:n.get(i)}:{}});}return uo(o,s,e.failureMessage)}function uo(t,e,r){let n=bn(r);if(n.diffPixels===void 0&&n.actualWidth===void 0)return [...t];let s=t.filter(o=>o.status==="failed").map(o=>qn(o,e)),a=co(s,n.snapshotName);return a===null?[...t]:t.map(o=>qn(o,e)===a?{...o,...n.diffPixels!==void 0?{diffPixels:n.diffPixels}:{},...n.actualWidth!==void 0?{actualWidth:n.actualWidth}:{},...n.actualHeight!==void 0?{actualHeight:n.actualHeight}:{}}:o)}function qn(t,e){if(e.has(t.name))return t.name;for(let[r,n]of e)if(n.actual===t.actualPath&&n.expected===t.expectedPath&&n.diff===t.diffPath)return r;return t.name}var po=new RegExp("[\x1B\x9B][[\\]()#;?]*(?:\\d{1,4}(?:;\\d{0,4})*)?[0-9A-PR-TZcf-nqry=><]","g");function se(t){return !t||t.indexOf("\x1B")===-1&&t.indexOf("\x9B")===-1?t:t.replace(po,"")}function Un(t){return t==null?null:se(t)}var fo=/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/,go="1.0",Ht="visual";function $n(t){let e=extname(t).toLowerCase();return [".png",".jpg",".jpeg",".gif",".bmp",".webp"].includes(e)?"screenshot":[".webm",".mp4",".avi",".mov"].includes(e)?"video":e===".zip"&&basename(t,e)==="trace"?"trace":"other"}function ho(t){if(t.endsWith(".dom.json"))return "dom";let e=basename(t,extname(t));return e==="baseline"?"baseline":e==="diff"?"diff":$n(t)}function mo(t,e,r){let n=[];try{for(let s of readdirSync(t,{withFileTypes:!0})){if(!s.isFile())continue;let a=statSync(join(t,s.name));n.push({name:`${Ht}/${r}/${s.name}`,type:ho(s.name),relativePath:`${e}/${Ht}/${r}/${s.name}`,sizeBytes:a.size});}}catch{}return n}function Wn(t,e,r){let n=[],s=`artifacts/${e}/${r}`;try{let a=readdirSync(t,{withFileTypes:!0});for(let o of a){if(o.isDirectory()&&o.name===Ht){let l=join(t,o.name);for(let c of readdirSync(l,{withFileTypes:!0}))c.isDirectory()&&n.push(...mo(join(l,c.name),s,c.name));continue}if(!o.isFile())continue;let i=join(t,o.name),d=statSync(i);n.push({name:o.name,type:$n(o.name),relativePath:`${s}/${o.name}`,sizeBytes:d.size});}}catch{}return {testName:r,files:n}}function vo(t,e){let r=join(t,e),n=[],s=0;try{let o=readdirSync(r,{withFileTypes:!0});for(let i of o){if(!i.isDirectory())continue;let d=Wn(join(r,i.name),e,i.name);n.push(d);for(let l of d.files)s+=l.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 Gn(t,e){let r=join(t,"artifacts"),n=[],s=[],a=0;try{let i=readdirSync(r,{withFileTypes:!0});for(let d of i)if(d.isDirectory())if(fo.test(d.name)){let l=vo(r,d.name);n.push({...l,isCurrentRun:d.name===e});}else {let l=Wn(join(r,d.name),d.name,d.name);s.push(l);for(let c of l.files)a+=c.sizeBytes;}}catch{}n.sort((i,d)=>d.timestamp.localeCompare(i.timestamp)),s.length>0&&(s.sort((i,d)=>i.testName.localeCompare(d.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,d)=>i+d.totalSizeBytes,0);return {schemaVersion:go,generatedAt:new Date().toISOString(),artifactBaseDir:"artifacts",totalSizeBytes:o,runs:n,serverPort:null}}var Co=256;function To(t){let e=resolve(t);for(let r=0;r<Co;r++){if(existsSync(join(e,".git")))return e;let n=dirname(e);if(n===e)return null;e=n;}return null}function Yn(t){try{let e=To(t);if(!e)return;let r=join(e,".gitignore"),n=relative(e,t).replace(/\\/g,"/"),s=n.endsWith("/")?n:`${n}/`;if(existsSync(r)&&readFileSync(r,"utf-8").split(`
2606
4256
  `).map(d=>d.trim()).some(d=>d===s||d===n))return;let a=`
2607
4257
  # TestRelic test artifacts
2608
4258
  ${s}
2609
- `;appendFileSync(r,a,"utf-8");}catch{}}var jr=Symbol("testrelic.timeout"),Wr=3e4,it=12e4,ot=6e4,Gr=15e3;async function $(t,e,r){let n,s=new Promise(a=>{n=setTimeout(()=>a(jr),e),typeof n?.unref=="function"&&n.unref();});try{let a=await Promise.race([t(),s]);if(a===jr){process.stderr.write(`[testrelic] ${r} timed out after ${Math.round(e/1e3)}s \u2014 continuing without it. Your tests are unaffected.
4259
+ `;appendFileSync(r,a,"utf-8");}catch{}}var Xn=Symbol("testrelic.timeout"),Zn=3e4,Ut=12e4,zt=6e4,Qn=15e3;async function Q(t,e,r){let n,s=new Promise(a=>{n=setTimeout(()=>a(Xn),e),typeof n?.unref=="function"&&n.unref();});try{let a=await Promise.race([t(),s]);if(a===Xn){process.stderr.write(`[testrelic] ${r} timed out after ${Math.round(e/1e3)}s \u2014 continuing without it. Your tests are unaffected.
2610
4260
  `);return}return a}catch(a){process.stderr.write(`[testrelic] ${r} failed \u2014 continuing without it. Your tests are unaffected. (${a instanceof Error?a.message:String(a)})
2611
- `);return}finally{n&&clearTimeout(n);}}function Jr(t,e){let r=[];for(let n of t){let s=da(n),a=ca(n),l=ua(n,s,a,e),i=pa(n,s,a);if(l.length===0&&i.length===0){r.push({type:"navigation",url:"about:blank",timestamp:n.startedAt,durationOnUrl:n.duration,navigationType:"dummy",domContentLoadedAt:null,networkIdleAt:null,networkStats:null,specFile:n.specFile,test:s,tests:[a],_testTitle:n.title});continue}r.push(...l,...i);}return r.sort(fa),ga(r,t),r.map((n,s)=>n.type==="navigation"?{...{index:s,type:"navigation",url:n.url,timestamp:n.timestamp,durationOnUrl:n.durationOnUrl??0,navigationType:n.navigationType,domContentLoadedAt:n.domContentLoadedAt??null,networkIdleAt:n.networkIdleAt??null,networkStats:n.networkStats??null,specFile:n.specFile,test:n.test},tests:n.tests}:{...{index:s,type:"api_call",callId:n.callId,method:n.method,url:n.url,timestamp:n.timestamp,responseTime:n.responseTime??null,request:n.request,response:n.response??null,...n.error?{error:n.error}:{},assertions:n.assertions??[],specFile:n.specFile,test:n.test},tests:n.tests})}function da(t){return {title:t.title,fullTitle:t.titlePath,status:t.status,duration:t.duration,retries:t.retryCount,retry:t.retry,tags:t.tags,failure:t.failure}}function ca(t){return {title:t.title,status:t.status,duration:t.duration,startedAt:t.startedAt,completedAt:t.completedAt,retryCount:t.retryCount,tags:t.tags,failure:t.failure,testId:t.testId,filePath:t.filePath,suiteName:t.suiteName,testType:t.testType,isFlaky:t.isFlaky,retryStatus:t.retryStatus,expectedStatus:t.expectedStatus,actualStatus:t.actualStatus,artifacts:t.artifacts,networkRequests:t.networkRequests,apiCalls:t.apiCalls,apiAssertions:t.apiAssertions,actions:t.actions,consoleLogs:t.consoleLogs}}function ua(t,e,r,n){let s=[];for(let a of t.navigations)n.navigationTypes!==null&&!n.navigationTypes.includes(a.navigationType)||s.push({type:"navigation",url:a.url,timestamp:a.timestamp,durationOnUrl:0,navigationType:a.navigationType,domContentLoadedAt:a.domContentLoadedAt??null,networkIdleAt:a.networkIdleAt??null,networkStats:a.networkStats??null,specFile:t.specFile,test:e,tests:[r],_testTitle:t.title});return s}function pa(t,e,r){if(!t.apiCalls||t.apiCalls.length===0)return [];let n=t.apiAssertions??[];return t.apiCalls.map(s=>{let a=n.filter(i=>i.callId===s.id).map(i=>({type:i.type,expected:i.expected,actual:i.actual,status:i.status,location:i.location,...i.expression!==void 0?{expression:i.expression}:{}})),l=null;return s.responseStatusCode!==null&&s.responseStatusText!==null&&(l={statusCode:s.responseStatusCode,statusText:s.responseStatusText,headers:s.responseHeaders,body:Kr(s.responseBody)}),{type:"api_call",callId:s.id,method:s.method,url:s.url,timestamp:s.timestamp,responseTime:s.error?null:s.responseTimeMs,request:{headers:s.requestHeaders,body:Kr(s.requestBody)},response:l,...s.error?{error:s.error}:{},assertions:a,specFile:t.specFile,test:e,tests:[r],_testTitle:t.title}})}function fa(t,e){let r=new Date(t.timestamp).getTime(),n=new Date(e.timestamp).getTime();if(r!==n)return r-n;let s={navigation:0,api_call:1},a=s[t.type],l=s[e.type];return a!==l?a-l:t.type==="api_call"&&e.type==="api_call"&&t.callId&&e.callId?Vr(t.callId)-Vr(e.callId):0}function Vr(t){let e=t.match(/(\d+)$/);return e?parseInt(e[1],10):0}function ga(t,e){for(let r=0;r<t.length;r++){let n=t[r];if(n.type!=="navigation")continue;let s=new Date(n.timestamp).getTime(),a=null;for(let l=r+1;l<t.length;l++)if(t[l]._testTitle===n._testTitle){a=new Date(t[l].timestamp).getTime();break}if(a!==null)n.durationOnUrl=Math.max(0,a-s);else {let l=e.find(i=>i.title===n._testTitle);if(l){let i=new Date(l.completedAt).getTime();n.durationOnUrl=Math.max(0,i-s);}}}}function Kr(t){if(t==null)return null;try{return JSON.parse(t)}catch{return t}}function j(t,e){let r=t.length,n=Math.min(Math.ceil(e/100*r)-1,r-1);return t[Math.max(0,n)]}function lt(t){try{let e=new URL(t);return e.origin+e.pathname}catch{return t}}function ha(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 Yr(t,e){let r=0,n=0,s=0,a=0,l=0;for(let b of t)switch(b.status){case "passed":r++;break;case "failed":n++;break;case "flaky":s++;break;case "skipped":a++;break;case "timedout":l++;break}let i=[];for(let b of t)b.apiCalls&&i.push(...b.apiCalls);let d=i.length,o=new Set,c={},p={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},h=[];for(let b of i)o.add(lt(b.url)),c[b.method]=(c[b.method]??0)+1,p[ha(b.responseStatusCode)]+=1,h.push(b.responseTimeMs);let u=null;if(h.length>0){let b=[...h].sort((S,L)=>S-L),T=b.reduce((S,L)=>S+L,0);u={avg:Math.round(T/b.length),min:b[0],max:b[b.length-1],p50:j(b,50),p95:j(b,95),p99:j(b,99)};}let f=0,g=0,m=0;for(let b of t)if(b.apiAssertions)for(let T of b.apiAssertions)f++,T.status==="passed"?g++:m++;let y=0,w=new Set;for(let b of t){y+=b.navigations.length;for(let T of b.navigations)w.add(T.url);}let v=0,k={};function C(b){for(let T of b)v++,k[T.category]=(k[T.category]??0)+1,T.children.length>0&&C(T.children);}for(let b of t)b.actions&&C(b.actions);return {total:t.length,passed:r,failed:n,flaky:s,skipped:a,timedout:l,totalApiCalls:d,uniqueApiUrls:o.size,apiCallsByMethod:c,apiCallsByStatusRange:p,apiResponseTime:u,totalAssertions:f,passedAssertions:g,failedAssertions:m,totalNavigations:y,uniqueNavigationUrls:w.size,totalTimelineSteps:e,totalActionSteps:v,actionStepsByCategory:k}}function ma(t){let e=56-t.length;return e>0?t+" ".repeat(e):t}function I(t){return `\u2502 ${ma(t)} \u2502
2612
- `}function dt(t,e,r,n){if(n||t.total===0)return;let s=`\u250C${"\u2500".repeat(58)}\u2510
4261
+ `);return}finally{n&&clearTimeout(n);}}function rs(t,e){let r=[];for(let n of t){let s=So(n),a=Ro(n),o=Ao(n,s,a,e),i=_o(n,s,a);if(o.length===0&&i.length===0){r.push({type:"navigation",url:"about:blank",timestamp:n.startedAt,durationOnUrl:n.duration,navigationType:"dummy",domContentLoadedAt:null,networkIdleAt:null,networkStats:null,specFile:n.specFile,test:s,tests:[a],_testTitle:n.title});continue}r.push(...o,...i);}return r.sort(Io),Mo(r,t),r.map((n,s)=>n.type==="navigation"?{...{index:s,type:"navigation",url:n.url,timestamp:n.timestamp,durationOnUrl:n.durationOnUrl??0,navigationType:n.navigationType,domContentLoadedAt:n.domContentLoadedAt??null,networkIdleAt:n.networkIdleAt??null,networkStats:n.networkStats??null,specFile:n.specFile,test:n.test},tests:n.tests}:{...{index:s,type:"api_call",callId:n.callId,method:n.method,url:n.url,timestamp:n.timestamp,responseTime:n.responseTime??null,request:n.request,response:n.response??null,...n.error?{error:n.error}:{},assertions:n.assertions??[],specFile:n.specFile,test:n.test},tests:n.tests})}function So(t){return {title:t.title,fullTitle:t.titlePath,status:t.status,duration:t.duration,retries:t.retryCount,retry:t.retry,tags:t.tags,failure:t.failure}}function Ro(t){return {title:t.title,status:t.status,duration:t.duration,startedAt:t.startedAt,completedAt:t.completedAt,retryCount:t.retryCount,tags:t.tags,failure:t.failure,testId:t.testId,filePath:t.filePath,suiteName:t.suiteName,testType:t.testType,isFlaky:t.isFlaky,retryStatus:t.retryStatus,expectedStatus:t.expectedStatus,actualStatus:t.actualStatus,artifacts:t.artifacts,networkRequests:t.networkRequests,apiCalls:t.apiCalls,apiAssertions:t.apiAssertions,actions:t.actions,consoleLogs:t.consoleLogs}}function Ao(t,e,r,n){let s=[];for(let a of t.navigations)n.navigationTypes!==null&&!n.navigationTypes.includes(a.navigationType)||s.push({type:"navigation",url:a.url,timestamp:a.timestamp,durationOnUrl:0,navigationType:a.navigationType,domContentLoadedAt:a.domContentLoadedAt??null,networkIdleAt:a.networkIdleAt??null,networkStats:a.networkStats??null,specFile:t.specFile,test:e,tests:[r],_testTitle:t.title});return s}function _o(t,e,r){if(!t.apiCalls||t.apiCalls.length===0)return [];let n=t.apiAssertions??[];return t.apiCalls.map(s=>{let a=n.filter(i=>i.callId===s.id).map(i=>({type:i.type,expected:i.expected,actual:i.actual,status:i.status,location:i.location,...i.expression!==void 0?{expression:i.expression}:{}})),o=null;return s.responseStatusCode!==null&&s.responseStatusText!==null&&(o={statusCode:s.responseStatusCode,statusText:s.responseStatusText,headers:s.responseHeaders,body:ts(s.responseBody)}),{type:"api_call",callId:s.id,method:s.method,url:s.url,timestamp:s.timestamp,responseTime:s.error?null:s.responseTimeMs,request:{headers:s.requestHeaders,body:ts(s.requestBody)},response:o,...s.error?{error:s.error}:{},assertions:a,specFile:t.specFile,test:e,tests:[r],_testTitle:t.title}})}function Io(t,e){let r=new Date(t.timestamp).getTime(),n=new Date(e.timestamp).getTime();if(r!==n)return r-n;let s={navigation:0,api_call:1},a=s[t.type],o=s[e.type];return a!==o?a-o:t.type==="api_call"&&e.type==="api_call"&&t.callId&&e.callId?es(t.callId)-es(e.callId):0}function es(t){let e=t.match(/(\d+)$/);return e?parseInt(e[1],10):0}function Mo(t,e){for(let r=0;r<t.length;r++){let n=t[r];if(n.type!=="navigation")continue;let s=new Date(n.timestamp).getTime(),a=null;for(let o=r+1;o<t.length;o++)if(t[o]._testTitle===n._testTitle){a=new Date(t[o].timestamp).getTime();break}if(a!==null)n.durationOnUrl=Math.max(0,a-s);else {let o=e.find(i=>i.title===n._testTitle);if(o){let i=new Date(o.completedAt).getTime();n.durationOnUrl=Math.max(0,i-s);}}}}function ts(t){if(t==null)return null;try{return JSON.parse(t)}catch{return t}}function ee(t,e){let r=t.length,n=Math.min(Math.ceil(e/100*r)-1,r-1);return t[Math.max(0,n)]}function jt(t){try{let e=new URL(t);return e.origin+e.pathname}catch{return t}}function Eo(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 ns(t,e){let r=0,n=0,s=0,a=0,o=0;for(let x of t)switch(x.status){case "passed":r++;break;case "failed":n++;break;case "flaky":s++;break;case "skipped":a++;break;case "timedout":o++;break}let i=[];for(let x of t)x.apiCalls&&i.push(...x.apiCalls);let d=i.length,l=new Set,c={},u={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},f=[];for(let x of i)l.add(jt(x.url)),c[x.method]=(c[x.method]??0)+1,u[Eo(x.responseStatusCode)]+=1,f.push(x.responseTimeMs);let p=null;if(f.length>0){let x=[...f].sort((_,L)=>_-L),C=x.reduce((_,L)=>_+L,0);p={avg:Math.round(C/x.length),min:x[0],max:x[x.length-1],p50:ee(x,50),p95:ee(x,95),p99:ee(x,99)};}let g=0,m=0,h=0;for(let x of t)if(x.apiAssertions)for(let C of x.apiAssertions)g++,C.status==="passed"?m++:h++;let v=0,y=new Set;for(let x of t){v+=x.navigations.length;for(let C of x.navigations)y.add(C.url);}let b=0,k={};function A(x){for(let C of x)b++,k[C.category]=(k[C.category]??0)+1,C.children.length>0&&A(C.children);}for(let x of t)x.actions&&A(x.actions);let E=0,T=0,w=0,S=0;for(let x of t)if(x.visual)for(let C of x.visual)E++,C.status==="failed"?T++:(C.status==="new"||C.status==="updated")&&w++,S+=C.domChangeCount??0;return {total:t.length,passed:r,failed:n,flaky:s,skipped:a,timedout:o,totalApiCalls:d,uniqueApiUrls:l.size,apiCallsByMethod:c,apiCallsByStatusRange:u,apiResponseTime:p,totalAssertions:g,passedAssertions:m,failedAssertions:h,totalNavigations:v,uniqueNavigationUrls:y.size,totalTimelineSteps:e,totalActionSteps:b,actionStepsByCategory:k,visualChecks:E,visualFailures:T,visualNew:w,visualDomChanges:S}}function Po(t){let e=56-t.length;return e>0?t+" ".repeat(e):t}function F(t){return `\u2502 ${Po(t)} \u2502
4262
+ `}function Vt(t,e,r,n){if(n||t.total===0)return;let s=`\u250C${"\u2500".repeat(58)}\u2510
2613
4263
  `,a=`\u2514${"\u2500".repeat(58)}\u2518
2614
- `,l=I(""),i=s;i+=I("TestRelic AI - Playwright Test Report"),i+=l;let d=[];if(t.passed>0&&d.push(`${t.passed} \u2713`),t.failed>0&&d.push(`${t.failed} \u2717`),t.flaky>0&&d.push(`${t.flaky} \u26A0`),t.skipped>0&&d.push(`${t.skipped} skipped`),t.timedout>0&&d.push(`${t.timedout} timedout`),i+=I(`Tests: ${t.total} total (${d.join(" ")})`),t.totalNavigations>0&&(i+=I(`Navigations: ${t.totalNavigations} visits across ${t.uniqueNavigationUrls} unique URLs`)),t.totalApiCalls>0){i+=I(`API Calls: ${t.totalApiCalls} calls across ${t.uniqueApiUrls} unique endpoints`);let o=Object.entries(t.apiCallsByMethod).filter(([,p])=>p>0).map(([p,h])=>`${p}: ${h}`);o.length>0&&(i+=I(` ${o.join(" ")}`));let c=Object.entries(t.apiCallsByStatusRange).filter(([,p])=>p>0).map(([p,h])=>`${p}: ${h}`);c.length>0&&(i+=I(` ${c.join(" ")}`)),t.apiResponseTime&&(i+=I(` Avg response: ${t.apiResponseTime.avg}ms P95: ${t.apiResponseTime.p95}ms`));}if(t.totalAssertions>0&&(i+=I(`Assertions: ${t.totalAssertions} total (${t.passedAssertions} \u2713 ${t.failedAssertions} \u2717)`)),t.totalActionSteps>0){let o=[],c=t.actionStepsByCategory;c.ui_action&&o.push(`${c.ui_action} UI`),c.assertion&&o.push(`${c.assertion} assertions`),c.custom_step&&o.push(`${c.custom_step} custom`);let p=o.length>0?` (${o.join(" ")})`:"";i+=I(`Actions: ${t.totalActionSteps} steps${p}`);}i+=I(`Report: ${r}`),i+=I(`Data: ${e}`),i+=a,i+=`For more information visit us at https://docs.testrelic.ai
2615
- `,process.stderr.write(i);}var ya=new Set(["pw:api","expect","test.step"]);function va(t){return t==="expect"?"assertion":t==="test.step"?"custom_step":"ui_action"}function ct(t,e,r){if(ya.has(t.category)){let n=[];for(let a of t.steps)ct(a,e,n);let s=t.startTime.getTime()-e.getTime();r.push({title:t.title,category:va(t.category),timestamp:t.startTime.toISOString(),duration:t.duration,videoOffset:s>=0?s/1e3:null,status:t.error?"failed":"passed",error:t.error?.message??null,children:n});}else for(let n of t.steps)ct(n,e,r);}function Qr(t,e){let r=[];for(let n of t)ct(n,e,r);return r}var xa=new Set(["localhost","127.0.0.1","0.0.0.0"]);function Xr(t){let e=new URL(t);if(e.protocol!=="https:"&&!(e.protocol==="http:"&&xa.has(e.hostname)))throw createError(ErrorCode.CLOUD_CONFIG_INVALID,`HTTPS is required for cloud communication. Got: ${e.protocol}//${e.hostname}`)}var ka=3e3;async function ut(t){let e=new AbortController,r=setTimeout(()=>e.abort(),ka);try{return (await fetch(`${t}/health`,{method:"GET",signal:e.signal})).ok}catch{return false}finally{clearTimeout(r);}}async function en(t){return new Promise(e=>setTimeout(e,t))}async function Zr(t){try{let r=(await t.json()).error;if(r&&typeof r.message=="string"){let n=r.details;if(Array.isArray(n)&&n.length>0){let s=n.map(a=>`${a.field}: ${a.message}`).join(", ");return `${r.message} (${s})`}return r.message}}catch{}return `HTTP ${t.status}`}async function pt(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),r);try{let a=await fetch(`${t}/sdk/auth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:e}),signal:n.signal});if(a.ok){let i=await a.json();return {accessToken:i.accessToken,refreshToken:i.refreshToken,expiresIn:i.expiresIn,orgId:i.orgId,orgName:i.orgName,userId:i.userId,userName:i.userName}}if(a.status===429){let i=a.headers.get("Retry-After"),d=i?parseInt(i,10)*1e3:5e3;if(!isNaN(d)&&d>0){await en(d);let o=await fetch(`${t}/sdk/auth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:e}),signal:n.signal});if(o.ok){let c=await o.json();return {accessToken:c.accessToken,refreshToken:c.refreshToken,expiresIn:c.expiresIn,orgId:c.orgId,orgName:c.orgName,userId:c.userId,userName:c.userName}}}return {code:"rate_limited",message:"Rate limited during token exchange.",statusCode:429}}return a.status===400?{code:"validation_error",message:await Zr(a),statusCode:400}:a.status===401?{code:"invalid_key",message:"API key is invalid or has been revoked.",statusCode:401}:a.status===403?{code:"expired_key",message:"API key has expired.",statusCode:403}:{code:"server_error",message:await Zr(a),statusCode:a.status}}catch(a){return a instanceof DOMException&&a.name==="AbortError"?{code:"timeout",message:"Token exchange timed out.",statusCode:null}:{code:"network_error",message:"Failed to reach cloud for token exchange.",statusCode:null}}finally{clearTimeout(s);}}function ft(t){return "code"in t&&typeof t.code=="string"&&["invalid_key","expired_key","validation_error","rate_limited","server_error","network_error","timeout"].includes(t.code)}async function tn(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),r);try{let a=await fetch(`${t}/sdk/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e}),signal:n.signal});if(a.status===429){let i=a.headers.get("Retry-After"),d=i?parseInt(i,10)*1e3:5e3;!isNaN(d)&&d>0&&(await en(d),a=await fetch(`${t}/sdk/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e}),signal:n.signal}));}if(!a.ok)return null;let l=await a.json();return {accessToken:l.accessToken,refreshToken:l.refreshToken,expiresIn:l.expiresIn}}catch{return null}finally{clearTimeout(s);}}async function rn(t,e,r,n,s,a){let l=new AbortController,i=setTimeout(()=>l.abort(),s);try{let d={gitId:r,displayName:n};a&&(d.branch=a);let o=await fetch(`${t}/repos/resolve`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(d),signal:l.signal});if(!o.ok)return null;let c=await o.json();return {repoId:c.repoId,displayName:c.displayName}}catch{return null}finally{clearTimeout(i);}}var Aa=5e3;function B(t,e){try{return execSync(t,{cwd:e,timeout:Aa,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function nn(t){let e=B("git rev-parse --abbrev-ref HEAD",t),r=B("git rev-parse --short HEAD",t),n=B("git log -1 --pretty=%s",t),s=B("git log -1 --format=%an",t)??B("git config user.name",t),a=_a(t),l=a?le(a):null;return {branch:e,commitSha:r,commitMessage:n,commitAuthor:s,remoteUrl:l}}function _a(t){let e=B("git remote get-url origin",t);if(e)return e;let r=B("git remote",t);if(!r)return null;let n=r.split(`
2616
- `)[0]?.trim();return n?B(`git remote get-url ${n}`,t):null}function le(t){let e=t.trim();return e=e.replace(/^[a-z+]+:\/\//,""),e=e.replace(/^[^@]+@/,""),e=e.replace(/:(?!\d)/,"/"),e=e.replace(/\.git$/,""),e=e.replace(/\/+$/,""),e=e.replace(/^[^@/]+@/,""),e.toLowerCase()}function sn(t){let e=t.split("/").filter(Boolean);return e[e.length-1]??t}function gt(t){try{let e=readFileSync(join(t,"package.json"),"utf-8"),r=JSON.parse(e);return typeof r.name=="string"&&r.name.length>0?r.name:null}catch{return null}}function de(t){let e=gt(t);return e||`local/${basename(t)}`}var Ia=new Set(["smoke","regression","nightly","ci"]);function we(){let t=process.env.TESTRELIC_RUN_TYPE?.trim().toLowerCase();if(t&&Ia.has(t))return t}async function O(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),Math.max(1,r));try{return await fetch(t,{...e,signal:n.signal})}finally{clearTimeout(s);}}function an(t,e){let r=e+Math.ceil(Math.max(0,t)/1e3);return Math.min(6e5,Math.max(3e4,r))}var xe=class{constructor(e=12e4,r=Date.now()){this.deadline=r+Math.max(0,e);}remaining(e=Date.now()){return Math.max(0,this.deadline-e)}expired(e=Date.now()){return this.remaining(e)===0}slice(e,r=Date.now()){return Math.min(e,this.remaining(r))}};var Ea=1048576,ke=[1e3,3e3,9e3],Te=3,ln=3e4,Pa=90*1048576,Na=new Set(["e2e","mobile","unit"]);function mt(t){if(t&&t.length>0&&t.every(s=>s.testType==="api")){let s=new Map;for(let i of t){let d=i.apiProtocol??"rest";s.set(d,(s.get(d)??0)+1);}let a="rest",l=0;for(let[i,d]of s)d>l&&(l=d,a=i);return {testType:"api",apiProtocol:a,metadata:{testType:"api",primaryProtocol:a}}}let e=new Map;for(let s of t??[])Na.has(s.testType)&&e.set(s.testType,(e.get(s.testType)??0)+1);let r=null,n=0;for(let[s,a]of e)a>n&&(n=a,r=s);return {testType:r??"e2e"}}function yt(t,e,r,n,s,a){let l=a?.cloudPlatform?a.cloudPlatform:n?.provider&&n.provider!=="unknown"?n.provider:"local",i=we(),d=mt(s),o=d.testType==="api"?d:null,c=o&&s?s.map(h=>h.apiProtocol?h:{...h,apiProtocol:o.apiProtocol}):s;return {runId:t.testRunId,repoGitId:e,startedAt:t.startedAt,summary:t.summary,timeline:t.timeline,environment:l,...i?{runType:i}:{},...o?{testType:o.testType,apiProtocol:o.apiProtocol,metadata:o.metadata}:{testType:d.testType},...c&&c.length>0?{tests:c}:{},...r?.branch?{branch:r.branch}:{},...r?.commitSha?{commit:r.commitSha}:{},...r?.commitMessage?{commitMessage:r.commitMessage}:{},...r?.commitAuthor?{commitAuthor:r.commitAuthor}:{},...t.completedAt?{finishedAt:t.completedAt}:{},...t.totalDuration?{duration:t.totalDuration}:{},...n?.provider?{ciProvider:n.provider}:{},...n?.runUrl?{ciRunUrl:n.runUrl}:{},...a?.cloudPlatform?{cloudPlatform:a.cloudPlatform}:{},...a?.cloudBuildId?{cloudBuildId:a.cloudBuildId}:{},...a?.cloudSessionId?{cloudSessionId:a.cloudSessionId}:{}}}function ht(t){return typeof t=="string"?Buffer.byteLength(t,"utf-8"):0}function vt(t){return Array.isArray(t)?t.map(e=>{let r=e.statusCode??e.responseStatusCode??null;return {...e.id!=null?{id:e.id}:{},...e.protocol!=null?{protocol:e.protocol}:{},method:e.method??null,url:e.url??null,statusCode:r,statusText:e.statusText??e.responseStatusText??null,ok:typeof r=="number"?r<400:e.ok??null,durationMs:e.durationMs??e.responseTimeMs??null,bytesIn:typeof e.bytesIn=="number"?e.bytesIn:ht(e.responseBody),bytesOut:typeof e.bytesOut=="number"?e.bytesOut:ht(e.requestBody),requestHeaders:e.requestHeaders??null,responseHeaders:e.responseHeaders??null,requestBody:e.requestBody??null,responseBody:e.responseBody??null,requestCookies:e.requestCookies??null,responseCookies:e.responseCookies??null}}):[]}function bt(t){return Array.isArray(t)?t.map(e=>{let r=e.responseStatusCode??e.statusCode??0,n=e.timestamp??null;return {method:e.method??"GET",url:e.url??"",status:r,statusCode:r,type:"xhr",size:ht(e.responseBody),duration:e.responseTimeMs??e.durationMs??0,responseTimeMs:e.responseTimeMs??e.durationMs??0,requestHeaders:e.requestHeaders??null,responseHeaders:e.responseHeaders??null,requestBody:e.requestBody??null,responseBody:e.responseBody??null,timestamp:n,startedAt:n}}):[]}function wt(t){if(!Array.isArray(t))return [];let e=r=>{if(r==null)return String(r);if(typeof r=="string")return r;try{return JSON.stringify(r)}catch{return String(r)}};return t.map(r=>{let n=r.status==="failed"?"failed":"passed",s=typeof r.expression=="string"&&r.expression.trim()?r.expression.trim():null,a=r.kind??r.type??"assertion",l=n==="failed"?`expected ${e(r.expected)}, received ${e(r.actual)}`:r.expected!==void 0?`expected ${e(r.expected)}`:null;return {...r.callId!=null?{callId:r.callId}:{},kind:a,description:s??r.description??`${a} assertion`,status:n,detail:l}})}async function Ce(t){return new Promise(e=>setTimeout(e,t))}function Fa(t){let e={};for(let[r,n]of Object.entries(t))n!=null&&(e[r]=n);return e}async function dn(t,e,r,n=ln,s){for(let a=0;a<Te;a++){if(s?.expired())return null;let l=s?s.slice(n):n;if(l<=0)return null;try{let i=await O(t,e,l);if(i.ok)return i;if(i.status===401&&r&&a===0){let d=await r();if(d){let o={...e,headers:{...e.headers,Authorization:`Bearer ${d}`}},c=await O(t,o,l);if(c.ok)return c}return null}if(i.status===429&&a<Te-1){let d=i.headers.get("Retry-After"),o=d?parseInt(d,10)*1e3:ke[a];!isNaN(o)&&o>0?await Ce(o):await Ce(ke[a]);continue}if(i.status>=500&&a<Te-1){await Ce(ke[a]);continue}return i}catch{if(a<Te-1){await Ce(ke[a]);continue}return null}}return null}var Da=promisify(gzip);function on(t,e){if(!Array.isArray(t.tests))return t;let r=t.tests.map(n=>{let{consoleLogs:s,networkRequests:a,apiCalls:l,...i}=n;return e?i:{...i,...l!==void 0?{apiCalls:l}:{}}});return {...t,tests:r}}var Ba=["consoleLogs","networkRequests","apiCalls","actions","navigations","protocolAssertions"];function Oa(t){if(!Array.isArray(t.tests))return t;let e=new Set(Ba),r=t.tests.map(n=>{let s={};for(let a in n)if(!e.has(a))if(a==="failure"&&n[a]&&typeof n[a]=="object"){let l=n[a];s.failure={message:l.message??null,line:l.line??null};}else s[a]=n[a];return s});return {...t,tests:r}}function qa(t){let e=new Set(["tests","timeline","unifiedTimeline","navigations"]),r={};for(let n in t)e.has(n)||(r[n]=t[n]);return r.tests=[],r}async function xt(t,e=Pa){let r={"Content-Type":"application/json"},n=JSON.stringify(t),s=t,a=Array.isArray(s?.tests)?s.tests.length:0,l=i=>Buffer.byteLength(i,"utf-8")>e;if(a>0&&l(n)){let i=Math.max(1,Math.round(e/1048576));process.stderr.write(`[testrelic] WARNING: batch payload exceeds ${i}MB \u2014 dropping consoleLogs/networkRequests from ${a} test(s) to avoid HTTP 413. Console/network detail will be missing in the cloud.
2617
- `),n=JSON.stringify(on(s,false)),l(n)&&(process.stderr.write(`[testrelic] WARNING: batch still exceeds ${i}MB after dropping logs \u2014 also dropping apiCalls from ${a} test(s). API request/response detail will be missing in the cloud.
2618
- `),n=JSON.stringify(on(s,true))),l(n)&&(process.stderr.write(`[testrelic] WARNING: batch still exceeds ${i}MB \u2014 shedding remaining per-test detail (actions/navigations/assertions) from ${a} test(s). Only test results + summary will reach the cloud.
2619
- `),n=JSON.stringify(Oa(s))),l(n)&&(process.stderr.write(`[testrelic] ERROR: batch still exceeds ${i}MB after shedding all per-test detail \u2014 uploading run summary only (${a} per-test result(s) omitted) to avoid losing the run to HTTP 413.
2620
- `),n=JSON.stringify(qa(s)));}return Buffer.byteLength(n,"utf-8")>Ea?(r["Content-Encoding"]="gzip",{body:await Da(Buffer.from(n,"utf-8")),headers:r}):{body:n,headers:r}}async function cn(t,e,r,n,s,a){let l=`${t}/runs`,{body:i,headers:d}=await xt(r);d.Authorization=`Bearer ${e}`;let o=await dn(l,{method:"POST",headers:d,body:i},n,s,a);return o?.ok?{success:true,statusCode:o.status,error:null}:o?.status===409?{success:true,statusCode:409,error:null}:{success:false,reason:o?`Upload failed with status ${o.status}`:"Upload failed after retries",statusCode:o?.status??null,payload:r,targetEndpoint:l,method:"POST"}}async function un(t,e,r,n){let s=`${t}/runs/init`;try{let a=await O(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(Fa(r))},n??ln);return a.ok?{runId:(await a.json()).runId}:null}catch{return null}}function pn(t,e,r,n){let s=`${t}/runs/${r}/tests`;fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(n)}).catch(()=>{});}async function fn(t,e,r,n){let s=`${t}/runs/${r}/finalize`;return (await dn(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(n)}))?.ok??false}function kt(t,e,r,n){let s=`${t}/runs/${r}/finalize`,a=new Date,l={finishedAt:a.toISOString(),duration:a.getTime()-new Date(n).getTime(),summary:{}};fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(l)}).catch(()=>{});}var ja="1.0.0",gn=10;function hn(t){return t instanceof Error?t.message:String(t)}function Wa(t){return t>=400&&t<500&&t!==408&&t!==429}function ce(t,e,r,n,s,a,l,i){try{mkdirSync(t,{recursive:!0});let d=Date.now(),o=`${d}-${e}-${r}.json`,c=join(t,o),p={version:ja,queuedAt:new Date(d).toISOString(),reason:n,retryCount:0,targetEndpoint:s,method:a,payload:l,headers:i},h=c+".tmp";writeFileSync(h,JSON.stringify(p,null,2),"utf-8"),renameSync(h,c);}catch(d){d.code==="ENOSPC"?process.stderr.write(`\u26A0 TestRelic: Disk full \u2014 unable to queue upload data.
4264
+ `,o=F(""),i=s;i+=F("TestRelic AI - Playwright Test Report"),i+=o;let d=[];if(t.passed>0&&d.push(`${t.passed} \u2713`),t.failed>0&&d.push(`${t.failed} \u2717`),t.flaky>0&&d.push(`${t.flaky} \u26A0`),t.skipped>0&&d.push(`${t.skipped} skipped`),t.timedout>0&&d.push(`${t.timedout} timedout`),i+=F(`Tests: ${t.total} total (${d.join(" ")})`),t.totalNavigations>0&&(i+=F(`Navigations: ${t.totalNavigations} visits across ${t.uniqueNavigationUrls} unique URLs`)),t.totalApiCalls>0){i+=F(`API Calls: ${t.totalApiCalls} calls across ${t.uniqueApiUrls} unique endpoints`);let l=Object.entries(t.apiCallsByMethod).filter(([,u])=>u>0).map(([u,f])=>`${u}: ${f}`);l.length>0&&(i+=F(` ${l.join(" ")}`));let c=Object.entries(t.apiCallsByStatusRange).filter(([,u])=>u>0).map(([u,f])=>`${u}: ${f}`);c.length>0&&(i+=F(` ${c.join(" ")}`)),t.apiResponseTime&&(i+=F(` Avg response: ${t.apiResponseTime.avg}ms P95: ${t.apiResponseTime.p95}ms`));}if(t.totalAssertions>0&&(i+=F(`Assertions: ${t.totalAssertions} total (${t.passedAssertions} \u2713 ${t.failedAssertions} \u2717)`)),t.totalActionSteps>0){let l=[],c=t.actionStepsByCategory;c.ui_action&&l.push(`${c.ui_action} UI`),c.assertion&&l.push(`${c.assertion} assertions`),c.custom_step&&l.push(`${c.custom_step} custom`);let u=l.length>0?` (${l.join(" ")})`:"";i+=F(`Actions: ${t.totalActionSteps} steps${u}`);}i+=F(`Report: ${r}`),i+=F(`Data: ${e}`),i+=a,i+=`For more information visit us at https://docs.testrelic.ai
4265
+ `,process.stderr.write(i);}var Lo=new Set(["pw:api","expect","test.step"]);function No(t){return t==="expect"?"assertion":t==="test.step"?"custom_step":"ui_action"}function $t(t,e,r){if(Lo.has(t.category)){let n=[];for(let a of t.steps)$t(a,e,n);let s=t.startTime.getTime()-e.getTime();r.push({title:t.title,category:No(t.category),timestamp:t.startTime.toISOString(),duration:t.duration,videoOffset:s>=0?s/1e3:null,status:t.error?"failed":"passed",error:Un(t.error?.message),children:n});}else for(let n of t.steps)$t(n,e,r);}function ss(t,e){let r=[];for(let n of t)$t(n,e,r);return r}var Bo=new Set(["localhost","127.0.0.1","0.0.0.0"]);function is(t){let e=new URL(t);if(e.protocol!=="https:"&&!(e.protocol==="http:"&&Bo.has(e.hostname)))throw createError(ErrorCode.CLOUD_CONFIG_INVALID,`HTTPS is required for cloud communication. Got: ${e.protocol}//${e.hostname}`)}var Oo=3e3;async function Wt(t){let e=new AbortController,r=setTimeout(()=>e.abort(),Oo);try{return (await fetch(`${t}/health`,{method:"GET",signal:e.signal})).ok}catch{return false}finally{clearTimeout(r);}}async function os(t){return new Promise(e=>setTimeout(e,t))}async function as(t){try{let r=(await t.json()).error;if(r&&typeof r.message=="string"){let n=r.details;if(Array.isArray(n)&&n.length>0){let s=n.map(a=>`${a.field}: ${a.message}`).join(", ");return `${r.message} (${s})`}return r.message}}catch{}return `HTTP ${t.status}`}async function Gt(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),r);try{let a=await fetch(`${t}/sdk/auth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:e}),signal:n.signal});if(a.ok){let i=await a.json();return {accessToken:i.accessToken,refreshToken:i.refreshToken,expiresIn:i.expiresIn,orgId:i.orgId,orgName:i.orgName,userId:i.userId,userName:i.userName}}if(a.status===429){let i=a.headers.get("Retry-After"),d=i?parseInt(i,10)*1e3:5e3;if(!isNaN(d)&&d>0){await os(d);let l=await fetch(`${t}/sdk/auth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({apiKey:e}),signal:n.signal});if(l.ok){let c=await l.json();return {accessToken:c.accessToken,refreshToken:c.refreshToken,expiresIn:c.expiresIn,orgId:c.orgId,orgName:c.orgName,userId:c.userId,userName:c.userName}}}return {code:"rate_limited",message:"Rate limited during token exchange.",statusCode:429}}return a.status===400?{code:"validation_error",message:await as(a),statusCode:400}:a.status===401?{code:"invalid_key",message:"API key is invalid or has been revoked.",statusCode:401}:a.status===403?{code:"expired_key",message:"API key has expired.",statusCode:403}:{code:"server_error",message:await as(a),statusCode:a.status}}catch(a){return a instanceof DOMException&&a.name==="AbortError"?{code:"timeout",message:"Token exchange timed out.",statusCode:null}:{code:"network_error",message:"Failed to reach cloud for token exchange.",statusCode:null}}finally{clearTimeout(s);}}function Kt(t){return "code"in t&&typeof t.code=="string"&&["invalid_key","expired_key","validation_error","rate_limited","server_error","network_error","timeout"].includes(t.code)}async function ls(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),r);try{let a=await fetch(`${t}/sdk/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e}),signal:n.signal});if(a.status===429){let i=a.headers.get("Retry-After"),d=i?parseInt(i,10)*1e3:5e3;!isNaN(d)&&d>0&&(await os(d),a=await fetch(`${t}/sdk/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e}),signal:n.signal}));}if(!a.ok)return null;let o=await a.json();return {accessToken:o.accessToken,refreshToken:o.refreshToken,expiresIn:o.expiresIn}}catch{return null}finally{clearTimeout(s);}}async function ds(t,e,r,n,s,a){let o=new AbortController,i=setTimeout(()=>o.abort(),s);try{let d={gitId:r,displayName:n};a&&(d.branch=a);let l=await fetch(`${t}/repos/resolve`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(d),signal:o.signal});if(!l.ok)return null;let c=await l.json();return {repoId:c.repoId,displayName:c.displayName}}catch{return null}finally{clearTimeout(i);}}var jo=5e3;function K(t,e){try{return execSync(t,{cwd:e,timeout:jo,encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function cs(t){let e=K("git rev-parse --abbrev-ref HEAD",t),r=K("git rev-parse --short HEAD",t),n=K("git log -1 --pretty=%s",t),s=K("git log -1 --format=%an",t)??K("git config user.name",t),a=Vo(t),o=a?be(a):null;return {branch:e,commitSha:r,commitMessage:n,commitAuthor:s,remoteUrl:o}}function Vo(t){let e=K("git remote get-url origin",t);if(e)return e;let r=K("git remote",t);if(!r)return null;let n=r.split(`
4266
+ `)[0]?.trim();return n?K(`git remote get-url ${n}`,t):null}function be(t){let e=t.trim();return e=e.replace(/^[a-z+]+:\/\//,""),e=e.replace(/^[^@]+@/,""),e=e.replace(/:(?!\d)/,"/"),e=e.replace(/\.git$/,""),e=e.replace(/\/+$/,""),e=e.replace(/^[^@/]+@/,""),e.toLowerCase()}function us(t){let e=t.split("/").filter(Boolean);return e[e.length-1]??t}function Jt(t){try{let e=readFileSync(join(t,"package.json"),"utf-8"),r=JSON.parse(e);return typeof r.name=="string"&&r.name.length>0?r.name:null}catch{return null}}function xe(t){let e=Jt(t);return e||`local/${basename(t)}`}var $o=new Set(["smoke","regression","nightly","ci"]);function Fe(){let t=process.env.TESTRELIC_RUN_TYPE?.trim().toLowerCase();if(t&&$o.has(t))return t}async function J(t,e,r){let n=new AbortController,s=setTimeout(()=>n.abort(),Math.max(1,r));try{return await fetch(t,{...e,signal:n.signal})}finally{clearTimeout(s);}}function ps(t,e){let r=e+Math.ceil(Math.max(0,t)/1e3);return Math.min(6e5,Math.max(3e4,r))}var Be=class{constructor(e=12e4,r=Date.now()){this.deadline=r+Math.max(0,e);}remaining(e=Date.now()){return Math.max(0,this.deadline-e)}expired(e=Date.now()){return this.remaining(e)===0}slice(e,r=Date.now()){return Math.min(e,this.remaining(r))}};var Ko=1048576,Oe=[1e3,3e3,9e3],qe=3,gs=3e4,Jo=90*1048576,Yo=new Set(["e2e","mobile","unit"]);function Xt(t){if(t&&t.length>0&&t.every(s=>s.testType==="api")){let s=new Map;for(let i of t){let d=i.apiProtocol??"rest";s.set(d,(s.get(d)??0)+1);}let a="rest",o=0;for(let[i,d]of s)d>o&&(o=d,a=i);return {testType:"api",apiProtocol:a,metadata:{testType:"api",primaryProtocol:a}}}let e=new Map;for(let s of t??[])Yo.has(s.testType)&&e.set(s.testType,(e.get(s.testType)??0)+1);let r=null,n=0;for(let[s,a]of e)a>n&&(n=a,r=s);return {testType:r??"e2e"}}function Zt(t,e,r,n,s,a){let o=a?.cloudPlatform?a.cloudPlatform:n?.provider&&n.provider!=="unknown"?n.provider:"local",i=Fe(),d=Xt(s),l=d.testType==="api"?d:null,c=l&&s?s.map(f=>f.apiProtocol?f:{...f,apiProtocol:l.apiProtocol}):s;return {runId:t.testRunId,repoGitId:e,startedAt:t.startedAt,summary:t.summary,timeline:t.timeline,environment:o,...i?{runType:i}:{},...l?{testType:l.testType,apiProtocol:l.apiProtocol,metadata:l.metadata}:{testType:d.testType},...c&&c.length>0?{tests:c}:{},...r?.branch?{branch:r.branch}:{},...r?.commitSha?{commit:r.commitSha}:{},...r?.commitMessage?{commitMessage:r.commitMessage}:{},...r?.commitAuthor?{commitAuthor:r.commitAuthor}:{},...t.completedAt?{finishedAt:t.completedAt}:{},...t.totalDuration?{duration:t.totalDuration}:{},...n?.provider?{ciProvider:n.provider}:{},...n?.runUrl?{ciRunUrl:n.runUrl}:{},...a?.cloudPlatform?{cloudPlatform:a.cloudPlatform}:{},...a?.cloudBuildId?{cloudBuildId:a.cloudBuildId}:{},...a?.cloudSessionId?{cloudSessionId:a.cloudSessionId}:{}}}function Yt(t){return typeof t=="string"?Buffer.byteLength(t,"utf-8"):0}function Qt(t){return Array.isArray(t)?t.map(e=>{let r=e.statusCode??e.responseStatusCode??null;return {...e.id!=null?{id:e.id}:{},...e.protocol!=null?{protocol:e.protocol}:{},method:e.method??null,url:e.url??null,statusCode:r,statusText:e.statusText??e.responseStatusText??null,ok:typeof r=="number"?r<400:e.ok??null,durationMs:e.durationMs??e.responseTimeMs??null,bytesIn:typeof e.bytesIn=="number"?e.bytesIn:Yt(e.responseBody),bytesOut:typeof e.bytesOut=="number"?e.bytesOut:Yt(e.requestBody),requestHeaders:e.requestHeaders??null,responseHeaders:e.responseHeaders??null,requestBody:e.requestBody??null,responseBody:e.responseBody??null,requestCookies:e.requestCookies??null,responseCookies:e.responseCookies??null}}):[]}function er(t){return Array.isArray(t)?t.map(e=>{let r=e.responseStatusCode??e.statusCode??0,n=e.timestamp??null;return {method:e.method??"GET",url:e.url??"",status:r,statusCode:r,type:"xhr",size:Yt(e.responseBody),duration:e.responseTimeMs??e.durationMs??0,responseTimeMs:e.responseTimeMs??e.durationMs??0,requestHeaders:e.requestHeaders??null,responseHeaders:e.responseHeaders??null,requestBody:e.requestBody??null,responseBody:e.responseBody??null,timestamp:n,startedAt:n}}):[]}function tr(t){if(!Array.isArray(t))return [];let e=r=>{if(r==null)return String(r);if(typeof r=="string")return r;try{return JSON.stringify(r)}catch{return String(r)}};return t.map(r=>{let n=r.status==="failed"?"failed":"passed",s=typeof r.expression=="string"&&r.expression.trim()?r.expression.trim():null,a=r.kind??r.type??"assertion",o=n==="failed"?`expected ${e(r.expected)}, received ${e(r.actual)}`:r.expected!==void 0?`expected ${e(r.expected)}`:null;return {...r.callId!=null?{callId:r.callId}:{},kind:a,description:s??r.description??`${a} assertion`,status:n,detail:o}})}async function He(t){return new Promise(e=>setTimeout(e,t))}function Xo(t){let e={};for(let[r,n]of Object.entries(t))n!=null&&(e[r]=n);return e}async function hs(t,e,r,n=gs,s){for(let a=0;a<qe;a++){if(s?.expired())return null;let o=s?s.slice(n):n;if(o<=0)return null;try{let i=await J(t,e,o);if(i.ok)return i;if(i.status===401&&r&&a===0){let d=await r();if(d){let l={...e,headers:{...e.headers,Authorization:`Bearer ${d}`}},c=await J(t,l,o);if(c.ok)return c}return null}if(i.status===429&&a<qe-1){let d=i.headers.get("Retry-After"),l=d?parseInt(d,10)*1e3:Oe[a];!isNaN(l)&&l>0?await He(l):await He(Oe[a]);continue}if(i.status>=500&&a<qe-1){await He(Oe[a]);continue}return i}catch{if(a<qe-1){await He(Oe[a]);continue}return null}}return null}var Zo=promisify(gzip);function fs(t,e){if(!Array.isArray(t.tests))return t;let r=t.tests.map(n=>{let{consoleLogs:s,networkRequests:a,apiCalls:o,...i}=n;return e?i:{...i,...o!==void 0?{apiCalls:o}:{}}});return {...t,tests:r}}var Qo=["consoleLogs","networkRequests","apiCalls","actions","navigations","protocolAssertions"];function el(t){if(!Array.isArray(t.tests))return t;let e=new Set(Qo),r=t.tests.map(n=>{let s={};for(let a in n)if(!e.has(a))if(a==="failure"&&n[a]&&typeof n[a]=="object"){let o=n[a];s.failure={message:o.message??null,line:o.line??null};}else s[a]=n[a];return s});return {...t,tests:r}}function tl(t){let e=new Set(["tests","timeline","unifiedTimeline","navigations"]),r={};for(let n in t)e.has(n)||(r[n]=t[n]);return r.tests=[],r}async function rr(t,e=Jo){let r={"Content-Type":"application/json"},n=JSON.stringify(t),s=t,a=Array.isArray(s?.tests)?s.tests.length:0,o=i=>Buffer.byteLength(i,"utf-8")>e;if(a>0&&o(n)){let i=Math.max(1,Math.round(e/1048576));process.stderr.write(`[testrelic] WARNING: batch payload exceeds ${i}MB \u2014 dropping consoleLogs/networkRequests from ${a} test(s) to avoid HTTP 413. Console/network detail will be missing in the cloud.
4267
+ `),n=JSON.stringify(fs(s,false)),o(n)&&(process.stderr.write(`[testrelic] WARNING: batch still exceeds ${i}MB after dropping logs \u2014 also dropping apiCalls from ${a} test(s). API request/response detail will be missing in the cloud.
4268
+ `),n=JSON.stringify(fs(s,true))),o(n)&&(process.stderr.write(`[testrelic] WARNING: batch still exceeds ${i}MB \u2014 shedding remaining per-test detail (actions/navigations/assertions) from ${a} test(s). Only test results + summary will reach the cloud.
4269
+ `),n=JSON.stringify(el(s))),o(n)&&(process.stderr.write(`[testrelic] ERROR: batch still exceeds ${i}MB after shedding all per-test detail \u2014 uploading run summary only (${a} per-test result(s) omitted) to avoid losing the run to HTTP 413.
4270
+ `),n=JSON.stringify(tl(s)));}if(Buffer.byteLength(n,"utf-8")>Ko){r["Content-Encoding"]="gzip";let i=await Zo(Buffer.from(n,"utf-8")),d=i.buffer;return {body:new Uint8Array(d,i.byteOffset,i.byteLength),headers:r}}return {body:n,headers:r}}async function ms(t,e,r,n,s,a){let o=`${t}/runs`,{body:i,headers:d}=await rr(r);d.Authorization=`Bearer ${e}`;let l=await hs(o,{method:"POST",headers:d,body:i},n,s,a);return l?.ok?{success:true,statusCode:l.status,error:null}:l?.status===409?{success:true,statusCode:409,error:null}:{success:false,reason:l?`Upload failed with status ${l.status}`:"Upload failed after retries",statusCode:l?.status??null,payload:r,targetEndpoint:o,method:"POST"}}async function vs(t,e,r,n){let s=`${t}/runs/init`;try{let a=await J(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(Xo(r))},n??gs);return a.ok?{runId:(await a.json()).runId}:null}catch{return null}}function ys(t,e,r,n){let s=`${t}/runs/${r}/tests`;fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(n)}).catch(()=>{});}async function bs(t,e,r,n){let s=`${t}/runs/${r}/finalize`;return (await hs(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(n)}))?.ok??false}function nr(t,e,r,n){let s=`${t}/runs/${r}/finalize`,a=new Date,o={finishedAt:a.toISOString(),duration:a.getTime()-new Date(n).getTime(),summary:{}};fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(o)}).catch(()=>{});}var il="1.0.0",xs=10;function ws(t){return t instanceof Error?t.message:String(t)}function ol(t){return t>=400&&t<500&&t!==408&&t!==429}function we(t,e,r,n,s,a,o,i){try{mkdirSync(t,{recursive:!0});let d=Date.now(),l=`${d}-${e}-${r}.json`,c=join(t,l),u={version:il,queuedAt:new Date(d).toISOString(),reason:n,retryCount:0,targetEndpoint:s,method:a,payload:o,headers:i},f=c+".tmp";writeFileSync(f,JSON.stringify(u,null,2),"utf-8"),renameSync(f,c);}catch(d){d.code==="ENOSPC"?process.stderr.write(`\u26A0 TestRelic: Disk full \u2014 unable to queue upload data.
2621
4271
  `):process.stderr.write(`\u26A0 TestRelic: Unable to queue upload data.
2622
- `);}}async function bn(t,e,r){let n;try{n=readdirSync(t).filter(a=>a.endsWith(".json")&&!a.endsWith(".tmp")).sort();}catch{return}if(n.length===0)return;let s=[];for(let a=0;a<n.length;a+=gn)s.push(n.slice(a,a+gn));for(let a of s)for(let l of a){let i=join(t,l),d;try{if(!statSync(i).isFile())continue;let c=JSON.parse(readFileSync(i,"utf-8"));if(!isValidQueueEntry(c)){process.stderr.write(`\u26A0 TestRelic: Corrupted queue file deleted: ${l}
2623
- `),unlinkSync(i);continue}d=c;}catch(o){process.stderr.write(`\u26A0 TestRelic: Skipping unreadable queue file ${l}: ${hn(o)}
2624
- `);try{unlinkSync(i);}catch{}continue}try{let{body:o,headers:c}=await xt(d.payload),p={...d.headers,...c,Authorization:`Bearer ${r}`},h=await fetch(d.targetEndpoint,{method:d.method,headers:p,body:o});if(h.ok)unlinkSync(i);else if(Wa(h.status)){process.stderr.write(`\u26A0 TestRelic: Queue entry ${l} rejected (HTTP ${h.status}) \u2014 moving aside; it will not be retried.
2625
- `);try{renameSync(i,`${i}.failed`);}catch{try{unlinkSync(i);}catch{}}}else {process.stderr.write(`\u26A0 TestRelic: Queue flush paused \u2014 server returned HTTP ${h.status} for ${l}; will retry later.
2626
- `);return}}catch(o){process.stderr.write(`\u26A0 TestRelic: Queue flush paused \u2014 ${hn(o)}; will retry later.
2627
- `);return}}}function wn(t,e){try{let r=readdirSync(t).filter(s=>(s.endsWith(".json")||s.endsWith(".failed"))&&!s.endsWith(".tmp")),n=Date.now();for(let s of r){let a=join(t,s);try{let l=readFileSync(a,"utf-8"),d=JSON.parse(l).queuedAt;if(typeof d=="string"){let o=new Date(d).getTime();n-o>e&&unlinkSync(a);}}catch{try{unlinkSync(a);}catch{}}}}catch{}}var Za=3e5,Xa=864e5,ei=6e4,xn=3e4,St="https://platform.testrelic.ai/settings/api-keys";function kn(t){return new Promise(e=>{let r=setTimeout(e,t);typeof r?.unref=="function"&&r.unref();})}var Se=class{constructor(e){this.gitMetadata=null;this.repoId=null;this.failureReason=null;this.flushPromise=null;this.healthCheckTimer=null;this.config=e,this.authState={mode:"pending",accessToken:null,refreshToken:null,expiresAt:null,orgId:null,orgName:null,userId:null,userName:null};}async initialize(){if(!this.config||!this.config.apiKey){this.setLocalMode("no_api_key"),process.stderr.write(`\u2139 TestRelic: No API key configured. Running in local mode.
2628
- `);return}try{if(Xr(this.config.endpoint),this.gitMetadata=nn(),!await ut(this.config.endpoint)){this.setLocalMode("cloud_unreachable"),process.stderr.write(`\u26A0 TestRelic: Cloud unreachable. Switching to local mode. Data will be queued for later upload.
2629
- `);return}let r=await pt(this.config.endpoint,this.config.apiKey,this.config.timeout);if(ft(r)){this.handleAuthError(r.code,r.statusCode);return}let n=r;this.authState={mode:"cloud",accessToken:n.accessToken,refreshToken:n.refreshToken,expiresAt:Date.now()+n.expiresIn*1e3,orgId:n.orgId,orgName:n.orgName,userId:n.userId,userName:n.userName},process.stderr.write(`\u2713 TestRelic: Connected to cloud (${n.orgName} / ${n.userName})
2630
- `),await this.resolveRepoId(),this.config.queueDirectory&&(wn(this.config.queueDirectory,this.config.queueMaxAge),this.startBackgroundFlush()),this.startHealthCheck();}catch(e){this.setLocalMode("unexpected_error"),process.stderr.write(`\u26A0 TestRelic: Unexpected error during cloud initialization. Running in local mode. ${e instanceof Error?e.message:String(e)}
2631
- `);}}getMode(){return this.authState.mode}isCloudMode(){return this.authState.mode==="cloud"}isLocalMode(){return this.authState.mode==="local"}getAccessToken(){return this.authState.accessToken}getRepoId(){return this.repoId}getGitMetadata(){return this.gitMetadata}getConfig(){return this.config}getFailureReason(){return this.failureReason}getEndpoint(){return this.config?.endpoint??"https://platform.testrelic.ai/api/v1"}async ensureValidToken(){if(!this.isCloudMode()||!this.authState.accessToken)return false;let e=this.authState.expiresAt??0;if(Date.now()+Za<e)return true;if(!this.config||!this.authState.refreshToken)return this.switchToLocalMode("token_expired_no_refresh"),false;try{let r=await tn(this.config.endpoint,this.authState.refreshToken,this.config.timeout);return r?(this.authState.accessToken=r.accessToken,this.authState.refreshToken=r.refreshToken,this.authState.expiresAt=Date.now()+r.expiresIn*1e3,!0):(this.switchToLocalMode("token_refresh_failed"),!1)}catch{return this.switchToLocalMode("token_refresh_error"),false}}switchToLocalMode(e){this.authState.mode!=="local"&&(this.authState.mode="local",this.failureReason=e,process.stderr.write(`\u26A0 TestRelic: Switched to local mode (${e}).
2632
- `));}async dispose(){if(this.healthCheckTimer&&(clearInterval(this.healthCheckTimer),this.healthCheckTimer=null),this.flushPromise){try{await Promise.race([this.flushPromise,kn(xn)]);}catch{}this.flushPromise=null;}this.authState={mode:"local",accessToken:null,refreshToken:null,expiresAt:null,orgId:null,orgName:null,userId:null,userName:null},this.repoId=null,this.gitMetadata=null;}setLocalMode(e){this.authState.mode="local",this.failureReason=e;}handleAuthError(e,r){switch(e){case "invalid_key":this.setLocalMode("invalid_api_key"),process.stderr.write(`\u26A0 TestRelic: API key is invalid or revoked. Running in local mode.
2633
- \u2192 Manage keys: ${St}
4272
+ `);}}async function Ss(t,e,r){let n;try{n=readdirSync(t).filter(a=>a.endsWith(".json")&&!a.endsWith(".tmp")).sort();}catch{return}if(n.length===0)return;let s=[];for(let a=0;a<n.length;a+=xs)s.push(n.slice(a,a+xs));for(let a of s)for(let o of a){let i=join(t,o),d;try{if(!statSync(i).isFile())continue;let c=JSON.parse(readFileSync(i,"utf-8"));if(!isValidQueueEntry(c)){process.stderr.write(`\u26A0 TestRelic: Corrupted queue file deleted: ${o}
4273
+ `),unlinkSync(i);continue}d=c;}catch(l){process.stderr.write(`\u26A0 TestRelic: Skipping unreadable queue file ${o}: ${ws(l)}
4274
+ `);try{unlinkSync(i);}catch{}continue}try{let{body:l,headers:c}=await rr(d.payload),u={...d.headers,...c,Authorization:`Bearer ${r}`},f=await fetch(d.targetEndpoint,{method:d.method,headers:u,body:l});if(f.ok)unlinkSync(i);else if(ol(f.status)){process.stderr.write(`\u26A0 TestRelic: Queue entry ${o} rejected (HTTP ${f.status}) \u2014 moving aside; it will not be retried.
4275
+ `);try{renameSync(i,`${i}.failed`);}catch{try{unlinkSync(i);}catch{}}}else {process.stderr.write(`\u26A0 TestRelic: Queue flush paused \u2014 server returned HTTP ${f.status} for ${o}; will retry later.
4276
+ `);return}}catch(l){process.stderr.write(`\u26A0 TestRelic: Queue flush paused \u2014 ${ws(l)}; will retry later.
4277
+ `);return}}}function Rs(t,e){try{let r=readdirSync(t).filter(s=>(s.endsWith(".json")||s.endsWith(".failed"))&&!s.endsWith(".tmp")),n=Date.now();for(let s of r){let a=join(t,s);try{let o=readFileSync(a,"utf-8"),d=JSON.parse(o).queuedAt;if(typeof d=="string"){let l=new Date(d).getTime();n-l>e&&unlinkSync(a);}}catch{try{unlinkSync(a);}catch{}}}}catch{}}var gl=3e5,hl=864e5,ml=6e4,As=3e4,ir="https://platform.testrelic.ai/settings/api-keys";function _s(t){return new Promise(e=>{let r=setTimeout(e,t);typeof r?.unref=="function"&&r.unref();})}var Ue=class{constructor(e){this.gitMetadata=null;this.repoId=null;this.failureReason=null;this.flushPromise=null;this.healthCheckTimer=null;this.config=e,this.authState={mode:"pending",accessToken:null,refreshToken:null,expiresAt:null,orgId:null,orgName:null,userId:null,userName:null};}async initialize(){if(!this.config||!this.config.apiKey){this.setLocalMode("no_api_key"),process.stderr.write(`\u2139 TestRelic: No API key configured. Running in local mode.
4278
+ `);return}try{if(is(this.config.endpoint),this.gitMetadata=cs(),!await Wt(this.config.endpoint)){this.setLocalMode("cloud_unreachable"),process.stderr.write(`\u26A0 TestRelic: Cloud unreachable. Switching to local mode. Data will be queued for later upload.
4279
+ `);return}let r=await Gt(this.config.endpoint,this.config.apiKey,this.config.timeout);if(Kt(r)){this.handleAuthError(r.code,r.statusCode);return}let n=r;this.authState={mode:"cloud",accessToken:n.accessToken,refreshToken:n.refreshToken,expiresAt:Date.now()+n.expiresIn*1e3,orgId:n.orgId,orgName:n.orgName,userId:n.userId,userName:n.userName},process.stderr.write(`\u2713 TestRelic: Connected to cloud (${n.orgName} / ${n.userName})
4280
+ `),await this.resolveRepoId(),this.config.queueDirectory&&(Rs(this.config.queueDirectory,this.config.queueMaxAge),this.startBackgroundFlush()),this.startHealthCheck();}catch(e){this.setLocalMode("unexpected_error"),process.stderr.write(`\u26A0 TestRelic: Unexpected error during cloud initialization. Running in local mode. ${e instanceof Error?e.message:String(e)}
4281
+ `);}}getMode(){return this.authState.mode}isCloudMode(){return this.authState.mode==="cloud"}isLocalMode(){return this.authState.mode==="local"}getAccessToken(){return this.authState.accessToken}getRepoId(){return this.repoId}getGitMetadata(){return this.gitMetadata}getConfig(){return this.config}getFailureReason(){return this.failureReason}getEndpoint(){return this.config?.endpoint??"https://platform.testrelic.ai/api/v1"}async ensureValidToken(){if(!this.isCloudMode()||!this.authState.accessToken)return false;let e=this.authState.expiresAt??0;if(Date.now()+gl<e)return true;if(!this.config||!this.authState.refreshToken)return this.switchToLocalMode("token_expired_no_refresh"),false;try{let r=await ls(this.config.endpoint,this.authState.refreshToken,this.config.timeout);return r?(this.authState.accessToken=r.accessToken,this.authState.refreshToken=r.refreshToken,this.authState.expiresAt=Date.now()+r.expiresIn*1e3,!0):(this.switchToLocalMode("token_refresh_failed"),!1)}catch{return this.switchToLocalMode("token_refresh_error"),false}}switchToLocalMode(e){this.authState.mode!=="local"&&(this.authState.mode="local",this.failureReason=e,process.stderr.write(`\u26A0 TestRelic: Switched to local mode (${e}).
4282
+ `));}async dispose(){if(this.healthCheckTimer&&(clearInterval(this.healthCheckTimer),this.healthCheckTimer=null),this.flushPromise){try{await Promise.race([this.flushPromise,_s(As)]);}catch{}this.flushPromise=null;}this.authState={mode:"local",accessToken:null,refreshToken:null,expiresAt:null,orgId:null,orgName:null,userId:null,userName:null},this.repoId=null,this.gitMetadata=null;}setLocalMode(e){this.authState.mode="local",this.failureReason=e;}handleAuthError(e,r){switch(e){case "invalid_key":this.setLocalMode("invalid_api_key"),process.stderr.write(`\u26A0 TestRelic: API key is invalid or revoked. Running in local mode.
4283
+ \u2192 Manage keys: ${ir}
2634
4284
  `);break;case "expired_key":this.setLocalMode("expired_api_key"),process.stderr.write(`\u26A0 TestRelic: API key has expired. Running in local mode.
2635
- \u2192 Generate a new key: ${St}
4285
+ \u2192 Generate a new key: ${ir}
2636
4286
  `);break;case "validation_error":this.setLocalMode("validation_error"),process.stderr.write(`\u26A0 TestRelic: API key format invalid. Running in local mode.
2637
- \u2192 Manage keys: ${St}
4287
+ \u2192 Manage keys: ${ir}
2638
4288
  `);break;case "rate_limited":this.setLocalMode("rate_limited"),process.stderr.write(`\u26A0 TestRelic: Rate limited during authentication. Running in local mode.
2639
4289
  `);break;case "timeout":this.setLocalMode("auth_timeout"),process.stderr.write(`\u26A0 TestRelic: Authentication timed out. Running in local mode.
2640
4290
  `);break;default:this.setLocalMode(`auth_error_${r??"unknown"}`),process.stderr.write(`\u26A0 TestRelic: Cloud authentication failed. Running in local mode.
2641
- `);break}}async resolveRepoId(){if(!this.config||!this.authState.accessToken)return;let e=this.gitMetadata?.remoteUrl?le(this.gitMetadata.remoteUrl):null,r=e?sn(e):this.config.projectName??de(process.cwd()),n=e??this.config.projectName??de(process.cwd()),s=this.readRepoCache(n);if(s){this.repoId=s.repoId;return}try{let a=await rn(this.config.endpoint,this.authState.accessToken,n,r,this.config.timeout,this.gitMetadata?.branch);a&&(this.repoId=a.repoId,this.writeRepoCache(n,a.repoId,a.displayName));}catch{}!e&&!this.config.projectName&&!gt(process.cwd())&&process.stderr.write(`\u2139 TestRelic: No git remote or package.json detected. Set project.name in .testrelic for stable project identity.
2642
- `);}readRepoCache(e){if(!this.config)return null;let r=join(this.config.queueDirectory,"..","cache","repo.json");try{if(!existsSync(r))return null;let n=readFileSync(r,"utf-8"),s=JSON.parse(n);if(s.gitId!==e||Date.now()-s.resolvedAt>Xa)return null;let a=this.hashApiKey();return a&&s.apiKeyHash!==a?null:s}catch{return null}}writeRepoCache(e,r,n){if(!this.config)return;let s=join(this.config.queueDirectory,"..","cache"),a=join(s,"repo.json");try{mkdirSync(s,{recursive:!0});let l={repoId:r,gitId:e,displayName:n,resolvedAt:Date.now(),apiKeyHash:this.hashApiKey()??""},i=a+".tmp";writeFileSync(i,JSON.stringify(l,null,2),"utf-8"),renameSync(i,a);}catch{}}hashApiKey(){return this.config?.apiKey?createHash("sha256").update(this.config.apiKey).digest("hex").substring(0,16):null}startBackgroundFlush(){if(!this.config||!this.authState.accessToken)return;let e=this.authState.accessToken,r=this.config.queueDirectory,n=this.config.endpoint;this.flushPromise=Promise.race([bn(r,n,e),kn(xn)]).catch(()=>{});}startHealthCheck(){if(!this.config)return;let e=this.config.endpoint;this.healthCheckTimer=setInterval(async()=>{if(!this.isCloudMode()&&!(!this.failureReason||!["cloud_unreachable","auth_timeout","network_error"].includes(this.failureReason)))try{if(!await ut(e))return;if(this.config?.apiKey){let n=await pt(this.config.endpoint,this.config.apiKey,this.config.timeout);if(!ft(n)){let s=n;this.authState={mode:"cloud",accessToken:s.accessToken,refreshToken:s.refreshToken,expiresAt:Date.now()+s.expiresIn*1e3,orgId:s.orgId,orgName:s.orgName,userId:s.userId,userName:s.userName},this.failureReason=null,process.stderr.write(`\u2713 TestRelic: Cloud connectivity restored.
2643
- `),this.startBackgroundFlush();}}}catch{}},ei),typeof this.healthCheckTimer?.unref=="function"&&this.healthCheckTimer.unref();}};function Tn(t,e,r,n){let s=[],a=[],l=null,i=0,d=null,o=0,c=null,p=0,h=t.filter(g=>g.name===ATTACHMENT_NAME&&g.body),u=false;if(h.length>0)for(let g of h)try{let m=JSON.parse(g.body.toString());if(isTestRelicFilePayload(m)){s=s.concat(m.navigations),a=a.concat(m.apiAssertions),m.networkRequestsFile&&(l=m.networkRequestsFile,i+=m.networkRequestsCount),m.consoleLogsFile&&(d=m.consoleLogsFile,o+=m.consoleLogsCount),m.apiCallsFile&&(c=m.apiCallsFile,p+=m.apiCallsCount),u=!0;continue}if(isTestRelicDataPayload(m)){s=s.concat(m.navigations),Array.isArray(m.apiAssertions)&&(a=a.concat(m.apiAssertions)),i+=m.networkRequests?.length??0,p+=m.apiCalls?.length??0;let y=m.consoleLogs;Array.isArray(y)&&(o+=y.length),u=!0;}}catch{process.stderr.write(`[testrelic] Warning: Corrupt attachment for test "${r}"
2644
- `);}return u||(s=e.filter(g=>g.type==="lambdatest-navigation"&&g.description).map(g=>{try{return JSON.parse(g.description)}catch{return null}}).filter(g=>g!==null)),s.length===0&&i===0&&p===0&&!n&&!u&&process.stderr.write(`[testrelic] Warning: No data found for test "${r}". Make sure your tests import { test, expect } from '@testrelic/playwright-analytics/fixture' instead of '@playwright/test'. Without the TestRelic fixture, network logs, video sync, and Test Navigation data cannot be captured.
2645
- `),{navigations:s,apiAssertions:a,networkRequestsFile:l,networkRequestsCount:i,consoleLogsFile:d,consoleLogsCount:o,apiCallsFile:c,apiCallsCount:p}}function Re(t=process.env){let e=si(t.TESTRELIC_CLOUD_PLATFORM);if(e)return {cloudPlatform:e,cloudBuildId:t.TESTRELIC_CLOUD_BUILD_ID||void 0,cloudSessionId:t.TESTRELIC_CLOUD_SESSION_ID||void 0};if(t.BROWSERSTACK_USERNAME||t.BROWSERSTACK_ACCESS_KEY)return {cloudPlatform:"browserstack",cloudBuildId:t.BROWSERSTACK_BUILD_NAME||t.BROWSERSTACK_BUILD_ID||void 0,cloudSessionId:t.BROWSERSTACK_SESSION_ID||void 0};if(t.LT_USERNAME||t.LAMBDATEST_USERNAME||t.LT_ACCESS_KEY||t.LAMBDATEST_ACCESS_KEY)return {cloudPlatform:"lambdatest",cloudBuildId:t.LT_BUILD||t.LAMBDATEST_BUILD||void 0,cloudSessionId:t.LT_SESSION_ID||t.LAMBDATEST_SESSION_ID||void 0};let r=t.PLAYWRIGHT_WS_ENDPOINT;if(r){if(/cdp\.lambdatest\.com/i.test(r))return {cloudPlatform:"lambdatest"};if(/cdp\.browserstack\.com/i.test(r))return {cloudPlatform:"browserstack"}}return {}}function si(t){if(!t)return;let e=t.toLowerCase().trim();if(e==="browserstack"||e==="bs")return "browserstack";if(e==="lambdatest"||e==="lt")return "lambdatest"}var di=5,Ae=[1e3,3e3,9e3],Y=3,_t=3e4,ci={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".webm":"video/webm",".mp4":"video/mp4",".zip":"application/zip"},Rt=0,At=[];async function ui(){Rt>=di&&await new Promise(t=>At.push(t)),Rt++;}function pi(){Rt--,At.length>0&&At.shift()();}async function _e(t){return new Promise(e=>setTimeout(e,t))}function fi(t){let e=t.substring(t.lastIndexOf(".")).toLowerCase();return ci[e]??"application/octet-stream"}function gi(t){try{return statSync(t).size}catch{return 0}}async function hi(t,e,r,n,s){let a=`${t}/artifacts/upload-url`,l=JSON.stringify({runId:r.runId,testId:r.testId,fileName:basename(r.filePath),contentType:s,type:r.type,sizeBytes:n});for(let i=0;i<Y;i++)try{let d=await O(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:l},_t);if(d.ok)return await d.json();if(d.status>=500&&i<Y-1){await _e(Ae[i]);continue}return null}catch{if(i<Y-1){await _e(Ae[i]);continue}return null}return null}async function mi(t,e,r,n){let s=an(n,_t);for(let a=0;a<Y;a++)try{let l=createReadStream(e),i=Readable.toWeb(l),d=await O(t,{method:"PUT",headers:{"Content-Type":r,"Content-Length":String(n)},body:i,duplex:"half"},s);if(d.ok)return !0;if(d.status>=500&&a<Y-1){await _e(Ae[a]);continue}return !1}catch{if(a<Y-1){await _e(Ae[a]);continue}return false}return false}async function yi(t,e,r){let n=`${t}/artifacts/confirm`;try{return (await O(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({artifactId:r})},_t)).ok}catch{return false}}async function vi(t,e,r,n){let s=gi(r.filePath);if(s===0)return {success:false,storageKey:null,artifactId:null,error:"file_not_found_or_empty"};if(s>n*1024*1024)return {success:false,storageKey:null,artifactId:null,error:"file_too_large"};let a=fi(r.filePath);await ui();try{let l=await hi(t,e,r,s,a);if(!l)return {success:!1,storageKey:null,artifactId:null,error:"upload_url_request_failed"};if(!await mi(l.uploadUrl,r.filePath,a,s))return {success:!1,storageKey:l.storageKey,artifactId:l.artifactId,error:"presigned_put_failed"};let d=await yi(t,e,l.artifactId);return {success:d,storageKey:l.storageKey,artifactId:l.artifactId,error:d?null:"confirm_failed"}}finally{pi();}}async function Cn(t,e,r,n,s){let a=new Map,l=r.map(async i=>{if(s?.expired()){a.set(i.filePath,{success:false,storageKey:null,artifactId:null,error:"upload_budget_exhausted"});return}let d=await vi(t,e,i,n);a.set(i.filePath,d);});return await Promise.allSettled(l),a}function Lt(t){let r=t.getGitMetadata()?.remoteUrl;return r?le(r):t.getConfig()?.projectName??de(process.cwd())}async function Sn(t,e,r,n,s){if(!t.isCloudMode()||e!=="realtime"&&e!=="both"||!await t.ensureValidToken())return null;let l=t.getGitMetadata(),i=F(),d=Re(),o=we();return (await un(t.getEndpoint(),t.getAccessToken(),{runId:r,repoGitId:Lt(t),branch:l?.branch??null,commit:l?.commitSha??null,commitMessage:l?.commitMessage??null,commitAuthor:l?.commitAuthor??null,startedAt:n,totalTests:null,ciProvider:i?.provider??null,ciRunUrl:i?.runUrl??null,environment:s??d.cloudPlatform??null,cloudPlatform:d.cloudPlatform??null,cloudBuildId:d.cloudBuildId??null,cloudSessionId:d.cloudSessionId??null,...o?{runType:o}:{},testType:"e2e"}))?.runId??null}var wi=true,xi=50,Mt={keyMap:new Map,total:0,uploaded:0};async function ki(t,e,r,n,s){let a=new Map;if(!(e?.uploadArtifacts??wi)||!await t.ensureValidToken())return Mt;let d=[],o=new Map;for(let f of n){if(f.artifacts.screenshot){let g=join(f.outputDir,f.artifacts.screenshot);d.push({filePath:g,runId:r,testId:f.testId,type:"screenshot"}),o.set(g,{testId:f.testId,field:"screenshot"});}if(f.artifacts.video){let g=join(f.outputDir,f.artifacts.video);d.push({filePath:g,runId:r,testId:f.testId,type:"video"}),o.set(g,{testId:f.testId,field:"video"});}if(f.artifacts.trace){let g=join(f.outputDir,f.artifacts.trace);d.push({filePath:g,runId:r,testId:f.testId,type:"trace"}),o.set(g,{testId:f.testId,field:"trace"});}}if(d.length===0)return Mt;let c=e?.artifactMaxSizeMb??xi,p=await Cn(t.getEndpoint(),t.getAccessToken(),d,c,s),h=new Map;for(let[f,g]of p){if(!g.success||!g.storageKey)continue;let m=o.get(f);if(!m)continue;let y=h.get(m.testId)??{};m.field==="screenshot"&&(y.screenshotKey=g.storageKey),m.field==="video"&&(y.videoKey=g.storageKey),m.field==="trace"&&(y.traceKey=g.storageKey),h.set(m.testId,y);}for(let f of n){let g=h.get(f.testId);a.set(f.testId,{...f.artifacts,...g?.screenshotKey?{screenshotKey:g.screenshotKey}:{},...g?.videoKey?{videoKey:g.videoKey}:{},...g?.traceKey?{traceKey:g.traceKey}:{}});}let u=Array.from(p.values()).filter(f=>f.success).length;return u>0&&process.stderr.write(`\u2713 TestRelic: Uploaded ${u} artifact(s) to cloud storage.
2646
- `),{keyMap:a,total:d.length,uploaded:u}}async function Et(t,e,r,n,s,a,l,i,d,o,c){let p={realtimeFinalizeAttempted:false,realtimeFinalized:false,batchAttempted:false,batchAcknowledged:false,artifactsTotal:0,artifactsUploaded:0,failureReason:null},h=new xe(e?.uploadBudgetMs??12e4),u=f=>{p.failureReason||(p.failureReason=f);};try{if(t.isCloudMode()&&s&&(r==="realtime"||r==="both")){p.realtimeFinalizeAttempted=!0;let g=await t.ensureValidToken(),m=t.getGitMetadata(),y=mt(c),w={finishedAt:l,duration:i,summary:d,...m?.commitMessage?{commitMessage:m.commitMessage}:{},testType:y.testType,...y.testType==="api"?{apiProtocol:y.apiProtocol,metadata:y.metadata}:{}};if(g){let v=await fn(t.getEndpoint(),t.getAccessToken(),s,w);if(p.realtimeFinalized=v,!v){u("finalize_failed_after_retries");let k=e?.queueDirectory??".testrelic/queue";ce(k,n,"finalize","finalize_failed_after_retries",`${t.getEndpoint()}/runs/${s}/finalize`,"POST",w,{"Content-Type":"application/json"});}}else {u(t.getFailureReason()??"token_invalid");let v=e?.queueDirectory??".testrelic/queue";ce(v,n,"finalize",t.getFailureReason()??"token_invalid",`${t.getEndpoint()}/runs/${s}/finalize`,"POST",w,{"Content-Type":"application/json"});}}let f=new Map;if(t.isCloudMode()&&o&&o.length>0){let g=await ki(t,e,n,o,h);f=g.keyMap,p.artifactsTotal=g.total,p.artifactsUploaded=g.uploaded;}if(t.isCloudMode()&&(!r||r==="batch"||r==="both"))if(p.batchAttempted=!0,await t.ensureValidToken()){let m=t.getGitMetadata(),y=F(),w=Lt(t),v=Ti(a,f),k=yt(v,w,m,y,c,Re()),C=await cn(t.getEndpoint(),t.getAccessToken(),k,async()=>await t.ensureValidToken()?t.getAccessToken():null,e?.timeout,h);if(p.batchAcknowledged=C.success,!C.success){let b=C;u(b.reason);let T=e?.queueDirectory??".testrelic/queue";ce(T,n,"batch",b.reason,b.targetEndpoint,b.method,b.payload,{"Content-Type":"application/json"});}}else {u(t.getFailureReason()??"token_invalid");let m=e?.queueDirectory??".testrelic/queue",y=t.getGitMetadata(),w=F(),v=Lt(t),k=yt(a,v,y,w,void 0,Re());ce(m,n,"batch",t.getFailureReason()??"token_invalid",`${t.getEndpoint()}/runs`,"POST",k,{"Content-Type":"application/json"});}await t.dispose();}catch(f){u(f instanceof Error?f.message:"unexpected_upload_error");try{await t.dispose();}catch{}}return p}function Ti(t,e){if(e.size===0)return t;let r=t.timeline.map(n=>{let s=n.testId;if(!s)return n;let a=e.get(s);return a?{...n,...a.screenshotKey?{screenshotKey:a.screenshotKey}:{},...a.videoKey?{videoKey:a.videoKey}:{}}:n});return {...t,timeline:r}}var Ci=new Set(["batch","realtime","both"]),ue=Object.freeze({attempted:false,acknowledged:false,runId:null,endpoint:null,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:"cloud reporting was never initialised"});function Si(t){switch(t){case "no_api_key":return "cloud mode off \u2014 no API key configured";case "cloud_unreachable":case "network_error":return "cloud endpoint unreachable \u2014 the run stayed local";case "invalid_api_key":return "API key rejected as invalid or revoked \u2014 the run stayed local";case "expired_api_key":return "API key has expired \u2014 the run stayed local";case "validation_error":return "API key format rejected \u2014 the run stayed local";case "rate_limited":return "rate limited during authentication \u2014 the run stayed local";case "auth_timeout":return "authentication timed out \u2014 the run stayed local";case "token_expired_no_refresh":case "token_refresh_failed":case "token_refresh_error":return "access token could not be refreshed \u2014 the run stayed local";case "init_error":return "cloud initialisation timed out \u2014 the run stayed local";case "unexpected_error":return "cloud initialisation failed \u2014 the run stayed local";default:return t?`cloud mode off (${t}) \u2014 the run stayed local`:"cloud mode off \u2014 the run stayed local"}}function Rn(t,e,r){if(!t)return ue;if(!t.isCloudMode())return {...ue,detail:Si(t.getFailureReason())};let n=e??"batch";if(!Ci.has(n))return {...ue,detail:`upload strategy '${n}' is not recognised \u2014 nothing was sent`};let s=t.getEndpoint(),a=n==="realtime"||n==="both";return a&&r?{attempted:true,acknowledged:true,runId:r,endpoint:s,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:null}:{attempted:true,acknowledged:false,runId:null,endpoint:s,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:a?"run init failed \u2014 the platform did not acknowledge this run":`upload runs at the end of the run under the '${n}' strategy \u2014 not sent yet`}}function Pt(t,e,r){if(!t.attempted)return t;if(!e)return {...t,detail:t.acknowledged?"upload timed out before it completed \u2014 the run may be incomplete on the platform":"upload timed out before the platform acknowledged this run",acknowledged:t.acknowledged};let n=t.acknowledged||e.batchAcknowledged,s=t.runId??(e.batchAcknowledged?r:null),a=e.realtimeFinalizeAttempted?e.realtimeFinalized:e.batchAcknowledged,l=null;return n?a||(l="run acknowledged but not closed out \u2014 finalize failed and was queued for retry"):l=e.failureReason?`upload failed (${e.failureReason}) \u2014 queued locally for retry`:"upload failed \u2014 the platform did not acknowledge this run",{attempted:true,acknowledged:n,runId:s,endpoint:t.endpoint,finalized:a,artifactsUploaded:e.artifactsUploaded,artifactsTotal:e.artifactsTotal,detail:l}}function U(t){try{return readFileSync(t,"utf-8").split(`
2647
- `).filter(r=>r.length>0).map(r=>{try{return JSON.parse(r)}catch{return null}}).filter(r=>r!==null)}catch{return []}}function Ft(t){switch(t){case "passed":return "passed";case "failed":return "failed";case "timedOut":return "timedout";case "skipped":return "skipped";case "interrupted":return "failed";default:return "failed"}}function Ei(t,e,r){let n=`${t}::${e}::${r}`;return createHash("sha256").update(n).digest("hex").substring(0,16)}function Pi(t){return t.length<=3?"":t[t.length-2]}function Ni(t){let e=t.findIndex(r=>r.status==="passed");return e>0?`passed on retry ${e}`:null}function Fi(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 Mn(t,e){for(let r of t)e[r.category]=(e[r.category]??0)+1,r.children.length>0&&Mn(r.children,e);}function Di(t){if(t.length===0)return null;let e=[...t].sort((n,s)=>n-s),r=e.reduce((n,s)=>n+s,0);return {avg:Math.round(r/e.length),min:e[0],max:e[e.length-1],p50:j(e,50),p95:j(e,95),p99:j(e,99)}}var Ln=2e5,Ie=1e5;function In(t,e,r,n,s,a,l){let i={retryIndex:t,apiCalls:e?.length??0,apiUrls:[],methodCounts:{},statusRanges:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},responseTimes:[],passedAssertions:0,failedAssertions:0,assertions:r.length,navigations:n.length,navUrls:[],networkRequests:s,consoleLogs:a,actionSteps:l?.length??0,actionCategories:{}};if(e){let o=new Set;for(let c of e){let p=typeof c.method=="string"?c.method:"OTHER";i.methodCounts[p]=(i.methodCounts[p]??0)+1;let h=typeof c.url=="string"?c.url:null;h&&o.size<Ie&&o.add(lt(h));let u=typeof c.responseStatusCode=="number"?c.responseStatusCode:typeof c.statusCode=="number"?c.statusCode:null,f=u!==null&&Number.isFinite(u)?u:null;i.statusRanges[Fi(f)]++;let g=typeof c.responseTimeMs=="number"?c.responseTimeMs:typeof c.durationMs=="number"?c.durationMs:null;g!==null&&Number.isFinite(g)&&i.responseTimes.length<Ln&&i.responseTimes.push(g);}i.apiUrls=Array.from(o);}for(let o of r)o.status==="passed"?i.passedAssertions++:i.failedAssertions++;let d=new Set;for(let o of n)o.url&&d.size<Ie&&d.add(o.url);return i.navUrls=Array.from(d),l&&Mn(l,i.actionCategories),i}function Bi(){return {apiUrlSet:new Set,methodCounts:{},statusRanges:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},responseTimes:[],passedAssertions:0,failedAssertions:0,navUrlSet:new Set,actionCategoryCounts:{},volApiCalls:0,volAssertions:0,volNavigations:0,volNetworkRequests:0,volConsoleLogs:0,volActionSteps:0}}function Oi(t){return process.env.BROWSERSTACK_USERNAME?"BrowserStack":t?{"github-actions":"GitHub Actions","gitlab-ci":"GitLab CI",jenkins:"Jenkins",circleci:"CircleCI","bitbucket-pipelines":"Bitbucket Pipelines"}[t.provider]??"CI":"Local"}function qi(){let t=platform();return t==="win32"?"Windows":t==="darwin"?"macOS":"Linux"}function Ui(t){let e=t.parent?.project?.()?.use;if(!e)return;let{browserName:r,channel:n}=e;if(n){let a=n.toLowerCase();return a==="chrome"||a==="chrome-stable"?"Chrome":a.startsWith("chrome-beta")?"Chrome Beta":a.startsWith("chrome-dev")?"Chrome Dev":a.startsWith("chrome-canary")?"Chrome Canary":a==="msedge"||a==="msedge-stable"?"Microsoft Edge":a.startsWith("msedge-beta")?"Edge Beta":a.startsWith("msedge-dev")?"Edge Dev":a.startsWith("msedge-canary")?"Edge Canary":n}if(r){let a=r.toLowerCase();return a==="chromium"?"Chromium":a==="firefox"?"Firefox":a==="webkit"?"WebKit":r}let s=e.defaultBrowserType;if(s){let a=s.toLowerCase();return a==="chromium"?"Chromium":a==="firefox"?"Firefox":a==="webkit"?"WebKit":s}}function Hi(t,e,r){let n=r.parent?.project?.(),s="unknown";n!==void 0&&(n.use.isMobile===true?s="mobile":s="e2e");let a=["e2e","api","unit","mobile"];for(let d of a)if(t.some(o=>o===`@${d}`||o===d))return d;let l=e.replace(/\\/g,"/"),i=["e2e","api","unit"];for(let d of i)if(l.includes(`/${d}/`))return d;return s}var zi="__testrelic_api_config";function $i(t){return JSON.stringify(t,(e,r)=>r instanceof RegExp?{__regexp:true,source:r.source,flags:r.flags}:r)}var Me=class{constructor(e){this.rootDir="";this.startedAt="";this.testRunId="";this.collectedTests=[];this.fixtureDataReceived=false;this.navigationsCaptured=false;this.memoryPressureWarned=false;this.testCount=0;this.cloudClient=null;this.cloudRunId=null;this.runTimestamp="";this.cloudReceipt=ue;this.pendingArtifactEntries=[];this.detectedOs="";this.detectedSource="";this.cloudTestsBuffer=[];this.activeReportMode="embedded";this.streamingWriter=null;this.testIndex=[];this.summaryCounters={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0,interrupted:0,totalApiCalls:0,totalAssertions:0,totalNavigations:0,totalNetworkRequests:0,totalConsoleLogs:0,totalActionSteps:0};this.streamingTestAgg=new Map;this.config=or(e),this.apiConfig=ir(e);}upsertAggContribution(e,r){let n=this.streamingTestAgg.get(e);(!n||r.retryIndex>n.retryIndex)&&this.streamingTestAgg.set(e,r);}async onBegin(e,r){try{if(this.rootDir=e.rootDir,this.startedAt=new Date().toISOString(),this.testRunId=this.config.testRunId??randomUUID(),this.config.reportMode==="auto"){let l=r.allTests().length;this.activeReportMode=l>=this.config.streamingThreshold?"streaming":"embedded";}else this.activeReportMode=this.config.reportMode;let n=r.allTests().length;if(process.stderr.write(`[testrelic] Report mode: ${this.activeReportMode} (${n} tests)
2648
- `),this.activeReportMode==="streaming"){let l=dirname(this.config.outputPath);this.streamingWriter=new se(l);let i=()=>{if(this.streamingWriter){try{this.streamingWriter.writeIndex(this.testIndex);let d={testRunId:this.testRunId??"",startedAt:this.startedAt,completedAt:new Date().toISOString(),totalDuration:Date.now()-new Date(this.startedAt).getTime(),...this.summaryCounters,metadata:null,reportMode:"streaming",files:[],enrichedSummary:null,validation:null,writeErrors:this.streamingWriter.getWriteErrors(),cloudUpload:this.cloudReceipt};this.streamingWriter.writeSummary(d);}catch{}this.streamingWriter.dispose();}try{if(this.cloudClient?.isCloudMode()&&this.cloudRunId){let d=this.cloudClient.getAccessToken();d&&kt(this.cloudClient.getEndpoint(),d,this.cloudRunId,this.startedAt);}}catch{}};process.on("SIGTERM",i),process.on("SIGINT",i);}if(this.config.includeArtifacts){let l=dirname(this.config.outputPath);this.runTimestamp=Br(join(l,"artifacts"));try{$r(join(l,"artifacts"));}catch{}}let s=F();this.detectedOs=qi(),this.detectedSource=Oi(s);let a=await $(async()=>(this.cloudClient=new Se(this.config.cloud),await this.cloudClient.initialize(),Sn(this.cloudClient,this.config.cloud?.uploadStrategy,this.testRunId,this.startedAt,this.detectedSource)),Wr,"Cloud initialization");if(this.cloudRunId=a??null,a===void 0&&this.cloudClient?.switchToLocalMode("init_error"),this.cloudReceipt=Rn(this.cloudClient,this.config.cloud?.uploadStrategy,this.cloudRunId),this.activeReportMode!=="streaming"){let l=()=>{try{if(this.cloudClient?.isCloudMode()&&this.cloudRunId){let i=this.cloudClient.getAccessToken();i&&kt(this.cloudClient.getEndpoint(),i,this.cloudRunId,this.startedAt);}}catch{}};process.on("SIGTERM",l),process.on("SIGINT",l);}}catch{}}onTestBegin(e,r){try{e.annotations.push({type:zi,description:$i(this.apiConfig)});}catch{}}onTestEnd(e,r){try{let n=r,s=e.outcome(),a;s==="flaky"?a="flaky":s==="skipped"?a="skipped":a=Ft(n.status);let l=n.startTime.toISOString(),i=new Date(n.startTime.getTime()+n.duration).toISOString(),d=e.tags?[...e.tags]:e.annotations.filter(A=>A.type==="tag").map(A=>A.description??""),o=Tn(n.attachments,e.annotations,e.title,a==="skipped");(o.navigations.length>0||o.networkRequestsCount>0||o.apiCallsCount>0)&&(this.fixtureDataReceived=!0),o.navigations.length>0&&(this.navigationsCaptured=!0),!this.config.captureNavigation&&o.navigations.length>0&&(o={...o,navigations:[]});let c=null;if(a==="failed"||a==="flaky"){let R=(a==="flaky"?e.results.find(H=>H.status!=="passed")?.errors??[]:n.errors)[0];if(R){let H=dr(this.config.redactPatterns),te=R.location?.line??null,re=null;this.config.includeCodeSnippets&&te!==null&&R.location?.file&&(re=lr(R.location.file,te,this.config.codeContextLines),re&&(re=H(re))),c={message:H(R.message??"Unknown error"),line:te,code:re,stack:this.config.includeStackTrace&&R.stack?H(R.stack):null};}}let p=e.titlePath().filter(Boolean),h=relative(this.rootDir||".",e.location.file),u=Pi(p),f=p.join(" > "),g=h,m=Ei(g,u,f),y=Hi(d,g,e),w=y!=="mobile"&&o.apiCallsCount>0?"api":y,v=s==="flaky",k=Ni(e.results),C=Ft(e.expectedStatus),b=Ft(n.status),T=null;if(this.config.includeArtifacts&&a!=="skipped"&&n.attachments){let A=dirname(this.config.outputPath),R=p[p.length-1]??e.title;T=Or(n.attachments,R,n.retry,A,this.runTimestamp||void 0,m);}let S=this.config.includeActionSteps&&n.steps?Qr(n.steps,n.startTime):null,L=Ui(e),E={titlePath:p,title:f,status:a,duration:n.duration,startedAt:l,completedAt:i,retryCount:e.results.length-1,retry:n.retry,tags:d,failure:c,specFile:h,navigations:o.navigations,testId:m,filePath:g,suiteName:u,testType:w,isFlaky:v,retryStatus:k,expectedStatus:C,actualStatus:b,artifacts:T,apiAssertions:o.apiAssertions,actions:S,networkRequestsFile:o.networkRequestsFile,networkRequestsCount:o.networkRequestsCount,consoleLogsFile:o.consoleLogsFile,consoleLogsCount:o.consoleLogsCount,apiCallsFile:o.apiCallsFile,apiCallsCount:o.apiCallsCount,browser:L,os:this.detectedOs||void 0,source:this.detectedSource||void 0},$t=o.consoleLogsFile&&o.consoleLogsCount>0?U(o.consoleLogsFile):void 0,Fe=o.networkRequestsFile&&o.networkRequestsCount>0?U(o.networkRequestsFile):void 0,ee=o.apiCallsFile&&o.apiCallsCount>0?U(o.apiCallsFile):void 0,jt=ee?vt(ee):void 0,Wt=!Fe&&ee?bt(ee):void 0,Gt=o.apiAssertions&&o.apiAssertions.length>0?wt(o.apiAssertions):void 0;this.cloudTestsBuffer.push({testId:m,title:f,status:a,duration:n.duration,suiteName:u,filePath:g,testType:w,isFlaky:v,startedAt:l,completedAt:i,tags:d,failure:c,retry:n.retry,retryCount:e.results.length-1,retryStatus:k,browser:L,os:this.detectedOs||void 0,source:this.detectedSource||void 0,...S&&S.length>0?{actions:S}:{},...o.navigations&&o.navigations.length>0?{navigations:o.navigations}:{},...$t?{consoleLogs:$t}:{},...Fe?{networkRequests:Fe}:Wt?{networkRequests:Wt}:{},...jt?{apiCalls:jt}:{},...Gt?{protocolAssertions:Gt}:{}});try{let A=this.config.cloud?.uploadStrategy;if(this.cloudClient?.isCloudMode()&&this.cloudRunId&&(A==="realtime"||A==="both")){let R=this.cloudClient.getAccessToken();R&&pn(this.cloudClient.getEndpoint(),R,this.cloudRunId,this.buildCloudTestPayload(E));}}catch{}if(this.activeReportMode==="streaming"&&this.streamingWriter){let A=De(g,p,e.id??"",n.retry),R=n.retry===e.results.length-1,H={id:A,navigations:o.navigations,apiAssertions:o.apiAssertions,actions:S??[],artifacts:T,failureDiagnostic:c,hasNetworkFile:o.networkRequestsFile!==null,networkRequestsCount:o.networkRequestsCount,hasConsoleFile:o.consoleLogsFile!==null,consoleLogsCount:o.consoleLogsCount,hasApiCallsFile:o.apiCallsFile!==null,apiCallsCount:o.apiCallsCount,startedAt:l};this.streamingWriter.writeTestDetail(A,H,{networkRequestsFile:o.networkRequestsFile,consoleLogsFile:o.consoleLogsFile,apiCallsFile:o.apiCallsFile});let te={id:A,title:p[p.length-1]??e.title,titlePath:p,filePath:g,status:a,duration:n.duration,project:p[1]??"",retryIndex:n.retry,tags:d,hasNetworkData:o.networkRequestsCount>0,hasConsoleData:o.consoleLogsCount>0,hasArtifacts:T!==null,hasActionSteps:(S?.length??0)>0,errorMessage:c?.message?.split(`
2649
- `)[0]??null,networkCount:o.networkRequestsCount,consoleCount:o.consoleLogsCount,actionCount:S?.length??0,isRetry:!R,retryGroupKey:Be(g,p,e.id??"")};if(this.testIndex.push(te),R)switch(this.summaryCounters.total++,a){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}this.upsertAggContribution(`${g}::${p.join(" > ")}`,In(n.retry,ee,o.apiAssertions,o.navigations,o.networkRequestsCount,o.consoleLogsCount,S)),T&&this.pendingArtifactEntries.push({testId:m,artifacts:T,outputDir:dirname(this.config.outputPath)}),E.actions=null,E.apiAssertions=null,E.navigations=[],E.artifacts=null,E.failure=null,E.networkRequestsFile=null,E.consoleLogsFile=null,E.apiCallsFile=null;}this.activeReportMode!=="streaming"&&this.collectedTests.push(E),this.testCount++,this.checkMemoryPressure();}catch{}}async onEnd(e){try{let r=new Date().toISOString(),n=new Date(this.startedAt).getTime(),a=new Date(r).getTime()-n,l=this.activeReportMode==="streaming"?this.testIndex.some(d=>d.status!=="skipped"):this.collectedTests.some(d=>d.status!=="skipped");!this.fixtureDataReceived&&l&&process.stderr.write(`
4291
+ `);break}}async resolveRepoId(){if(!this.config||!this.authState.accessToken)return;let e=this.gitMetadata?.remoteUrl?be(this.gitMetadata.remoteUrl):null,r=e?us(e):this.config.projectName??xe(process.cwd()),n=e??this.config.projectName??xe(process.cwd()),s=this.readRepoCache(n);if(s){this.repoId=s.repoId;return}try{let a=await ds(this.config.endpoint,this.authState.accessToken,n,r,this.config.timeout,this.gitMetadata?.branch);a&&(this.repoId=a.repoId,this.writeRepoCache(n,a.repoId,a.displayName));}catch{}!e&&!this.config.projectName&&!Jt(process.cwd())&&process.stderr.write(`\u2139 TestRelic: No git remote or package.json detected. Set project.name in .testrelic for stable project identity.
4292
+ `);}readRepoCache(e){if(!this.config)return null;let r=join(this.config.queueDirectory,"..","cache","repo.json");try{if(!existsSync(r))return null;let n=readFileSync(r,"utf-8"),s=JSON.parse(n);if(s.gitId!==e||Date.now()-s.resolvedAt>hl)return null;let a=this.hashApiKey();return a&&s.apiKeyHash!==a?null:s}catch{return null}}writeRepoCache(e,r,n){if(!this.config)return;let s=join(this.config.queueDirectory,"..","cache"),a=join(s,"repo.json");try{mkdirSync(s,{recursive:!0});let o={repoId:r,gitId:e,displayName:n,resolvedAt:Date.now(),apiKeyHash:this.hashApiKey()??""},i=a+".tmp";writeFileSync(i,JSON.stringify(o,null,2),"utf-8"),renameSync(i,a);}catch{}}hashApiKey(){return this.config?.apiKey?createHash("sha256").update(this.config.apiKey).digest("hex").substring(0,16):null}startBackgroundFlush(){if(!this.config||!this.authState.accessToken)return;let e=this.authState.accessToken,r=this.config.queueDirectory,n=this.config.endpoint;this.flushPromise=Promise.race([Ss(r,n,e),_s(As)]).catch(()=>{});}startHealthCheck(){if(!this.config)return;let e=this.config.endpoint;this.healthCheckTimer=setInterval(async()=>{if(!this.isCloudMode()&&!(!this.failureReason||!["cloud_unreachable","auth_timeout","network_error"].includes(this.failureReason)))try{if(!await Wt(e))return;if(this.config?.apiKey){let n=await Gt(this.config.endpoint,this.config.apiKey,this.config.timeout);if(!Kt(n)){let s=n;this.authState={mode:"cloud",accessToken:s.accessToken,refreshToken:s.refreshToken,expiresAt:Date.now()+s.expiresIn*1e3,orgId:s.orgId,orgName:s.orgName,userId:s.userId,userName:s.userName},this.failureReason=null,process.stderr.write(`\u2713 TestRelic: Cloud connectivity restored.
4293
+ `),this.startBackgroundFlush();}}}catch{}},ml),typeof this.healthCheckTimer?.unref=="function"&&this.healthCheckTimer.unref();}};function xl(t,e){return e?t?{"2xx":t["2xx"]+e["2xx"],"3xx":t["3xx"]+e["3xx"],"4xx":t["4xx"]+e["4xx"],"5xx":t["5xx"]+e["5xx"],error:t.error+e.error}:{...e}:t}function Is(t,e,r,n){let s=[],a=[],o=null,i=0,d=null,l=0,c=null,u=0,f=null,p=t.filter(h=>h.name===ATTACHMENT_NAME&&h.body),g=false;if(p.length>0)for(let h of p)try{let v=JSON.parse(h.body.toString());if(isTestRelicFilePayload(v)){s=s.concat(v.navigations),a=a.concat(v.apiAssertions),v.networkRequestsFile&&(o=v.networkRequestsFile,i+=v.networkRequestsCount),v.consoleLogsFile&&(d=v.consoleLogsFile,l+=v.consoleLogsCount),v.apiCallsFile&&(c=v.apiCallsFile,u+=v.apiCallsCount),f=xl(f,v.apiStatusMix),g=!0;continue}if(isTestRelicDataPayload(v)){s=s.concat(v.navigations),Array.isArray(v.apiAssertions)&&(a=a.concat(v.apiAssertions)),i+=v.networkRequests?.length??0,u+=v.apiCalls?.length??0;let y=v.consoleLogs;Array.isArray(y)&&(l+=y.length),g=!0;}}catch{process.stderr.write(`[testrelic] Warning: Corrupt attachment for test "${r}"
4294
+ `);}return g||(s=e.filter(h=>h.type==="lambdatest-navigation"&&h.description).map(h=>{try{return JSON.parse(h.description)}catch{return null}}).filter(h=>h!==null)),s.length===0&&i===0&&u===0&&!n&&!g&&process.stderr.write(`[testrelic] Warning: No data found for test "${r}". Make sure your tests import { test, expect } from '@testrelic/playwright-analytics/fixture' instead of '@playwright/test'. Without the TestRelic fixture, network logs, video sync, and Test Navigation data cannot be captured.
4295
+ `),{navigations:s,apiAssertions:a,networkRequestsFile:o,networkRequestsCount:i,consoleLogsFile:d,consoleLogsCount:l,apiCallsFile:c,apiCallsCount:u,apiStatusMix:f}}function ze(t=process.env){let e=wl(t.TESTRELIC_CLOUD_PLATFORM);if(e)return {cloudPlatform:e,cloudBuildId:t.TESTRELIC_CLOUD_BUILD_ID||void 0,cloudSessionId:t.TESTRELIC_CLOUD_SESSION_ID||void 0};if(t.BROWSERSTACK_USERNAME||t.BROWSERSTACK_ACCESS_KEY)return {cloudPlatform:"browserstack",cloudBuildId:t.BROWSERSTACK_BUILD_NAME||t.BROWSERSTACK_BUILD_ID||void 0,cloudSessionId:t.BROWSERSTACK_SESSION_ID||void 0};if(t.LT_USERNAME||t.LAMBDATEST_USERNAME||t.LT_ACCESS_KEY||t.LAMBDATEST_ACCESS_KEY)return {cloudPlatform:"lambdatest",cloudBuildId:t.LT_BUILD||t.LAMBDATEST_BUILD||void 0,cloudSessionId:t.LT_SESSION_ID||t.LAMBDATEST_SESSION_ID||void 0};let r=t.PLAYWRIGHT_WS_ENDPOINT;if(r){if(/cdp\.lambdatest\.com/i.test(r))return {cloudPlatform:"lambdatest"};if(/cdp\.browserstack\.com/i.test(r))return {cloudPlatform:"browserstack"}}return {}}function wl(t){if(!t)return;let e=t.toLowerCase().trim();if(e==="browserstack"||e==="bs")return "browserstack";if(e==="lambdatest"||e==="lt")return "lambdatest"}var Rl=5,je=[1e3,3e3,9e3],ie=3,dr=3e4,Al={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".webm":"video/webm",".mp4":"video/mp4",".zip":"application/zip",".json":"application/json"},or=0,lr=[];async function _l(){or>=Rl&&await new Promise(t=>lr.push(t)),or++;}function Il(){or--,lr.length>0&&lr.shift()();}async function Ve(t){return new Promise(e=>setTimeout(e,t))}function Ml(t){let e=t.substring(t.lastIndexOf(".")).toLowerCase();return Al[e]??"application/octet-stream"}function El(t){try{return statSync(t).size}catch{return 0}}async function Pl(t,e,r,n,s){let a=`${t}/artifacts/upload-url`,o=JSON.stringify({runId:r.runId,testId:r.testId,fileName:basename(r.filePath),contentType:s,type:r.type,sizeBytes:n});for(let i=0;i<ie;i++)try{let d=await J(a,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:o},dr);if(d.ok)return await d.json();if(d.status>=500&&i<ie-1){await Ve(je[i]);continue}return null}catch{if(i<ie-1){await Ve(je[i]);continue}return null}return null}async function Ll(t,e,r,n){let s=ps(n,dr);for(let a=0;a<ie;a++)try{let o=createReadStream(e),i=Readable.toWeb(o),d=await J(t,{method:"PUT",headers:{"Content-Type":r,"Content-Length":String(n)},body:i,duplex:"half"},s);if(d.ok)return !0;if(d.status>=500&&a<ie-1){await Ve(je[a]);continue}return !1}catch{if(a<ie-1){await Ve(je[a]);continue}return false}return false}async function Nl(t,e,r){let n=`${t}/artifacts/confirm`;try{return (await J(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({artifactId:r})},dr)).ok}catch{return false}}async function Dl(t,e,r,n){let s=El(r.filePath);if(s===0)return {success:false,storageKey:null,artifactId:null,error:"file_not_found_or_empty"};if(s>n*1024*1024)return {success:false,storageKey:null,artifactId:null,error:"file_too_large"};let a=Ml(r.filePath);await _l();try{let o=await Pl(t,e,r,s,a);if(!o)return {success:!1,storageKey:null,artifactId:null,error:"upload_url_request_failed"};if(!await Ll(o.uploadUrl,r.filePath,a,s))return {success:!1,storageKey:o.storageKey,artifactId:o.artifactId,error:"presigned_put_failed"};let d=await Nl(t,e,o.artifactId);return {success:d,storageKey:o.storageKey,artifactId:o.artifactId,error:d?null:"confirm_failed"}}finally{Il();}}async function Ms(t,e,r,n,s){let a=new Map,o=r.map(async i=>{if(s?.expired()){a.set(i.filePath,{success:false,storageKey:null,artifactId:null,error:"upload_budget_exhausted"});return}let d=await Dl(t,e,i,n);a.set(i.filePath,d);});return await Promise.allSettled(o),a}function ur(t){let r=t.getGitMetadata()?.remoteUrl;return r?be(r):t.getConfig()?.projectName??xe(process.cwd())}async function Es(t,e,r,n,s){if(!t.isCloudMode()||e!=="realtime"&&e!=="both"||!await t.ensureValidToken())return null;let o=t.getGitMetadata(),i=V(),d=ze(),l=Fe();return (await vs(t.getEndpoint(),t.getAccessToken(),{runId:r,repoGitId:ur(t),branch:o?.branch??null,commit:o?.commitSha??null,commitMessage:o?.commitMessage??null,commitAuthor:o?.commitAuthor??null,startedAt:n,totalTests:null,ciProvider:i?.provider??null,ciRunUrl:i?.runUrl??null,environment:s??d.cloudPlatform??null,cloudPlatform:d.cloudPlatform??null,cloudBuildId:d.cloudBuildId??null,cloudSessionId:d.cloudSessionId??null,...l?{runType:l}:{},testType:"e2e"}))?.runId??null}var Bl=[{field:"baseline",keyField:"baselineKey",type:"baseline"},{field:"actual",keyField:"actualKey",type:"screenshot"},{field:"diff",keyField:"diffKey",type:"diff"},{field:"baselineDom",keyField:"baselineDomKey",type:"dom"},{field:"actualDom",keyField:"actualDomKey",type:"dom"}],Ol=true,ql=50,cr={keyMap:new Map,visualKeyMap:new Map,total:0,uploaded:0};async function Hl(t,e,r,n,s){let a=new Map,o=new Map;if(!(e?.uploadArtifacts??Ol)||!await t.ensureValidToken())return cr;let l=[],c=new Map,u=new Map;for(let h of n){if(h.artifacts.screenshot){let y=join(h.outputDir,h.artifacts.screenshot);l.push({filePath:y,runId:r,testId:h.testId,type:"screenshot"}),c.set(y,{testId:h.testId,field:"screenshot"});}if(h.artifacts.video){let y=join(h.outputDir,h.artifacts.video);l.push({filePath:y,runId:r,testId:h.testId,type:"video"}),c.set(y,{testId:h.testId,field:"video"});}if(h.artifacts.trace){let y=join(h.outputDir,h.artifacts.trace);l.push({filePath:y,runId:r,testId:h.testId,type:"trace"}),c.set(y,{testId:h.testId,field:"trace"});}let v=h.visual??[];for(let y=0;y<v.length;y++){let b=v[y];if(b)for(let k of Bl){let A=b[k.field];if(!A)continue;let E=join(h.outputDir,A);l.push({filePath:E,runId:r,testId:h.testId,type:k.type}),c.set(E,{testId:h.testId,field:"visual",visualIndex:y,visualKey:k.keyField});}}}if(l.length===0)return cr;let f=e?.artifactMaxSizeMb??ql,p=await Ms(t.getEndpoint(),t.getAccessToken(),l,f,s),g=new Map;for(let[h,v]of p){if(!v.success||!v.storageKey)continue;let y=c.get(h);if(!y)continue;let b=g.get(y.testId)??{};if(y.field==="screenshot"&&(b.screenshotKey=v.storageKey),y.field==="video"&&(b.videoKey=v.storageKey),y.field==="trace"&&(b.traceKey=v.storageKey),y.field==="visual"&&y.visualIndex!==void 0&&y.visualKey){let k=u.get(y.testId)??new Map,A=k.get(y.visualIndex)??{};A[y.visualKey]=v.storageKey,k.set(y.visualIndex,A),u.set(y.testId,k);}g.set(y.testId,b);}for(let h of n){if(h.visual&&h.visual.length>0){let y=u.get(h.testId);o.set(h.testId,h.visual.map((b,k)=>({...b,...y?.get(k)??{}})));}let v=g.get(h.testId);a.set(h.testId,{...h.artifacts,...v?.screenshotKey?{screenshotKey:v.screenshotKey}:{},...v?.videoKey?{videoKey:v.videoKey}:{},...v?.traceKey?{traceKey:v.traceKey}:{}});}let m=Array.from(p.values()).filter(h=>h.success).length;return m>0&&process.stderr.write(`\u2713 TestRelic: Uploaded ${m} artifact(s) to cloud storage.
4296
+ `),{keyMap:a,visualKeyMap:o,total:l.length,uploaded:m}}async function pr(t,e,r,n,s,a,o,i,d,l,c){let u={realtimeFinalizeAttempted:false,realtimeFinalized:false,batchAttempted:false,batchAcknowledged:false,artifactsTotal:0,artifactsUploaded:0,failureReason:null},f=new Be(e?.uploadBudgetMs??12e4),p=g=>{u.failureReason||(u.failureReason=g);};try{if(t.isCloudMode()&&s&&(r==="realtime"||r==="both")){u.realtimeFinalizeAttempted=!0;let h=await t.ensureValidToken(),v=t.getGitMetadata(),y=Xt(c),b={finishedAt:o,duration:i,summary:d,...v?.commitMessage?{commitMessage:v.commitMessage}:{},testType:y.testType,...y.testType==="api"?{apiProtocol:y.apiProtocol,metadata:y.metadata}:{}};if(h){let k=await bs(t.getEndpoint(),t.getAccessToken(),s,b);if(u.realtimeFinalized=k,!k){p("finalize_failed_after_retries");let A=e?.queueDirectory??".testrelic/queue";we(A,n,"finalize","finalize_failed_after_retries",`${t.getEndpoint()}/runs/${s}/finalize`,"POST",b,{"Content-Type":"application/json"});}}else {p(t.getFailureReason()??"token_invalid");let k=e?.queueDirectory??".testrelic/queue";we(k,n,"finalize",t.getFailureReason()??"token_invalid",`${t.getEndpoint()}/runs/${s}/finalize`,"POST",b,{"Content-Type":"application/json"});}}let g=new Map,m=new Map;if(t.isCloudMode()&&l&&l.length>0){let h=await Hl(t,e,n,l,f);g=h.keyMap,m=h.visualKeyMap,u.artifactsTotal=h.total,u.artifactsUploaded=h.uploaded;}if(t.isCloudMode()&&(!r||r==="batch"||r==="both"))if(u.batchAttempted=!0,await t.ensureValidToken()){let v=t.getGitMetadata(),y=V(),b=ur(t),k=Ul(zl(a,g),m),A=Zt(k,b,v,y,c,ze()),E=await ms(t.getEndpoint(),t.getAccessToken(),A,async()=>await t.ensureValidToken()?t.getAccessToken():null,e?.timeout,f);if(u.batchAcknowledged=E.success,!E.success){let T=E;p(T.reason);let w=e?.queueDirectory??".testrelic/queue";we(w,n,"batch",T.reason,T.targetEndpoint,T.method,T.payload,{"Content-Type":"application/json"});}}else {p(t.getFailureReason()??"token_invalid");let v=e?.queueDirectory??".testrelic/queue",y=t.getGitMetadata(),b=V(),k=ur(t),A=Zt(a,k,y,b,void 0,ze());we(v,n,"batch",t.getFailureReason()??"token_invalid",`${t.getEndpoint()}/runs`,"POST",A,{"Content-Type":"application/json"});}await t.dispose();}catch(g){p(g instanceof Error?g.message:"unexpected_upload_error");try{await t.dispose();}catch{}}return u}function Ul(t,e){if(e.size===0)return t;let r=t.timeline.map(n=>{let s=n.tests;if(!Array.isArray(s))return n;let a=false,o=s.map(i=>{let d=i.testId;if(!d)return i;let l=e.get(d);return l?(a=true,{...i,visual:l}):i});return a?{...n,tests:o}:n});return {...t,timeline:r}}function zl(t,e){if(e.size===0)return t;let r=t.timeline.map(n=>{let s=n.testId;if(!s)return n;let a=e.get(s);return a?{...n,...a.screenshotKey?{screenshotKey:a.screenshotKey}:{},...a.videoKey?{videoKey:a.videoKey}:{}}:n});return {...t,timeline:r}}var jl=new Set(["batch","realtime","both"]),ke=Object.freeze({attempted:false,acknowledged:false,runId:null,endpoint:null,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:"cloud reporting was never initialised"});function Vl(t){switch(t){case "no_api_key":return "cloud mode off \u2014 no API key configured";case "cloud_unreachable":case "network_error":return "cloud endpoint unreachable \u2014 the run stayed local";case "invalid_api_key":return "API key rejected as invalid or revoked \u2014 the run stayed local";case "expired_api_key":return "API key has expired \u2014 the run stayed local";case "validation_error":return "API key format rejected \u2014 the run stayed local";case "rate_limited":return "rate limited during authentication \u2014 the run stayed local";case "auth_timeout":return "authentication timed out \u2014 the run stayed local";case "token_expired_no_refresh":case "token_refresh_failed":case "token_refresh_error":return "access token could not be refreshed \u2014 the run stayed local";case "init_error":return "cloud initialisation timed out \u2014 the run stayed local";case "unexpected_error":return "cloud initialisation failed \u2014 the run stayed local";default:return t?`cloud mode off (${t}) \u2014 the run stayed local`:"cloud mode off \u2014 the run stayed local"}}function Ps(t,e,r){if(!t)return ke;if(!t.isCloudMode())return {...ke,detail:Vl(t.getFailureReason())};let n=e??"batch";if(!jl.has(n))return {...ke,detail:`upload strategy '${n}' is not recognised \u2014 nothing was sent`};let s=t.getEndpoint(),a=n==="realtime"||n==="both";return a&&r?{attempted:true,acknowledged:true,runId:r,endpoint:s,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:null}:{attempted:true,acknowledged:false,runId:null,endpoint:s,finalized:false,artifactsUploaded:0,artifactsTotal:0,detail:a?"run init failed \u2014 the platform did not acknowledge this run":`upload runs at the end of the run under the '${n}' strategy \u2014 not sent yet`}}function fr(t,e,r){if(!t.attempted)return t;if(!e)return {...t,detail:t.acknowledged?"upload timed out before it completed \u2014 the run may be incomplete on the platform":"upload timed out before the platform acknowledged this run",acknowledged:t.acknowledged};let n=t.acknowledged||e.batchAcknowledged,s=t.runId??(e.batchAcknowledged?r:null),a=e.realtimeFinalizeAttempted?e.realtimeFinalized:e.batchAcknowledged,o=null;return n?a||(o="run acknowledged but not closed out \u2014 finalize failed and was queued for retry"):o=e.failureReason?`upload failed (${e.failureReason}) \u2014 queued locally for retry`:"upload failed \u2014 the platform did not acknowledge this run",{attempted:true,acknowledged:n,runId:s,endpoint:t.endpoint,finalized:a,artifactsUploaded:e.artifactsUploaded,artifactsTotal:e.artifactsTotal,detail:o}}function Y(t){try{return readFileSync(t,"utf-8").split(`
4297
+ `).filter(r=>r.length>0).map(r=>{try{return JSON.parse(r)}catch{return null}}).filter(r=>r!==null)}catch{return []}}function Xl(t){if(!t)return null;let e=t.split(`
4298
+ `).map(a=>a.trim()).filter(Boolean);if(e.length===0)return null;let r=e.filter(a=>!/^Error:\s*expect\(/.test(a));if(r.length===0)return e[0]??null;let n=r[0],s=r[1];return s!==void 0&&/^Received:/.test(s)&&/^Expected:/.test(n)?`${n} \xB7 ${s}`:n}function hr(t){switch(t){case "passed":return "passed";case "failed":return "failed";case "timedOut":return "timedout";case "skipped":return "skipped";case "interrupted":return "failed";default:return "failed"}}function Zl(t,e,r){let n=`${t}::${e}::${r}`;return createHash("sha256").update(n).digest("hex").substring(0,16)}function Ql(t){return t.length<=3?"":t[t.length-2]}function ed(t){let e=t.findIndex(r=>r.status==="passed");return e>0?`passed on retry ${e}`:null}function qs(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 td(t){let e={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},r=e;for(let n of t){let s=typeof n.responseStatusCode=="number"?n.responseStatusCode:null;r[qs(s)]++;}return e}function Fs(t){if(t){for(let e of t)if(e.status==="failed")return {type:e.type,expected:Bs(e.expected),actual:Bs(e.actual),...e.expression?{expression:e.expression}:{}}}}function Bs(t){if(t===void 0)return "undefined";if(t===null)return "null";let e;if(typeof t=="string")e=t;else try{e=JSON.stringify(t)??String(t);}catch{e=String(t);}return e.length>200?e.slice(0,200)+"\u2026":e}function We(t,e){if(!t)return 0;let r=0;for(let n of t)n.status===e&&r++;return r}function Hs(t,e){for(let r of t)e[r.category]=(e[r.category]??0)+1,r.children.length>0&&Hs(r.children,e);}function rd(t){if(t.length===0)return null;let e=[...t].sort((n,s)=>n-s),r=e.reduce((n,s)=>n+s,0);return {avg:Math.round(r/e.length),min:e[0],max:e[e.length-1],p50:ee(e,50),p95:ee(e,95),p99:ee(e,99)}}var Us=2e5,Ge=1e5;function Os(t,e,r,n,s,a,o){let i={retryIndex:t,apiCalls:e?.length??0,apiUrls:[],methodCounts:{},statusRanges:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},responseTimes:[],passedAssertions:0,failedAssertions:0,assertions:r.length,navigations:n.length,navUrls:[],networkRequests:s,consoleLogs:a,actionSteps:o?.length??0,actionCategories:{}};if(e){let l=new Set;for(let c of e){let u=typeof c.method=="string"?c.method:"OTHER";i.methodCounts[u]=(i.methodCounts[u]??0)+1;let f=typeof c.url=="string"?c.url:null;f&&l.size<Ge&&l.add(jt(f));let p=typeof c.responseStatusCode=="number"?c.responseStatusCode:typeof c.statusCode=="number"?c.statusCode:null,g=p!==null&&Number.isFinite(p)?p:null;i.statusRanges[qs(g)]++;let m=typeof c.responseTimeMs=="number"?c.responseTimeMs:typeof c.durationMs=="number"?c.durationMs:null;m!==null&&Number.isFinite(m)&&i.responseTimes.length<Us&&i.responseTimes.push(m);}i.apiUrls=Array.from(l);}for(let l of r)l.status==="passed"?i.passedAssertions++:i.failedAssertions++;let d=new Set;for(let l of n)l.url&&d.size<Ge&&d.add(l.url);return i.navUrls=Array.from(d),o&&Hs(o,i.actionCategories),i}function nd(){return {apiUrlSet:new Set,methodCounts:{},statusRanges:{"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0},responseTimes:[],passedAssertions:0,failedAssertions:0,navUrlSet:new Set,actionCategoryCounts:{},volApiCalls:0,volAssertions:0,volNavigations:0,volNetworkRequests:0,volConsoleLogs:0,volActionSteps:0}}function sd(t){return process.env.BROWSERSTACK_USERNAME?"BrowserStack":t?{"github-actions":"GitHub Actions","gitlab-ci":"GitLab CI",jenkins:"Jenkins",circleci:"CircleCI","bitbucket-pipelines":"Bitbucket Pipelines"}[t.provider]??"CI":"Local"}function ad(){let t=platform();return t==="win32"?"Windows":t==="darwin"?"macOS":"Linux"}function id(t){let e=t.parent?.project?.()?.use;if(!e)return;let{browserName:r,channel:n}=e;if(n){let a=n.toLowerCase();return a==="chrome"||a==="chrome-stable"?"Chrome":a.startsWith("chrome-beta")?"Chrome Beta":a.startsWith("chrome-dev")?"Chrome Dev":a.startsWith("chrome-canary")?"Chrome Canary":a==="msedge"||a==="msedge-stable"?"Microsoft Edge":a.startsWith("msedge-beta")?"Edge Beta":a.startsWith("msedge-dev")?"Edge Dev":a.startsWith("msedge-canary")?"Edge Canary":n}if(r){let a=r.toLowerCase();return a==="chromium"?"Chromium":a==="firefox"?"Firefox":a==="webkit"?"WebKit":r}let s=e.defaultBrowserType;if(s){let a=s.toLowerCase();return a==="chromium"?"Chromium":a==="firefox"?"Firefox":a==="webkit"?"WebKit":s}}function od(t,e,r){let n=r.parent?.project?.(),s="unknown";n!==void 0&&(n.use.isMobile===true?s="mobile":s="e2e");let a=["e2e","api","unit","mobile"];for(let d of a)if(t.some(l=>l===`@${d}`||l===d))return d;let o=e.replace(/\\/g,"/"),i=["e2e","api","unit"];for(let d of i)if(o.includes(`/${d}/`))return d;return s}var ld="__testrelic_api_config";function dd(t){return JSON.stringify(t,(e,r)=>r instanceof RegExp?{__regexp:true,source:r.source,flags:r.flags}:r)}var Ke=class{constructor(e){this.rootDir="";this.startedAt="";this.testRunId="";this.collectedTests=[];this.fixtureDataReceived=false;this.navigationsCaptured=false;this.memoryPressureWarned=false;this.testCount=0;this.cloudClient=null;this.cloudRunId=null;this.runTimestamp="";this.updatingSnapshots=false;this.cloudReceipt=ke;this.pendingArtifactEntries=[];this.detectedOs="";this.detectedSource="";this.cloudTestsBuffer=[];this.activeReportMode="embedded";this.streamingWriter=null;this.testIndex=[];this.summaryCounters={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0,interrupted:0,totalApiCalls:0,totalAssertions:0,totalNavigations:0,totalNetworkRequests:0,totalConsoleLogs:0,totalActionSteps:0,visualChecks:0,visualFailures:0,visualDomChanges:0};this.streamingTestAgg=new Map;this.config=Hr(e),this.apiConfig=qr(e);}upsertAggContribution(e,r){let n=this.streamingTestAgg.get(e);(!n||r.retryIndex>n.retryIndex)&&this.streamingTestAgg.set(e,r);}async onBegin(e,r){try{this.rootDir=e.rootDir;let n=e.updateSnapshots;if(this.updatingSnapshots=n==="all"||n==="changed",this.startedAt=new Date().toISOString(),this.testRunId=this.config.testRunId??randomUUID(),this.config.reportMode==="auto"){let i=r.allTests().length;this.activeReportMode=i>=this.config.streamingThreshold?"streaming":"embedded";}else this.activeReportMode=this.config.reportMode;let s=r.allTests().length;if(process.stderr.write(`[testrelic] Report mode: ${this.activeReportMode} (${s} tests)
4299
+ `),this.activeReportMode==="streaming"){let i=dirname(this.config.outputPath);this.streamingWriter=new he(i);let d=()=>{if(this.streamingWriter){try{this.streamingWriter.writeIndex(this.testIndex);let l={testRunId:this.testRunId??"",startedAt:this.startedAt,completedAt:new Date().toISOString(),totalDuration:Date.now()-new Date(this.startedAt).getTime(),...this.summaryCounters,metadata:null,reportMode:"streaming",files:[],enrichedSummary:null,validation:null,writeErrors:this.streamingWriter.getWriteErrors(),cloudUpload:this.cloudReceipt};this.streamingWriter.writeSummary(l);}catch{}this.streamingWriter.dispose();}try{if(this.cloudClient?.isCloudMode()&&this.cloudRunId){let l=this.cloudClient.getAccessToken();l&&nr(this.cloudClient.getEndpoint(),l,this.cloudRunId,this.startedAt);}}catch{}};process.on("SIGTERM",d),process.on("SIGINT",d);}if(this.config.includeArtifacts){let i=dirname(this.config.outputPath);this.runTimestamp=Cn(join(i,"artifacts"));try{Yn(join(i,"artifacts"));}catch{}}let a=V();this.detectedOs=ad(),this.detectedSource=sd(a);let o=await Q(async()=>(this.cloudClient=new Ue(this.config.cloud),await this.cloudClient.initialize(),Es(this.cloudClient,this.config.cloud?.uploadStrategy,this.testRunId,this.startedAt,this.detectedSource)),Zn,"Cloud initialization");if(this.cloudRunId=o??null,o===void 0&&this.cloudClient?.switchToLocalMode("init_error"),this.cloudReceipt=Ps(this.cloudClient,this.config.cloud?.uploadStrategy,this.cloudRunId),this.activeReportMode!=="streaming"){let i=()=>{try{if(this.cloudClient?.isCloudMode()&&this.cloudRunId){let d=this.cloudClient.getAccessToken();d&&nr(this.cloudClient.getEndpoint(),d,this.cloudRunId,this.startedAt);}}catch{}};process.on("SIGTERM",i),process.on("SIGINT",i);}}catch{}}onTestBegin(e,r){try{e.annotations.push({type:ld,description:dd(this.apiConfig)});}catch{}}onTestEnd(e,r){try{let n=r,s=e.outcome(),a;s==="flaky"?a="flaky":s==="skipped"?a="skipped":a=hr(n.status);let o=n.startTime.toISOString(),i=new Date(n.startTime.getTime()+n.duration).toISOString(),d=e.tags?[...e.tags]:e.annotations.filter(I=>I.type==="tag").map(I=>I.description??""),l=Is(n.attachments,e.annotations,e.title,a==="skipped");(l.navigations.length>0||l.networkRequestsCount>0||l.apiCallsCount>0)&&(this.fixtureDataReceived=!0),l.navigations.length>0&&(this.navigationsCaptured=!0),!this.config.captureNavigation&&l.navigations.length>0&&(l={...l,navigations:[]});let c=null;if(a==="failed"||a==="flaky"){let M=(a==="flaky"?e.results.find(N=>N.status!=="passed")?.errors??[]:n.errors)[0];if(M){let N=zr(this.config.redactPatterns),j=M.location?.file??null,P=l.apiAssertions.find(ia=>ia.status==="failed")?.location??null,re=P!==null&&(j===null||resolve(j)!==resolve(e.location.file)),Sr=re?P.file:j,Qe=re?P.line:M.location?.line??null,fe=null;this.config.includeCodeSnippets&&Qe!==null&&Sr&&(fe=Ur(Sr,Qe,this.config.codeContextLines),fe&&(fe=N(fe))),c={message:N(se(M.message??"Unknown error")),line:Qe,code:fe,stack:this.config.includeStackTrace&&M.stack?N(se(M.stack)):null};}}let u=e.titlePath().filter(Boolean),f=relative(this.rootDir||".",e.location.file).replace(/\\/g,"/"),p=Ql(u),g=u.join(" > "),m=f,h=Zl(m,p,g),v=od(d,m,e),y=v!=="mobile"&&l.apiCallsCount>0?"api":v,b=s==="flaky",k=ed(e.results),A=hr(e.expectedStatus),E=hr(n.status),T=null;if(this.config.includeArtifacts&&a!=="skipped"&&n.attachments){let I=dirname(this.config.outputPath),M=u[u.length-1]??e.title;T=Sn(n.attachments,M,n.retry,I,this.runTimestamp||void 0,h);}let w=null;if(this.config.includeArtifacts&&a!=="skipped"&&n.attachments)try{let I=Hn(n.attachments,{updatingSnapshots:this.updatingSnapshots,failureMessage:n.errors.map(M=>M.message??"").join(`
4300
+ `)});if(I.length>0){let M=dirname(this.config.outputPath),N=u[u.length-1]??e.title;w=Rn(I,N,n.retry,M,this.runTimestamp||void 0,h);}}catch{}let S=this.config.includeActionSteps&&n.steps?ss(n.steps,n.startTime):null,x=id(e),C={titlePath:u,title:g,status:a,duration:n.duration,startedAt:o,completedAt:i,retryCount:e.results.length-1,retry:n.retry,tags:d,failure:c,specFile:f,navigations:l.navigations,testId:h,filePath:m,suiteName:p,testType:y,isFlaky:b,retryStatus:k,expectedStatus:A,actualStatus:E,artifacts:T,visual:w,apiAssertions:l.apiAssertions,actions:S,networkRequestsFile:l.networkRequestsFile,networkRequestsCount:l.networkRequestsCount,consoleLogsFile:l.consoleLogsFile,consoleLogsCount:l.consoleLogsCount,apiCallsFile:l.apiCallsFile,apiCallsCount:l.apiCallsCount,apiStatusMix:l.apiStatusMix,browser:x,os:this.detectedOs||void 0,source:this.detectedSource||void 0},_=l.consoleLogsFile&&l.consoleLogsCount>0?Y(l.consoleLogsFile):void 0,L=l.networkRequestsFile&&l.networkRequestsCount>0?Y(l.networkRequestsFile):void 0,z=l.apiCallsFile&&l.apiCallsCount>0?Y(l.apiCallsFile):void 0,ce=z?Qt(z):void 0,ue=!L&&z?er(z):void 0,pe=l.apiAssertions&&l.apiAssertions.length>0?tr(l.apiAssertions):void 0;this.cloudTestsBuffer.push({testId:h,title:g,status:a,duration:n.duration,suiteName:p,filePath:m,testType:y,isFlaky:b,startedAt:o,completedAt:i,tags:d,failure:c,retry:n.retry,retryCount:e.results.length-1,retryStatus:k,browser:x,os:this.detectedOs||void 0,source:this.detectedSource||void 0,...S&&S.length>0?{actions:S}:{},...l.navigations&&l.navigations.length>0?{navigations:l.navigations}:{},..._?{consoleLogs:_}:{},...L?{networkRequests:L}:ue?{networkRequests:ue}:{},...ce?{apiCalls:ce}:{},...pe?{protocolAssertions:pe}:{}});try{let I=this.config.cloud?.uploadStrategy;if(this.cloudClient?.isCloudMode()&&this.cloudRunId&&(I==="realtime"||I==="both")){let M=this.cloudClient.getAccessToken();M&&ys(this.cloudClient.getEndpoint(),M,this.cloudRunId,this.buildCloudTestPayload(C));}}catch{}if(this.activeReportMode==="streaming"&&this.streamingWriter){let I=et(m,u,e.id??"",n.retry),M=n.retry===e.results.length-1,N={id:I,navigations:l.navigations,apiAssertions:l.apiAssertions,actions:S??[],artifacts:T,visual:w,failureDiagnostic:c,hasNetworkFile:l.networkRequestsFile!==null,networkRequestsCount:l.networkRequestsCount,hasConsoleFile:l.consoleLogsFile!==null,consoleLogsCount:l.consoleLogsCount,hasApiCallsFile:l.apiCallsFile!==null,apiCallsCount:l.apiCallsCount,startedAt:o};this.streamingWriter.writeTestDetail(I,N,{networkRequestsFile:l.networkRequestsFile,consoleLogsFile:l.consoleLogsFile,apiCallsFile:l.apiCallsFile});let j={id:I,title:u[u.length-1]??e.title,titlePath:u,filePath:m,status:a,duration:n.duration,project:u[1]??"",retryIndex:n.retry,tags:d,hasNetworkData:l.networkRequestsCount>0,hasConsoleData:l.consoleLogsCount>0,hasArtifacts:T!==null,hasActionSteps:(S?.length??0)>0,visualCount:w?.length??0,visualFailedCount:w?.filter(P=>P.status==="failed").length??0,domChangeCount:w?.reduce((P,re)=>P+(re.domChangeCount??0),0)??0,...(()=>{let P=w?.find(re=>re.diff);return P?.diff?{visualDiffPath:P.diff}:{}})(),errorMessage:Xl(c?.message),networkCount:l.networkRequestsCount,consoleCount:l.consoleLogsCount,actionCount:S?.length??0,testType:y,apiCallCount:l.apiCallsCount,...l.apiStatusMix?{apiStatusMix:l.apiStatusMix}:{},assertionsPassed:We(l.apiAssertions,"passed"),assertionsFailed:We(l.apiAssertions,"failed"),...(()=>{let P=Fs(l.apiAssertions);return P?{firstFailedAssertion:P}:{}})(),isRetry:!M,retryGroupKey:tt(m,u,e.id??"")};if(this.testIndex.push(j),M)switch(this.summaryCounters.total++,a){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}this.upsertAggContribution(`${m}::${u.join(" > ")}`,Os(n.retry,z,l.apiAssertions,l.navigations,l.networkRequestsCount,l.consoleLogsCount,S)),(T||w)&&this.pendingArtifactEntries.push({testId:h,artifacts:T??{},visual:w,outputDir:dirname(this.config.outputPath)}),C.actions=null,C.apiAssertions=null,C.navigations=[],C.artifacts=null,C.failure=null,C.networkRequestsFile=null,C.consoleLogsFile=null,C.apiCallsFile=null;}this.activeReportMode!=="streaming"&&this.collectedTests.push(C),this.testCount++,this.checkMemoryPressure();}catch{}}async onEnd(e){try{let r=new Date().toISOString(),n=new Date(this.startedAt).getTime(),a=new Date(r).getTime()-n,o=this.activeReportMode==="streaming"?this.testIndex.some(d=>d.status!=="skipped"):this.collectedTests.some(d=>d.status!=="skipped");!this.fixtureDataReceived&&o&&process.stderr.write(`
2650
4301
  \u26A0 TestRelic: No fixture data received from any test.
2651
4302
  To enable network logs, video sync, and Test Navigation tracking, import the TestRelic fixture:
2652
4303
  import { test, expect } from '@testrelic/playwright-analytics/fixture';
2653
4304
  instead of:
2654
4305
  import { test, expect } from '@playwright/test';
2655
4306
 
2656
- `),this.config.captureNavigation?this.fixtureDataReceived&&!this.navigationsCaptured&&l&&process.stderr.write(`[testrelic] Test Navigation: 0 navigation events captured this run. Navigation is recorded automatically on page.goto()/clicks; if you expected a navigation graph, ensure your tests drive page navigations (and use the '@testrelic/playwright-analytics/fixture' import).
4307
+ `),this.config.captureNavigation?this.fixtureDataReceived&&!this.navigationsCaptured&&o&&process.stderr.write(`[testrelic] Test Navigation: 0 navigation events captured this run. Navigation is recorded automatically on page.goto()/clicks; if you expected a navigation graph, ensure your tests drive page navigations (and use the '@testrelic/playwright-analytics/fixture' import).
2657
4308
  `):process.stderr.write(`[testrelic] Test Navigation capture is disabled (captureNavigation: false). Remove the option or set it to true to capture the navigation graph.
2658
- `),await $(()=>Dr(),ot,"Artifact flush");let i=null;if(this.config.includeArtifacts&&this.runTimestamp)try{let d=dirname(this.config.outputPath);i=Ur(d,this.runTimestamp);let o=join(d,"artifact-manifest.json"),c=JSON.stringify(i),p=o+".tmp";writeFileSync(p,c,"utf-8"),renameSync(p,o);}catch{}if(this.activeReportMode==="streaming"&&this.streamingWriter)await $(()=>this.finalizeStreamingReport(r,a,e,i),it,"Report finalization");else {let d=this.buildTimeline(),o=new Map;for(let u of this.collectedTests){let f=o.get(u.testId);(!f||u.retry>f.retry)&&o.set(u.testId,u);}let c=Array.from(o.values()),p=Yr(c,d.length),h={schemaVersion:pe,testRunId:this.testRunId,startedAt:this.startedAt,completedAt:r,totalDuration:a,summary:p,ci:F(),metadata:this.config.metadata,timeline:d,shardRunIds:null};if(this.writeReport(h),await $(()=>et(h,this.config,i),ot,"HTML report write"),dt(p,this.config.outputPath,this.config.htmlReportPath,this.config.quiet),this.cloudClient){let u=this.collectArtifactEntries(),f=this.cloudTestsBuffer.slice();this.cloudTestsBuffer.length=0;let g=await $(()=>Et(this.cloudClient,this.config.cloud,this.config.cloud?.uploadStrategy,this.testRunId,this.cloudRunId,h,r,a,p,u,f),it,"Cloud upload");this.cloudReceipt=Pt(this.cloudReceipt,g,this.testRunId);}}}catch{}finally{await this.disposeCloudClient();}}async disposeCloudClient(){let e=this.cloudClient;e&&(this.cloudClient=null,await $(()=>e.dispose(),Gr,"Cloud client shutdown"));}collectArtifactEntries(){if(this.activeReportMode==="streaming")return this.pendingArtifactEntries;let e=dirname(this.config.outputPath),r=[];for(let n of this.collectedTests)n.artifacts&&r.push({testId:n.testId,artifacts:n.artifacts,outputDir:e});return r}async finalizeStreamingReport(e,r,n,s){if(!this.streamingWriter)return;this.summaryCounters={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0,interrupted:0,totalApiCalls:0,totalAssertions:0,totalNavigations:0,totalNetworkRequests:0,totalConsoleLogs:0,totalActionSteps:0};let a=new Map,l=new Map;for(let m of this.testIndex){let y=`${m.filePath}::${m.titlePath.join(" > ")}`,w=a.get(y);(w===void 0||m.retryIndex>w)&&a.set(y,m.retryIndex);}let i=0;for(let m of this.testIndex){let y=`${m.filePath}::${m.titlePath.join(" > ")}`,w=a.get(y);if(m.isRetry=m.retryIndex<w,m.isRetry)continue;switch(i++,this.summaryCounters.total++,m.status){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}let v=l.get(m.filePath);switch(v||(v={filePath:m.filePath,total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0},l.set(m.filePath,v)),v.total++,m.status){case "passed":v.passed++;break;case "failed":v.failed++;break;case "flaky":v.flaky++;break;case "skipped":v.skipped++;break;case "timedout":v.timedOut++;break}}let d=Array.from(l.values()),o=Bi();for(let m of this.streamingTestAgg.values()){o.volApiCalls+=m.apiCalls,o.volAssertions+=m.assertions,o.volNavigations+=m.navigations,o.volNetworkRequests+=m.networkRequests,o.volConsoleLogs+=m.consoleLogs,o.volActionSteps+=m.actionSteps;for(let y in m.methodCounts)o.methodCounts[y]=(o.methodCounts[y]??0)+m.methodCounts[y];o.statusRanges["2xx"]+=m.statusRanges["2xx"],o.statusRanges["3xx"]+=m.statusRanges["3xx"],o.statusRanges["4xx"]+=m.statusRanges["4xx"],o.statusRanges["5xx"]+=m.statusRanges["5xx"],o.statusRanges.error+=m.statusRanges.error,o.passedAssertions+=m.passedAssertions,o.failedAssertions+=m.failedAssertions;for(let y in m.actionCategories)o.actionCategoryCounts[y]=(o.actionCategoryCounts[y]??0)+m.actionCategories[y];for(let y of m.apiUrls){if(o.apiUrlSet.size>=Ie)break;o.apiUrlSet.add(y);}for(let y of m.navUrls){if(o.navUrlSet.size>=Ie)break;o.navUrlSet.add(y);}for(let y of m.responseTimes){if(o.responseTimes.length>=Ln)break;o.responseTimes.push(y);}}this.summaryCounters.totalApiCalls=o.volApiCalls,this.summaryCounters.totalAssertions=o.volAssertions,this.summaryCounters.totalNavigations=o.volNavigations,this.summaryCounters.totalNetworkRequests=o.volNetworkRequests,this.summaryCounters.totalConsoleLogs=o.volConsoleLogs,this.summaryCounters.totalActionSteps=o.volActionSteps,this.streamingWriter.writeIndex(this.testIndex),this.streamingWriter.writeCompactIndex(this.testIndex);let c={reporterTotal:this.summaryCounters.total,indexTotal:i,playwrightStatus:n.status,isValid:this.summaryCounters.total===i};c.isValid||process.stderr.write(`[testrelic] WARNING: Summary count mismatch \u2014 reporter: ${c.reporterTotal}, index: ${c.indexTotal}
2659
- `);let p={total:this.summaryCounters.total,passed:this.summaryCounters.passed,failed:this.summaryCounters.failed,flaky:this.summaryCounters.flaky,skipped:this.summaryCounters.skipped,timedout:this.summaryCounters.timedOut,totalApiCalls:this.summaryCounters.totalApiCalls,uniqueApiUrls:o.apiUrlSet.size,apiCallsByMethod:{...o.methodCounts},apiCallsByStatusRange:{"2xx":o.statusRanges["2xx"],"3xx":o.statusRanges["3xx"],"4xx":o.statusRanges["4xx"],"5xx":o.statusRanges["5xx"],error:o.statusRanges.error},apiResponseTime:Di(o.responseTimes),totalAssertions:this.summaryCounters.totalAssertions,passedAssertions:o.passedAssertions,failedAssertions:o.failedAssertions,totalNavigations:this.summaryCounters.totalNavigations,uniqueNavigationUrls:o.navUrlSet.size,totalTimelineSteps:i,totalActionSteps:this.summaryCounters.totalActionSteps,actionStepsByCategory:{...o.actionCategoryCounts}},h={testRunId:this.testRunId,startedAt:this.startedAt,completedAt:e,totalDuration:r,...this.summaryCounters,metadata:this.config.metadata,reportMode:"streaming",files:d,enrichedSummary:p,validation:c,writeErrors:this.streamingWriter.getWriteErrors(),cloudUpload:this.cloudReceipt};this.streamingWriter.writeSummary(h);let u={schemaVersion:"2.0",generatedAt:new Date().toISOString(),reportMode:"streaming",summaryFile:"summary.json",indexFile:"index.json",testsDir:"tests",artifactsDir:"artifacts",testCount:this.testIndex.length,totalSizeBytes:this.streamingWriter.computeTotalSize()};this.streamingWriter.writeManifest(u),await et(this.buildStreamingReport(h,p,e,r),this.config,s),dt(p,this.config.outputPath,this.config.htmlReportPath,this.config.quiet);let f=this.streamingWriter.getReportDir();process.stderr.write(`[testrelic] Streaming report written to ${f}
4309
+ `),await Q(()=>kn(),zt,"Artifact flush");let i=null;if(this.config.includeArtifacts&&this.runTimestamp)try{let d=dirname(this.config.outputPath);i=Gn(d,this.runTimestamp);let l=join(d,"artifact-manifest.json"),c=JSON.stringify(i),u=l+".tmp";writeFileSync(u,c,"utf-8"),renameSync(u,l);}catch{}if(this.activeReportMode==="streaming"&&this.streamingWriter)await Q(()=>this.finalizeStreamingReport(r,a,e,i),Ut,"Report finalization");else {let d=this.buildTimeline(),l=new Map;for(let p of this.collectedTests){let g=l.get(p.testId);(!g||p.retry>g.retry)&&l.set(p.testId,p);}let c=Array.from(l.values()),u=ns(c,d.length),f={schemaVersion:Ce,testRunId:this.testRunId,startedAt:this.startedAt,completedAt:r,totalDuration:a,summary:u,ci:V(),metadata:this.config.metadata,timeline:d,shardRunIds:null};if(this.writeReport(f),await Q(()=>Mt(f,this.config,i),zt,"HTML report write"),Vt(u,this.config.outputPath,this.config.htmlReportPath,this.config.quiet),this.cloudClient){let p=this.collectArtifactEntries(),g=this.cloudTestsBuffer.slice();this.cloudTestsBuffer.length=0;let m=await Q(()=>pr(this.cloudClient,this.config.cloud,this.config.cloud?.uploadStrategy,this.testRunId,this.cloudRunId,f,r,a,u,p,g),Ut,"Cloud upload");this.cloudReceipt=fr(this.cloudReceipt,m,this.testRunId);}}}catch{}finally{await this.disposeCloudClient();}}async disposeCloudClient(){let e=this.cloudClient;e&&(this.cloudClient=null,await Q(()=>e.dispose(),Qn,"Cloud client shutdown"));}collectArtifactEntries(){if(this.activeReportMode==="streaming")return this.pendingArtifactEntries;let e=dirname(this.config.outputPath),r=[];for(let n of this.collectedTests)(n.artifacts||n.visual)&&r.push({testId:n.testId,artifacts:n.artifacts??{},visual:n.visual,outputDir:e});return r}async finalizeStreamingReport(e,r,n,s){if(!this.streamingWriter)return;this.summaryCounters={total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0,interrupted:0,totalApiCalls:0,totalAssertions:0,totalNavigations:0,totalNetworkRequests:0,totalConsoleLogs:0,totalActionSteps:0,visualChecks:0,visualFailures:0,visualDomChanges:0};let a=new Map,o=new Map;for(let h of this.testIndex){let v=`${h.filePath}::${h.titlePath.join(" > ")}`,y=a.get(v);(y===void 0||h.retryIndex>y)&&a.set(v,h.retryIndex);}let i=0;for(let h of this.testIndex){let v=`${h.filePath}::${h.titlePath.join(" > ")}`,y=a.get(v);if(h.isRetry=h.retryIndex<y,h.isRetry)continue;switch(i++,this.summaryCounters.total++,h.status){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}this.summaryCounters.visualChecks+=h.visualCount??0,this.summaryCounters.visualFailures+=h.visualFailedCount??0,this.summaryCounters.visualDomChanges+=h.domChangeCount??0;let b=o.get(h.filePath);switch(b||(b={filePath:h.filePath,total:0,passed:0,failed:0,flaky:0,skipped:0,timedOut:0},o.set(h.filePath,b)),b.total++,h.status){case "passed":b.passed++;break;case "failed":b.failed++;break;case "flaky":b.flaky++;break;case "skipped":b.skipped++;break;case "timedout":b.timedOut++;break}}let d=Array.from(o.values()),l=nd();for(let h of this.streamingTestAgg.values()){l.volApiCalls+=h.apiCalls,l.volAssertions+=h.assertions,l.volNavigations+=h.navigations,l.volNetworkRequests+=h.networkRequests,l.volConsoleLogs+=h.consoleLogs,l.volActionSteps+=h.actionSteps;for(let v in h.methodCounts)l.methodCounts[v]=(l.methodCounts[v]??0)+h.methodCounts[v];l.statusRanges["2xx"]+=h.statusRanges["2xx"],l.statusRanges["3xx"]+=h.statusRanges["3xx"],l.statusRanges["4xx"]+=h.statusRanges["4xx"],l.statusRanges["5xx"]+=h.statusRanges["5xx"],l.statusRanges.error+=h.statusRanges.error,l.passedAssertions+=h.passedAssertions,l.failedAssertions+=h.failedAssertions;for(let v in h.actionCategories)l.actionCategoryCounts[v]=(l.actionCategoryCounts[v]??0)+h.actionCategories[v];for(let v of h.apiUrls){if(l.apiUrlSet.size>=Ge)break;l.apiUrlSet.add(v);}for(let v of h.navUrls){if(l.navUrlSet.size>=Ge)break;l.navUrlSet.add(v);}for(let v of h.responseTimes){if(l.responseTimes.length>=Us)break;l.responseTimes.push(v);}}this.summaryCounters.totalApiCalls=l.volApiCalls,this.summaryCounters.totalAssertions=l.volAssertions,this.summaryCounters.totalNavigations=l.volNavigations,this.summaryCounters.totalNetworkRequests=l.volNetworkRequests,this.summaryCounters.totalConsoleLogs=l.volConsoleLogs,this.summaryCounters.totalActionSteps=l.volActionSteps,this.streamingWriter.writeIndex(this.testIndex),this.streamingWriter.writeCompactIndex(this.testIndex);let c={reporterTotal:this.summaryCounters.total,indexTotal:i,playwrightStatus:n.status,isValid:this.summaryCounters.total===i};c.isValid||process.stderr.write(`[testrelic] WARNING: Summary count mismatch \u2014 reporter: ${c.reporterTotal}, index: ${c.indexTotal}
4310
+ `);let u={total:this.summaryCounters.total,passed:this.summaryCounters.passed,failed:this.summaryCounters.failed,flaky:this.summaryCounters.flaky,skipped:this.summaryCounters.skipped,timedout:this.summaryCounters.timedOut,totalApiCalls:this.summaryCounters.totalApiCalls,uniqueApiUrls:l.apiUrlSet.size,apiCallsByMethod:{...l.methodCounts},apiCallsByStatusRange:{"2xx":l.statusRanges["2xx"],"3xx":l.statusRanges["3xx"],"4xx":l.statusRanges["4xx"],"5xx":l.statusRanges["5xx"],error:l.statusRanges.error},apiResponseTime:rd(l.responseTimes),totalAssertions:this.summaryCounters.totalAssertions,passedAssertions:l.passedAssertions,failedAssertions:l.failedAssertions,totalNavigations:this.summaryCounters.totalNavigations,uniqueNavigationUrls:l.navUrlSet.size,totalTimelineSteps:i,totalActionSteps:this.summaryCounters.totalActionSteps,actionStepsByCategory:{...l.actionCategoryCounts},visualChecks:this.summaryCounters.visualChecks,visualFailures:this.summaryCounters.visualFailures,visualDomChanges:this.summaryCounters.visualDomChanges},f={testRunId:this.testRunId,startedAt:this.startedAt,completedAt:e,totalDuration:r,...this.summaryCounters,metadata:this.config.metadata,reportMode:"streaming",files:d,enrichedSummary:u,validation:c,writeErrors:this.streamingWriter.getWriteErrors(),cloudUpload:this.cloudReceipt};this.streamingWriter.writeSummary(f);let p={schemaVersion:"2.0",generatedAt:new Date().toISOString(),reportMode:"streaming",summaryFile:"summary.json",indexFile:"index.json",testsDir:"tests",artifactsDir:"artifacts",testCount:this.testIndex.length,totalSizeBytes:this.streamingWriter.computeTotalSize()};this.streamingWriter.writeManifest(p),await Mt(this.buildStreamingReport(f,u,e,r),this.config,s),Vt(u,this.config.outputPath,this.config.htmlReportPath,this.config.quiet);let g=this.streamingWriter.getReportDir();process.stderr.write(`[testrelic] Streaming report written to ${g}
2660
4311
  [testrelic] ${this.testIndex.length} test detail files on disk
2661
- [testrelic] View report: npx testrelic serve ${f}
4312
+ [testrelic] View report: npx testrelic serve ${g}
2662
4313
  `),this.streamingWriter.getWriteErrors().length>0&&process.stderr.write(`[testrelic] WARNING: ${this.streamingWriter.getWriteErrors().length} write error(s) during streaming
2663
- `);let g=this.streamingWriter;try{if(this.cloudClient){let m=this.buildStreamingReport(h,p,e,r),y=this.collectArtifactEntries(),w=this.cloudTestsBuffer.slice();this.cloudTestsBuffer.length=0;let v=await Et(this.cloudClient,this.config.cloud,this.config.cloud?.uploadStrategy,this.testRunId,this.cloudRunId,m,e,r,p,y,w);this.cloudReceipt=Pt(this.cloudReceipt,v,this.testRunId);let k={...h,cloudUpload:this.cloudReceipt};try{g.writeSummary(k);}catch{}}}finally{g.dispose();}this.testIndex.length=0,this.collectedTests.length=0,this.pendingArtifactEntries.length=0,this.cloudTestsBuffer.length=0,this.streamingWriter=null,this.cloudClient=null;}buildStreamingReport(e,r,n,s){return {schemaVersion:pe,testRunId:this.testRunId,startedAt:this.startedAt,completedAt:n,totalDuration:s,summary:r,ci:F(),metadata:this.config.metadata,timeline:[],shardRunIds:null}}printsToStdio(){return false}checkMemoryPressure(){try{let e=process.memoryUsage(),r=e.heapUsed/1024/1024;if(r<200)return;let n=e.heapUsed/e.heapTotal;n>.9&&this.activeReportMode==="embedded"?(process.stderr.write(`[testrelic] WARNING: High memory (${Math.round(r)} MB). Auto-switching to streaming mode.
4314
+ `);let m=this.streamingWriter;try{if(this.cloudClient){let h=this.buildStreamingReport(f,u,e,r),v=this.collectArtifactEntries(),y=this.cloudTestsBuffer.slice();this.cloudTestsBuffer.length=0;let b=await pr(this.cloudClient,this.config.cloud,this.config.cloud?.uploadStrategy,this.testRunId,this.cloudRunId,h,e,r,u,v,y);this.cloudReceipt=fr(this.cloudReceipt,b,this.testRunId);let k={...f,cloudUpload:this.cloudReceipt};try{m.writeSummary(k);}catch{}}}finally{m.dispose();}this.testIndex.length=0,this.collectedTests.length=0,this.pendingArtifactEntries.length=0,this.cloudTestsBuffer.length=0,this.streamingWriter=null,this.cloudClient=null;}buildStreamingReport(e,r,n,s){return {schemaVersion:Ce,testRunId:this.testRunId,startedAt:this.startedAt,completedAt:n,totalDuration:s,summary:r,ci:V(),metadata:this.config.metadata,timeline:[],shardRunIds:null}}printsToStdio(){return false}checkMemoryPressure(){try{let e=process.memoryUsage(),r=e.heapUsed/1024/1024;if(r<200)return;let n=e.heapUsed/e.heapTotal;n>.9&&this.activeReportMode==="embedded"?(process.stderr.write(`[testrelic] WARNING: High memory (${Math.round(r)} MB). Auto-switching to streaming mode.
2664
4315
  `),this.switchToStreamingMode()):n>.8&&r>=500&&!this.memoryPressureWarned&&(this.memoryPressureWarned=!0,process.stderr.write(`[testrelic] WARNING: Memory pressure detected (${Math.round(r)} MB used).
2665
- `));}catch{}}switchToStreamingMode(){try{if(this.activeReportMode==="streaming")return;this.activeReportMode="streaming";let e=dirname(this.config.outputPath);this.streamingWriter=new se(e);for(let r of this.collectedTests){let n=De(r.filePath,r.titlePath,r.testId,r.retry),s=r.apiCallsFile&&r.apiCallsCount>0?U(r.apiCallsFile):void 0,a={id:n,navigations:r.navigations??[],apiAssertions:r.apiAssertions??[],actions:r.actions??[],artifacts:r.artifacts,failureDiagnostic:r.failure,hasNetworkFile:r.networkRequestsFile!==null,networkRequestsCount:r.networkRequestsCount,hasConsoleFile:r.consoleLogsFile!==null,consoleLogsCount:r.consoleLogsCount,hasApiCallsFile:r.apiCallsFile!==null,apiCallsCount:r.apiCallsCount,startedAt:r.startedAt};this.streamingWriter.writeTestDetail(n,a,{networkRequestsFile:r.networkRequestsFile,consoleLogsFile:r.consoleLogsFile,apiCallsFile:r.apiCallsFile});let l={id:n,title:r.titlePath[r.titlePath.length-1]??r.title,titlePath:r.titlePath,filePath:r.filePath,status:r.status,duration:r.duration,project:r.titlePath[1]??"",retryIndex:r.retry,tags:r.tags,hasNetworkData:r.networkRequestsCount>0,hasConsoleData:r.consoleLogsCount>0,hasArtifacts:r.artifacts!==null,hasActionSteps:(r.actions?.length??0)>0,errorMessage:r.failure?.message?.split(`
2666
- `)[0]??null,networkCount:r.networkRequestsCount,consoleCount:r.consoleLogsCount,actionCount:r.actions?.length??0,isRetry:!1,retryGroupKey:Be(r.filePath,r.titlePath,r.testId)};switch(this.testIndex.push(l),this.summaryCounters.total++,r.status){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}this.upsertAggContribution(`${r.filePath}::${r.titlePath.join(" > ")}`,In(r.retry,s,r.apiAssertions??[],r.navigations??[],r.networkRequestsCount,r.consoleLogsCount,r.actions??null));}this.collectedTests.length=0;}catch{}}buildTimeline(){let e=this.collectedTests.map(r=>({titlePath:r.titlePath,title:r.title,status:r.status,duration:r.duration,startedAt:r.startedAt,completedAt:r.completedAt,retryCount:r.retryCount,retry:r.retry,tags:r.tags,failure:r.failure,specFile:r.specFile,navigations:r.navigations,apiCalls:null,apiAssertions:r.apiAssertions,testId:r.testId,filePath:r.filePath,suiteName:r.suiteName,testType:r.testType,isFlaky:r.isFlaky,retryStatus:r.retryStatus,expectedStatus:r.expectedStatus,actualStatus:r.actualStatus,artifacts:r.artifacts,networkRequests:null,actions:r.actions,consoleLogs:null}));return Jr(e,{navigationTypes:this.config.navigationTypes})}readJsonlFile(e){if(!e)return null;let r=U(e);return r.length>0?r:null}toTestResult(e,r){let n=null,s=null,a=null;return r?.loadFiles&&(n=this.readJsonlFile(e.networkRequestsFile),s=this.readJsonlFile(e.consoleLogsFile),a=this.readJsonlFile(e.apiCallsFile)),{title:e.title,status:e.status,duration:e.duration,startedAt:e.startedAt,completedAt:e.completedAt,retryCount:e.retryCount,tags:e.tags,failure:e.failure,testId:e.testId,filePath:e.filePath,suiteName:e.suiteName,testType:e.testType,isFlaky:e.isFlaky,retryStatus:e.retryStatus,expectedStatus:e.expectedStatus,actualStatus:e.actualStatus,artifacts:e.artifacts,networkRequests:n,apiCalls:a,apiAssertions:e.apiAssertions,actions:e.actions??null,consoleLogs:s}}buildCloudTestPayload(e){let r={title:e.title,status:e.status,duration:e.duration,startedAt:e.startedAt,completedAt:e.completedAt,retry:e.retry,retryCount:e.retryCount,retryStatus:e.retryStatus,tags:e.tags,failure:e.failure,testId:e.testId,filePath:e.filePath,suiteName:e.suiteName,testType:e.testType,isFlaky:e.isFlaky,expectedStatus:e.expectedStatus,actualStatus:e.actualStatus,artifacts:e.artifacts,actions:e.actions??[],navigations:e.navigations??[],steps:e.actions??[],browser:e.browser,os:e.os,source:e.source};if(e.apiAssertions&&e.apiAssertions.length>0&&(r.protocolAssertions=wt(e.apiAssertions)),e.consoleLogsFile&&(r.consoleLogs=U(e.consoleLogsFile)),e.networkRequestsFile&&(r.networkRequests=U(e.networkRequestsFile)),e.apiCallsFile){let n=U(e.apiCallsFile);r.apiCalls=vt(n),(!r.networkRequests||r.networkRequests.length===0)&&(r.networkRequests=bt(n));}return r}writeReport(e){try{let r=JSON.stringify(e),n=this.config.outputPath,s=dirname(n);mkdirSync(s,{recursive:!0});let a=n+".tmp";writeFileSync(a,r,"utf-8"),renameSync(a,n);}catch(r){process.stderr.write(`[testrelic] Failed to write report: ${r instanceof Error?r.message:String(r)}
2667
- `);}}};var Ki=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function Ji(t){let e=t.toLowerCase();return Ki.some(r=>e.includes(r))}var Le=class t{constructor(e,r){this.page=e;this.records=[];this.listeners=[];this.navCounters=[];this.pendingRequests=new Map;this.networkWriter=null;this.consoleWriter=null;this.pendingBodyReads=[];this.requestIdCounter=0;this.networkRequestCount=0;this.consoleLogCount=0;this.includeNetworkStats=r?.includeNetworkStats??true,this.origGoto=e.goto.bind(e),this.origGoBack=e.goBack.bind(e),this.origGoForward=e.goForward.bind(e),this.origReload=e.reload.bind(e);try{this.includeNetworkStats&&(this.networkWriter=new P("network")),this.consoleWriter=new P("console");}catch{}this.interceptMethods(),this.attachListeners();}async init(){await this.injectSPADetection();}async finalizeCapturedRequests(){await Promise.allSettled(this.pendingBodyReads),this.pendingBodyReads=[];for(let[,e]of this.pendingRequests)this.networkWriter&&(this.networkWriter.append({url:e.url,method:e.method,resourceType:e.resourceType,statusCode:0,responseTimeMs:Date.now()-e.startTimeMs,startedAt:e.startedAt,requestHeaders:e.headers,requestBody:e.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:e.postDataTruncated,responseBodyTruncated:false,isBinary:false,error:"incomplete"}),this.networkRequestCount++);this.pendingRequests.clear(),this.networkWriter?.close(),this.consoleWriter?.close();}static mapConsoleType(e){switch(e){case "log":return "log";case "warning":return "warn";case "error":return "error";case "info":return "info";case "debug":return "debug";default:return "log"}}async getFileData(){this.assembleNetworkStats();let e=this.records.map(r=>({url:r.url,navigationType:r.navigationType,timestamp:r.timestamp,domContentLoadedAt:r.domContentLoadedAt,networkIdleAt:r.networkIdleAt,networkStats:r.networkStats}));return this.includeNetworkStats?await this.finalizeCapturedRequests():this.consoleWriter?.close(),{navigations:e,networkRequestsFile:this.networkWriter?.getCount()?this.networkWriter.getPath():null,networkRequestsCount:this.networkWriter?.getCount()??0,consoleLogsFile:this.consoleWriter?.getCount()?this.consoleWriter.getPath():null,consoleLogsCount:this.consoleWriter?.getCount()??0}}async flushLegacyAnnotations(e){}dispose(){this.page.goto=this.origGoto,this.page.goBack=this.origGoBack,this.page.goForward=this.origGoForward,this.page.reload=this.origReload;for(let{event:e,handler:r}of this.listeners)this.page.off(e,r);this.listeners=[],this.records=[],this.pendingRequests.clear(),this.pendingBodyReads=[],this.networkWriter?.close(),this.consoleWriter?.close();}getRecords(){return this.assembleNetworkStats(),this.records}interceptMethods(){let e=this,r=this.page;r.goto=async function(n,s){return e.recordNavigation(n,"goto"),e.origGoto(n,s)},r.goBack=async function(n){let s=await e.origGoBack(n);return e.recordNavigation(r.url(),"back"),s},r.goForward=async function(n){let s=await e.origGoForward(n);return e.recordNavigation(r.url(),"forward"),s},r.reload=async function(n){return e.recordNavigation(r.url(),"refresh"),e.origReload(n)};}attachListeners(){let e=()=>{this.lastDomContentLoaded=new Date().toISOString(),this.records.length>0&&(this.records[this.records.length-1].domContentLoadedAt=this.lastDomContentLoaded);};this.page.on("domcontentloaded",e),this.listeners.push({event:"domcontentloaded",handler:e});let r=s=>{try{let a=s;if(typeof a.parentFrame=="function"&&a.parentFrame()!==null)return;let l=a.url(),i=this.records[this.records.length-1];if(i&&Date.now()-new Date(i.timestamp).getTime()<50&&i.url===l)return;this.recordNavigation(l,"navigation");}catch{}};this.page.on("framenavigated",r),this.listeners.push({event:"framenavigated",handler:r});let n=s=>{try{let a=s,l=a.type(),i=a.text();if(l==="debug"&&i.startsWith("__testrelic_nav:")){try{let d=JSON.parse(i.slice(16));d.type&&d.url&&this.recordNavigation(d.url,d.type);}catch{}return}{let d=null;try{let c=a.location();c&&c.url&&(d=`${c.url}:${c.lineNumber}:${c.columnNumber}`);}catch{}let o=t.mapConsoleType(l);this.consoleWriter&&(this.consoleWriter.append({level:o,text:i,timestamp:new Date().toISOString(),location:d}),this.consoleLogCount++);}}catch{}};if(this.page.on("console",n),this.listeners.push({event:"console",handler:n}),this.includeNetworkStats){let s=i=>{let d=this.records.length-1,o=this.counterForNav(d);o&&o.totalRequests++;try{let c=i,p=String(this.requestIdCounter++),h=c.postData()??null,u={url:c.url(),method:c.method(),resourceType:this.mapResourceType(c.resourceType()),headers:c.headers(),postData:h,postDataTruncated:!1,startedAt:new Date().toISOString(),startTimeMs:Date.now(),navIndex:d};this.pendingRequests.set(p,u),i.__testrelic_id=p;}catch{}};this.page.on("request",s),this.listeners.push({event:"request",handler:s});let a=i=>{try{let d=i,o=d.request().__testrelic_id,c=o?this.pendingRequests.get(o):void 0,p=this.counterForNav(c?c.navIndex:this.records.length-1);if(p){let w=d.status();w>=400&&(p.failedRequests++,p.failedRequestUrls.push(w+" "+d.url()));let v=d.headers()["content-length"];v&&(p.totalBytes+=parseInt(v,10)||0);let k=d.request().resourceType(),C=this.mapResourceType(k);p.byType[C]++;}if(!o||!c)return;this.pendingRequests.delete(o);let h=Date.now()-c.startTimeMs,u=d.headers(),f=u["content-type"]??null,g=parseInt(u["content-length"]??"0",10)||0,m=f?!Ji(f):!1,y=(async()=>{let w=null;if(!m)try{w=(await d.body()).toString("utf-8");}catch{}let v={url:c.url,method:c.method,resourceType:c.resourceType,statusCode:d.status(),responseTimeMs:h,startedAt:c.startedAt,requestHeaders:c.headers,requestBody:c.postData,responseBody:w,responseHeaders:u,contentType:f,responseSize:g,requestBodyTruncated:c.postDataTruncated,responseBodyTruncated:!1,isBinary:m,error:null};this.networkWriter&&(this.networkWriter.append(v),this.networkRequestCount++);})();this.pendingBodyReads.push(y);}catch{}};this.page.on("response",a),this.listeners.push({event:"response",handler:a});let l=i=>{let d=i.__testrelic_id,o=d?this.pendingRequests.get(d):void 0,c=this.counterForNav(o?o.navIndex:this.records.length-1);if(c){c.failedRequests++;try{let p=i;c.failedRequestUrls.push("ERR "+p.url());}catch{}}try{let p=i;if(!d||!o)return;this.pendingRequests.delete(d);let h={url:o.url,method:o.method,resourceType:o.resourceType,statusCode:0,responseTimeMs:Date.now()-o.startTimeMs,startedAt:o.startedAt,requestHeaders:o.headers,requestBody:o.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:o.postDataTruncated,responseBodyTruncated:!1,isBinary:!1,error:p.failure()?.errorText??"Unknown error"};this.networkWriter&&(this.networkWriter.append(h),this.networkRequestCount++);}catch{}};this.page.on("requestfailed",l),this.listeners.push({event:"requestfailed",handler:l});}}async injectSPADetection(){try{await this.page.addInitScript(()=>{let e=history.pushState.bind(history),r=history.replaceState.bind(history);history.pushState=function(...n){e(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_route",url:location.href}));},history.replaceState=function(...n){r(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_replace",url:location.href}));},window.addEventListener("popstate",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"popstate",url:location.href}));}),window.addEventListener("hashchange",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"hash_change",url:location.href}));});});}catch{}}recordNavigation(e,r){this.records.push({url:e,navigationType:r,timestamp:new Date().toISOString()}),this.includeNetworkStats&&this.navCounters.push(this.createNetworkCounter());}createNetworkCounter(){return {totalRequests:0,failedRequests:0,failedRequestUrls:[],totalBytes:0,byType:{xhr:0,document:0,script:0,stylesheet:0,image:0,font:0,other:0}}}counterForNav(e){return e>=0?this.navCounters[e]:void 0}assembleNetworkStats(){if(this.includeNetworkStats)for(let e=0;e<this.records.length;e++){let r=this.navCounters[e];r&&(this.records[e].networkStats={totalRequests:r.totalRequests,failedRequests:r.failedRequests,failedRequestUrls:[...r.failedRequestUrls],totalBytes:r.totalBytes,byType:{...r.byType}});}}mapResourceType(e){switch(e){case "xhr":case "fetch":return "xhr";case "document":return "document";case "script":return "script";case "stylesheet":return "stylesheet";case "image":return "image";case "font":return "font";default:return "other"}}};var Bt=Symbol.for("__testrelic_call_id"),Zi="__testrelic_api_assertions",W=new WeakMap,Q=class{constructor(){this.assertions=[];this.currentCallId=null;}recordAssertion(e){this.assertions.push(e);}getAssertions(){return this.assertions}setCurrentCallId(e){this.currentCallId=e;}getCurrentCallId(){return this.currentCallId}getData(){return [...this.assertions]}flushLegacyAnnotations(e){this.assertions.length!==0&&e.annotations.push({type:Zi,description:JSON.stringify(this.assertions)});}dispose(){this.assertions=[],this.currentCallId=null;}},En=new Map;function Xi(t){let e=En.get(t);if(e)return e;try{let r=t.startsWith("file://")?fileURLToPath(t):t,s=readFileSync(r,"utf-8").split(`
2668
- `);return En.set(t,s),s}catch{return null}}function eo(){let e=new Error().stack;if(!e)return null;let r=e.split(`
2669
- `);for(let n of r){if(n.includes("/assertion-tracker.")||n.includes("/api-request-tracker.")||n.includes("node:internal")||n.includes(" at new Error")||n.includes(" at captureAssertionLocation")||n.includes(" at expectWrapper")||n.includes(" at wrappedMatcher"))continue;let s=n.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/);if(s)return {file:s[1],line:parseInt(s[2],10),column:parseInt(s[3],10)}}return null}function to(t,e){if(!t)return "custom";let r=t.toLowerCase();return r.includes(".status()")&&!r.includes(".statustext()")?r.includes(".ok()")?"statusOk":"status":r.includes(".ok()")?"statusOk":r.includes(".headers()")||r.includes(".headersarray()")?"header":r.includes("tomatchobject")||r.includes("toequal")?"bodyMatch":r.includes("tocontain")||r.includes("tostringcontaining")?"bodyContains":r.includes(".json()")||r.includes(".text()")?"bodyField":"custom"}function ro(t,e,r){let n=function(a,...l){let i=t(),d=e();if(!i||!d)return r(a,...l);let o=null;if(a!=null&&typeof a=="object"){let u=a[Bt];typeof u=="string"&&(o=u);}if(!o&&a!==null&&a!==void 0&&typeof a=="object"&&(o=W.get(a)??null),o||(o=d.getCallIdForValue(a)),o||(o=i.getCurrentCallId()),!o)return r(a,...l);let c=eo(),p;if(c){let u=Xi(c.file);u&&c.line>0&&c.line<=u.length&&(p=u[c.line-1].trim());}let h=r(a,...l);return o?Dn(h,{tracker:i,callId:o,received:a,location:c??{file:"unknown",line:0},expression:p}):h};for(let s of Object.keys(r))n[s]=r[s];return n}var Nn=null,Fn=null;function Z(t,e){Nn=t,Fn=e;}function Ee(t){return ro(()=>Nn,()=>Fn,t)}function Dn(t,e){return new Proxy(t,{get(r,n,s){let a=Reflect.get(r,n,s);return typeof a!="function"||typeof n=="symbol"?a:n==="not"?Dn(a,e):function(...i){let d=i[0],o="passed",c=e.received,p=null;try{let h=a.apply(this??r,i);return h&&typeof h=="object"&&typeof h.then=="function"?h.then(()=>{Dt(e,n,d,c,"passed");},u=>{throw Dt(e,n,d,c,"failed"),u}):h}catch(h){if(o="failed",p=h,h instanceof Error&&h.message){let u=h.message.match(/Received:\s*(.+)/);u&&(c=u[1]);}throw h}finally{(p!==null||o==="passed")&&Dt(e,n,d,c,o);}}}})}function Dt(t,e,r,n,s){let a=to(t.expression,t.received),l={callId:t.callId,type:a,expected:Pn(r),actual:Pn(n),status:s,location:t.location,expression:t.expression};t.tracker.recordAssertion(l);}function Pn(t){if(t==null||typeof t=="string"||typeof t=="number"||typeof t=="boolean")return t;try{return JSON.stringify(t),t}catch{return String(t)}}var Bn="[REDACTED]";function qt(t,e){if(t===null||e.length===0)return t;let r=new Set(e.map(s=>s.toLowerCase())),n={};for(let s of Object.keys(t))Object.hasOwn(t,s)&&(n[s]=r.has(s.toLowerCase())?Bn:t[s]);return n}function Pe(t,e){if(t===null||e.length===0)return t;let r;try{r=JSON.parse(t);}catch{return t}if(typeof r!="object"||r===null)return t;let n=new Set(e.map(a=>a.toLowerCase())),s=Ot(r,n);return JSON.stringify(s)}function Ot(t,e){if(Array.isArray(t))return t.map(r=>Ot(r,e));if(typeof t=="object"&&t!==null){let r={};for(let n of Object.keys(t)){if(!Object.hasOwn(t,n))continue;let s=t[n];e.has(n.toLowerCase())?r[n]=Bn:r[n]=Ot(s,e);}return r}return t}var no=new Set(["access_token","api_key","token","sig"]);function Ut(t){if(!t)return t;let e;try{e=new URL(t);}catch{return t}let r=false;for(let n of [...e.searchParams.keys()])no.has(n.toLowerCase())&&(e.searchParams.delete(n),r=true);return r?e.toString():t}function qn(t,e,r){if(r.length>0){for(let n of r)if(On(t,n))return false}if(e.length>0){for(let n of e)if(On(t,n))return true;return false}return true}function On(t,e){try{return e instanceof RegExp?e.test(t):so(e).test(t)}catch{return console.warn(`[testrelic] Invalid URL filter pattern: ${String(e)}`),false}}function so(t){let e="",r=0;for(;r<t.length;){let n=t[r];n==="*"&&t[r+1]==="*"?(e+=".*",r+=2,t[r]==="/"&&r++):n==="*"?(e+="[^/]*",r++):n==="?"?(e+="[^/]",r++):".+^${}()|[]\\".includes(n)?(e+="\\"+n,r++):(e+=n,r++);}return new RegExp(e)}var ao=["get","post","put","patch","delete","head","fetch"],io=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function oo(t){let e=t.toLowerCase();return io.some(r=>e.includes(r))}function lo(t){if(!t)return null;if(t.data!==void 0&&t.data!==null){let e=t.data;return typeof e=="string"?e:Buffer.isBuffer(e)?e.toString("base64"):JSON.stringify(e)}if(t.form!==void 0&&t.form!==null)return JSON.stringify(t.form);if(t.multipart!==void 0&&t.multipart!==null){let e=t.multipart,r={};for(let[n,s]of Object.entries(e))typeof s=="string"||typeof s=="number"||typeof s=="boolean"?r[n]=String(s):s&&typeof s=="object"&&"name"in s?r[n]=`[file: ${s.name}]`:r[n]="[binary]";return JSON.stringify(r)}return null}function Un(t){return t?"\u2022\u2022\u2022\u2022":""}function co(t){if(!t)return [];let e;for(let[n,s]of Object.entries(t))if(n.toLowerCase()==="cookie"){e=s;break}if(!e)return [];let r=[];for(let n of e.split(";")){let s=n.trim();if(!s)continue;let a=s.indexOf("="),l=a===-1?s:s.slice(0,a).trim(),i=a===-1?"":s.slice(a+1).trim();l&&r.push({name:l,value:Un(i)});}return r}function uo(t){let e=[];for(let r of t)for(let n of r.split(/,(?=\s*[A-Za-z0-9_.\-]+=)/)){let s=n.trim();if(!s)continue;let a=s.split(";"),l=a[0].indexOf("=");if(l===-1)continue;let i=a[0].slice(0,l).trim(),d=a[0].slice(l+1).trim(),o=a.slice(1).map(c=>c.trim()).filter(Boolean).join(" \xB7 ")||void 0;i&&e.push({name:i,value:Un(d),attributes:o});}return e}var Ne=class Ne{constructor(e,r,n){this.originals=new Map;this.apiCallWriter=null;this.callCounter=0;this.apiCallCount=0;this.disposed=false;this._lastCallId=null;this.primitiveCallIds=new Map;this.context=e,this.assertionTracker=r??null,this.apiConfig=n??Ne.DEFAULT_API_CONFIG;try{this.apiCallWriter=new P("api-calls");}catch{}}get lastCallId(){return this._lastCallId}getCallIdForValue(e){return e!=null&&typeof e=="object"?W.get(e)??null:this.primitiveCallIds.get(e)??null}intercept(){for(let r of ao){let n=this.context[r].bind(this.context);this.originals.set(r,n),this.context[r]=this.createWrapper(r,n);}let e=this.context.dispose.bind(this.context);this.originals.set("dispose",e),this.context.dispose=async r=>(this.disposed=true,e(r));}getFileData(){return this.apiCallWriter?.close(),{apiCallsFile:this.apiCallWriter?.getCount()?this.apiCallWriter.getPath():null,apiCallsCount:this.apiCallWriter?.getCount()??0}}flushLegacyAnnotations(e){}dispose(){for(let[e,r]of this.originals)this.context[e]=r;this.originals.clear(),this.apiCallWriter?.close(),this.callCounter=0,this.apiCallCount=0,this._lastCallId=null,this.primitiveCallIds.clear();}get isDisposed(){return this.disposed}getCapturedCallCount(){return this.apiCallCount}tagResponseMethods(e,r){let n=this,s=e.headers.bind(e);e.headers=function(){let u=s();return W.set(u,r),u};let a=e.headersArray.bind(e);e.headersArray=function(){let u=a();return W.set(u,r),u};let l=e.json.bind(e);e.json=async function(){let u=await l();return u!=null&&typeof u=="object"&&W.set(u,r),u};let i=e.status.bind(e);e.status=function(){let u=i();return n.primitiveCallIds.set(u,r),u};let d=e.statusText.bind(e);e.statusText=function(){let u=d();return n.primitiveCallIds.set(u,r),u};let o=e.ok.bind(e);e.ok=function(){let u=o();return n.primitiveCallIds.set(u,r),u};let c=e.text.bind(e);e.text=async function(){let u=await c();return n.primitiveCallIds.set(u,r),u};let p=e.body.bind(e);e.body=async function(){let u=await p();return W.set(u,r),u};}createWrapper(e,r){let n=this;return async function(a,l){if(!qn(a,n.apiConfig.apiIncludeUrls,n.apiConfig.apiExcludeUrls))return r(a,l);let i=`api-call-${n.callCounter++}`,d=new Date().toISOString(),o=e==="fetch"?(l?.method??"GET").toUpperCase():e.toUpperCase(),c=lo(l),p=performance.now();n._lastCallId=i,n.assertionTracker&&n.assertionTracker.setCurrentCallId(i);let h;try{h=await r(a,l);}catch(u){let f=performance.now();try{let g=n.apiConfig.captureRequestBody?Pe(c,n.apiConfig.redactBodyFields):null,m={id:i,timestamp:d,method:o,url:Ut(a),requestHeaders:null,requestBody:g,responseStatusCode:null,responseStatusText:null,responseHeaders:null,responseBody:null,responseTimeMs:Math.round((f-p)*100)/100,isBinary:!1,error:u instanceof Error?u.message:String(u)};n.apiCallWriter&&(n.apiCallWriter.append(m),n.apiCallCount++);}catch{}throw u}try{let u=performance.now(),f=h.headers(),g=f["content-type"]??null,m=g?!oo(g):!1,y=null;n.apiConfig.captureRequestHeaders&&l?.headers&&(y=l.headers);let w=null;n.apiConfig.captureResponseHeaders&&(w=f);let v=n.apiConfig.captureRequestBody?c:null,k=null;if(n.apiConfig.captureResponseBody)try{m?k=(await h.body()).toString("base64"):k=await h.text();}catch{}let C=[],b=[];try{if(n.apiConfig.captureRequestHeaders&&(C=co(l?.headers)),n.apiConfig.captureResponseHeaders){let S=h.headersArray().filter(L=>L.name.toLowerCase()==="set-cookie").map(L=>L.value);b=uo(S);}}catch{}y=qt(y,n.apiConfig.redactHeaders),w=qt(w,n.apiConfig.redactHeaders),v=Pe(v,n.apiConfig.redactBodyFields),k=Pe(k,n.apiConfig.redactBodyFields);let T={id:i,timestamp:d,method:o,url:Ut(h.url()),requestHeaders:y,requestBody:v,responseStatusCode:h.status(),responseStatusText:h.statusText(),responseHeaders:w,responseBody:k,requestCookies:C.length?C:null,responseCookies:b.length?b:null,responseTimeMs:Math.round((u-p)*100)/100,isBinary:m,error:null};n.apiCallWriter&&(n.apiCallWriter.append(T),n.apiCallCount++);}catch{}try{h[Bt]=i,n.tagResponseMethods(h,i);}catch{}return h}}};Ne.DEFAULT_API_CONFIG=Object.freeze({trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]});var X=Ne;Ee(expect);var go="__testrelic_api_config",ho="__testrelic_config_trackApiCalls";function zt(t){let e=t.annotations.find(n=>n.type===go&&n.description!==void 0);if(e)try{return JSON.parse(e.description,mo)}catch{}let r=t.annotations.find(n=>n.type===ho&&n.description!==void 0);return r?{trackApiCalls:r.description!=="false",captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}:{trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}}function mo(t,e){if(typeof e=="object"&&e!==null&&e.__regexp===true&&typeof e.source=="string"){let{source:r,flags:n}=e;return new RegExp(r,n)}return e}var jn={page:async({page:t},e,r)=>{let n=new Le(t);try{await n.init();}catch{}await e(t);try{let{navigations:s,networkRequestsFile:a,networkRequestsCount:l,consoleLogsFile:i,consoleLogsCount:d}=await n.getFileData(),o={testRelicData:!0,version:PAYLOAD_VERSION,navigations:s,apiAssertions:[],networkRequestsFile:a,networkRequestsCount:l,consoleLogsFile:i,consoleLogsCount:d,apiCallsFile:null,apiCallsCount:0};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(o)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}n.dispose();},request:async({request:t},e,r)=>{let n=zt(r);if(!n.trackApiCalls){await e(t);return}let s=new Q,a=new X(t,s,n);a.intercept(),Z(s,a);try{await e(t);}finally{Z(null,null);}try{let{apiCallsFile:l,apiCallsCount:i}=a.getFileData(),d=n.captureAssertions?s.getData():[],o={testRelicData:!0,version:PAYLOAD_VERSION,navigations:[],apiAssertions:d,networkRequestsFile:null,networkRequestsCount:0,consoleLogsFile:null,consoleLogsCount:0,apiCallsFile:l,apiCallsCount:i};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(o)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}a.dispose(),s.dispose();}};test.extend(jn);Ee(expect);var xo={request:async({request:t},e,r)=>{let n=zt(r);if(!n.trackApiCalls){await e(t);return}let s=new Q,a=new X(t,s,n);a.intercept(),Z(s,a);let l=null;try{l=new P("api-console");}catch{}let i=0,d=process.stdout.write,o=process.stderr.write;process.stdout.write=function(c,...p){try{let h=typeof c=="string"?c:Buffer.isBuffer(c)?c.toString("utf-8"):String(c);!h.startsWith("[testrelic]")&&!h.startsWith("\u2139 TestRelic")&&!h.startsWith("\u2713 TestRelic")&&!h.startsWith("\u26A0 TestRelic")&&l&&(l.append({level:"stdout",text:h.replace(/\n$/,""),timestamp:new Date().toISOString(),location:null}),i++);}catch{}return d.apply(process.stdout,[c,...p])},process.stderr.write=function(c,...p){try{let h=typeof c=="string"?c:Buffer.isBuffer(c)?c.toString("utf-8"):String(c);!h.startsWith("[testrelic]")&&!h.startsWith("\u26A0 TestRelic")&&l&&(l.append({level:"stderr",text:h.replace(/\n$/,""),timestamp:new Date().toISOString(),location:null}),i++);}catch{}return o.apply(process.stderr,[c,...p])};try{await e(t);}finally{process.stdout.write=d,process.stderr.write=o,Z(null,null);}try{l?.close();let{apiCallsFile:c,apiCallsCount:p}=a.getFileData(),h=n.captureAssertions?s.getData():[],u={testRelicData:!0,version:PAYLOAD_VERSION,navigations:[],apiAssertions:h,networkRequestsFile:null,networkRequestsCount:0,consoleLogsFile:i>0?l?.getPath()??null:null,consoleLogsCount:i,apiCallsFile:c,apiCallsCount:p};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(u)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}a.dispose(),s.dispose();}};var Wn="@testrelic/playwright-analytics";function To(t){return Array.isArray(t)?t.some(e=>Array.isArray(e)&&typeof e[0]=="string"&&e[0]===Wn):false}function Co(t,e){let n={video:"on",screenshot:"on",trace:"on",...t.use??{}},s;Array.isArray(t.reporter)?s=t.reporter:typeof t.reporter=="string"?s=[[t.reporter]]:s=[["list"]];let a=To(s)?s:[...s,[Wn,e??{}]];return defineConfig({...t,use:n,reporter:a})}var Gn=false;function Kc(t,e,r="manual_record"){if(!t||!t.annotations){Gn||(Gn=true,process.stderr.write(`[testrelic] recordNavigation: reporter not active, navigation not recorded
2670
- `));return}let n={url:e,navigationType:r,timestamp:new Date().toISOString()};t.annotations.push({type:"lambdatest-navigation",description:JSON.stringify(n)});}export{pe as SCHEMA_VERSION,Me as default,Co as defineConfig,Kc as recordNavigation,xo as testRelicApiFixture,jn as testRelicFixture};//# sourceMappingURL=index.js.map
4316
+ `));}catch{}}switchToStreamingMode(){try{if(this.activeReportMode==="streaming")return;this.activeReportMode="streaming";let e=dirname(this.config.outputPath);this.streamingWriter=new he(e);for(let r of this.collectedTests){let n=et(r.filePath,r.titlePath,r.testId,r.retry),s=r.apiCallsFile&&r.apiCallsCount>0?Y(r.apiCallsFile):void 0,a={id:n,navigations:r.navigations??[],apiAssertions:r.apiAssertions??[],actions:r.actions??[],artifacts:r.artifacts,failureDiagnostic:r.failure,hasNetworkFile:r.networkRequestsFile!==null,networkRequestsCount:r.networkRequestsCount,hasConsoleFile:r.consoleLogsFile!==null,consoleLogsCount:r.consoleLogsCount,hasApiCallsFile:r.apiCallsFile!==null,apiCallsCount:r.apiCallsCount,startedAt:r.startedAt};this.streamingWriter.writeTestDetail(n,a,{networkRequestsFile:r.networkRequestsFile,consoleLogsFile:r.consoleLogsFile,apiCallsFile:r.apiCallsFile});let o={id:n,title:r.titlePath[r.titlePath.length-1]??r.title,titlePath:r.titlePath,filePath:r.filePath,status:r.status,duration:r.duration,project:r.titlePath[1]??"",retryIndex:r.retry,tags:r.tags,hasNetworkData:r.networkRequestsCount>0,hasConsoleData:r.consoleLogsCount>0,hasArtifacts:r.artifacts!==null,hasActionSteps:(r.actions?.length??0)>0,errorMessage:r.failure?.message?.split(`
4317
+ `)[0]??null,networkCount:r.networkRequestsCount,consoleCount:r.consoleLogsCount,actionCount:r.actions?.length??0,testType:r.testType,apiCallCount:r.apiCallsCount,...r.apiStatusMix?{apiStatusMix:r.apiStatusMix}:s?{apiStatusMix:td(s)}:{},assertionsPassed:We(r.apiAssertions,"passed"),assertionsFailed:We(r.apiAssertions,"failed"),...(()=>{let i=Fs(r.apiAssertions);return i?{firstFailedAssertion:i}:{}})(),isRetry:!1,retryGroupKey:tt(r.filePath,r.titlePath,r.testId)};switch(this.testIndex.push(o),this.summaryCounters.total++,r.status){case "passed":this.summaryCounters.passed++;break;case "failed":this.summaryCounters.failed++;break;case "flaky":this.summaryCounters.flaky++;break;case "skipped":this.summaryCounters.skipped++;break;case "timedout":this.summaryCounters.timedOut++;break;default:this.summaryCounters.interrupted++;break}this.upsertAggContribution(`${r.filePath}::${r.titlePath.join(" > ")}`,Os(r.retry,s,r.apiAssertions??[],r.navigations??[],r.networkRequestsCount,r.consoleLogsCount,r.actions??null));}this.collectedTests.length=0;}catch{}}buildTimeline(){let e=this.collectedTests.map(r=>({titlePath:r.titlePath,title:r.title,status:r.status,duration:r.duration,startedAt:r.startedAt,completedAt:r.completedAt,retryCount:r.retryCount,retry:r.retry,tags:r.tags,failure:r.failure,specFile:r.specFile,navigations:r.navigations,apiCalls:null,apiAssertions:r.apiAssertions,testId:r.testId,filePath:r.filePath,suiteName:r.suiteName,testType:r.testType,isFlaky:r.isFlaky,retryStatus:r.retryStatus,expectedStatus:r.expectedStatus,actualStatus:r.actualStatus,artifacts:r.artifacts,networkRequests:null,actions:r.actions,consoleLogs:null}));return rs(e,{navigationTypes:this.config.navigationTypes})}readJsonlFile(e){if(!e)return null;let r=Y(e);return r.length>0?r:null}toTestResult(e,r){let n=null,s=null,a=null;return r?.loadFiles&&(n=this.readJsonlFile(e.networkRequestsFile),s=this.readJsonlFile(e.consoleLogsFile),a=this.readJsonlFile(e.apiCallsFile)),{title:e.title,status:e.status,duration:e.duration,startedAt:e.startedAt,completedAt:e.completedAt,retryCount:e.retryCount,tags:e.tags,failure:e.failure,testId:e.testId,filePath:e.filePath,suiteName:e.suiteName,testType:e.testType,isFlaky:e.isFlaky,retryStatus:e.retryStatus,expectedStatus:e.expectedStatus,actualStatus:e.actualStatus,artifacts:e.artifacts,visual:e.visual,networkRequests:n,apiCalls:a,apiAssertions:e.apiAssertions,actions:e.actions??null,consoleLogs:s}}buildCloudTestPayload(e){let r={title:e.title,status:e.status,duration:e.duration,startedAt:e.startedAt,completedAt:e.completedAt,retry:e.retry,retryCount:e.retryCount,retryStatus:e.retryStatus,tags:e.tags,failure:e.failure,testId:e.testId,filePath:e.filePath,suiteName:e.suiteName,testType:e.testType,isFlaky:e.isFlaky,expectedStatus:e.expectedStatus,actualStatus:e.actualStatus,artifacts:e.artifacts,visual:e.visual??[],actions:e.actions??[],navigations:e.navigations??[],steps:e.actions??[],browser:e.browser,os:e.os,source:e.source};if(e.apiAssertions&&e.apiAssertions.length>0&&(r.protocolAssertions=tr(e.apiAssertions)),e.consoleLogsFile&&(r.consoleLogs=Y(e.consoleLogsFile)),e.networkRequestsFile&&(r.networkRequests=Y(e.networkRequestsFile)),e.apiCallsFile){let n=Y(e.apiCallsFile);r.apiCalls=Qt(n),(!r.networkRequests||r.networkRequests.length===0)&&(r.networkRequests=er(n));}return r}writeReport(e){try{let r=JSON.stringify(e),n=this.config.outputPath,s=dirname(n);mkdirSync(s,{recursive:!0});let a=n+".tmp";writeFileSync(a,r,"utf-8"),renameSync(a,n);}catch(r){process.stderr.write(`[testrelic] Failed to write report: ${r instanceof Error?r.message:String(r)}
4318
+ `);}}};var gd=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function hd(t){let e=t.toLowerCase();return gd.some(r=>e.includes(r))}var Je=class t{constructor(e,r){this.page=e;this.records=[];this.listeners=[];this.navCounters=[];this.pendingRequests=new Map;this.networkWriter=null;this.consoleWriter=null;this.pendingBodyReads=[];this.requestIdCounter=0;this.networkRequestCount=0;this.consoleLogCount=0;this.includeNetworkStats=r?.includeNetworkStats??true,this.origGoto=e.goto.bind(e),this.origGoBack=e.goBack.bind(e),this.origGoForward=e.goForward.bind(e),this.origReload=e.reload.bind(e);try{this.includeNetworkStats&&(this.networkWriter=new q("network")),this.consoleWriter=new q("console");}catch{}this.interceptMethods(),this.attachListeners();}async init(){await this.injectSPADetection();}async finalizeCapturedRequests(){await Promise.allSettled(this.pendingBodyReads),this.pendingBodyReads=[];for(let[,e]of this.pendingRequests)this.networkWriter&&(this.networkWriter.append({url:e.url,method:e.method,resourceType:e.resourceType,statusCode:0,responseTimeMs:Date.now()-e.startTimeMs,startedAt:e.startedAt,requestHeaders:e.headers,requestBody:e.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:e.postDataTruncated,responseBodyTruncated:false,isBinary:false,error:"incomplete"}),this.networkRequestCount++);this.pendingRequests.clear(),this.networkWriter?.close(),this.consoleWriter?.close();}static mapConsoleType(e){switch(e){case "log":return "log";case "warning":return "warn";case "error":return "error";case "info":return "info";case "debug":return "debug";default:return "log"}}async getFileData(){this.assembleNetworkStats();let e=this.records.map(r=>({url:r.url,navigationType:r.navigationType,timestamp:r.timestamp,domContentLoadedAt:r.domContentLoadedAt,networkIdleAt:r.networkIdleAt,networkStats:r.networkStats}));return this.includeNetworkStats?await this.finalizeCapturedRequests():this.consoleWriter?.close(),{navigations:e,networkRequestsFile:this.networkWriter?.getCount()?this.networkWriter.getPath():null,networkRequestsCount:this.networkWriter?.getCount()??0,consoleLogsFile:this.consoleWriter?.getCount()?this.consoleWriter.getPath():null,consoleLogsCount:this.consoleWriter?.getCount()??0}}async flushLegacyAnnotations(e){}dispose(){let e=t.asPatchable(this.page);e.goto=this.origGoto,e.goBack=this.origGoBack,e.goForward=this.origGoForward,e.reload=this.origReload;for(let{event:r,handler:n}of this.listeners)this.page.off(r,n);this.listeners=[],this.records=[],this.pendingRequests.clear(),this.pendingBodyReads=[],this.networkWriter?.close(),this.consoleWriter?.close();}getRecords(){return this.assembleNetworkStats(),this.records}static asPatchable(e){return e}interceptMethods(){let e=this,r=this.page,n=t.asPatchable(r);n.goto=async function(s,a){return e.recordNavigation(s,"goto"),e.origGoto(s,a)},n.goBack=async function(s){let a=await e.origGoBack(s);return e.recordNavigation(r.url(),"back"),a},n.goForward=async function(s){let a=await e.origGoForward(s);return e.recordNavigation(r.url(),"forward"),a},n.reload=async function(s){return e.recordNavigation(r.url(),"refresh"),e.origReload(s)};}attachListeners(){let e=()=>{this.lastDomContentLoaded=new Date().toISOString(),this.records.length>0&&(this.records[this.records.length-1].domContentLoadedAt=this.lastDomContentLoaded);};this.page.on("domcontentloaded",e),this.listeners.push({event:"domcontentloaded",handler:e});let r=s=>{try{let a=s;if(typeof a.parentFrame=="function"&&a.parentFrame()!==null)return;let o=a.url(),i=this.records[this.records.length-1];if(i&&Date.now()-new Date(i.timestamp).getTime()<50&&i.url===o)return;this.recordNavigation(o,"navigation");}catch{}};this.page.on("framenavigated",r),this.listeners.push({event:"framenavigated",handler:r});let n=s=>{try{let a=s,o=a.type(),i=a.text();if(o==="debug"&&i.startsWith("__testrelic_nav:")){try{let d=JSON.parse(i.slice(16));d.type&&d.url&&this.recordNavigation(d.url,d.type);}catch{}return}{let d=null;try{let c=a.location();c&&c.url&&(d=`${c.url}:${c.lineNumber}:${c.columnNumber}`);}catch{}let l=t.mapConsoleType(o);this.consoleWriter&&(this.consoleWriter.append({level:l,text:i,timestamp:new Date().toISOString(),location:d}),this.consoleLogCount++);}}catch{}};if(this.page.on("console",n),this.listeners.push({event:"console",handler:n}),this.includeNetworkStats){let s=i=>{let d=this.records.length-1,l=this.counterForNav(d);l&&l.totalRequests++;try{let c=i,u=String(this.requestIdCounter++),f=c.postData()??null,p={url:c.url(),method:c.method(),resourceType:this.mapResourceType(c.resourceType()),headers:c.headers(),postData:f,postDataTruncated:!1,startedAt:new Date().toISOString(),startTimeMs:Date.now(),navIndex:d};this.pendingRequests.set(u,p),i.__testrelic_id=u;}catch{}};this.page.on("request",s),this.listeners.push({event:"request",handler:s});let a=i=>{try{let d=i,l=d.request().__testrelic_id,c=l?this.pendingRequests.get(l):void 0,u=this.counterForNav(c?c.navIndex:this.records.length-1);if(u){let y=d.status();y>=400&&(u.failedRequests++,u.failedRequestUrls.push(y+" "+d.url()));let b=d.headers()["content-length"];b&&(u.totalBytes+=parseInt(b,10)||0);let k=d.request().resourceType(),A=this.mapResourceType(k);u.byType[A]++;}if(!l||!c)return;this.pendingRequests.delete(l);let f=Date.now()-c.startTimeMs,p=d.headers(),g=p["content-type"]??null,m=parseInt(p["content-length"]??"0",10)||0,h=g?!hd(g):!1,v=(async()=>{let y=null;if(!h)try{y=(await d.body()).toString("utf-8");}catch{}let b={url:c.url,method:c.method,resourceType:c.resourceType,statusCode:d.status(),responseTimeMs:f,startedAt:c.startedAt,requestHeaders:c.headers,requestBody:c.postData,responseBody:y,responseHeaders:p,contentType:g,responseSize:m,requestBodyTruncated:c.postDataTruncated,responseBodyTruncated:!1,isBinary:h,error:null};this.networkWriter&&(this.networkWriter.append(b),this.networkRequestCount++);})();this.pendingBodyReads.push(v);}catch{}};this.page.on("response",a),this.listeners.push({event:"response",handler:a});let o=i=>{let d=i.__testrelic_id,l=d?this.pendingRequests.get(d):void 0,c=this.counterForNav(l?l.navIndex:this.records.length-1);if(c){c.failedRequests++;try{let u=i;c.failedRequestUrls.push("ERR "+u.url());}catch{}}try{let u=i;if(!d||!l)return;this.pendingRequests.delete(d);let f={url:l.url,method:l.method,resourceType:l.resourceType,statusCode:0,responseTimeMs:Date.now()-l.startTimeMs,startedAt:l.startedAt,requestHeaders:l.headers,requestBody:l.postData,responseBody:null,responseHeaders:null,contentType:null,responseSize:0,requestBodyTruncated:l.postDataTruncated,responseBodyTruncated:!1,isBinary:!1,error:u.failure()?.errorText??"Unknown error"};this.networkWriter&&(this.networkWriter.append(f),this.networkRequestCount++);}catch{}};this.page.on("requestfailed",o),this.listeners.push({event:"requestfailed",handler:o});}}async injectSPADetection(){try{await this.page.addInitScript(()=>{let e=history.pushState.bind(history),r=history.replaceState.bind(history);history.pushState=function(...n){e(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_route",url:location.href}));},history.replaceState=function(...n){r(...n),console.debug("__testrelic_nav:"+JSON.stringify({type:"spa_replace",url:location.href}));},window.addEventListener("popstate",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"popstate",url:location.href}));}),window.addEventListener("hashchange",()=>{console.debug("__testrelic_nav:"+JSON.stringify({type:"hash_change",url:location.href}));});});}catch{}}recordNavigation(e,r){this.records.push({url:e,navigationType:r,timestamp:new Date().toISOString()}),this.includeNetworkStats&&this.navCounters.push(this.createNetworkCounter());}createNetworkCounter(){return {totalRequests:0,failedRequests:0,failedRequestUrls:[],totalBytes:0,byType:{xhr:0,document:0,script:0,stylesheet:0,image:0,font:0,other:0}}}counterForNav(e){return e>=0?this.navCounters[e]:void 0}assembleNetworkStats(){if(this.includeNetworkStats)for(let e=0;e<this.records.length;e++){let r=this.navCounters[e];r&&(this.records[e].networkStats={totalRequests:r.totalRequests,failedRequests:r.failedRequests,failedRequestUrls:[...r.failedRequestUrls],totalBytes:r.totalBytes,byType:{...r.byType}});}}mapResourceType(e){switch(e){case "xhr":case "fetch":return "xhr";case "document":return "document";case "script":return "script";case "stylesheet":return "stylesheet";case "image":return "image";case "font":return "font";default:return "other"}}};var yr=Symbol.for("__testrelic_call_id"),vd="__testrelic_api_assertions",te=new WeakMap,oe=class{constructor(){this.assertions=[];this.currentCallId=null;}recordAssertion(e){this.assertions.push(e);}getAssertions(){return this.assertions}setCurrentCallId(e){this.currentCallId=e;}getCurrentCallId(){return this.currentCallId}getData(){return [...this.assertions]}flushLegacyAnnotations(e){this.assertions.length!==0&&e.annotations.push({type:vd,description:JSON.stringify(this.assertions)});}dispose(){this.assertions=[],this.currentCallId=null;}},zs=new Map;function yd(t){let e=zs.get(t);if(e)return e;try{let r=t.startsWith("file://")?fileURLToPath(t):t,s=readFileSync(r,"utf-8").split(`
4319
+ `);return zs.set(t,s),s}catch{return null}}var js=(()=>{let t=new Error().stack;if(!t)return null;for(let e of t.split(`
4320
+ `)){let r=e.match(/at\s+(?:.*?\s+\()?(.+?):\d+:\d+\)?$/);if(!r||r[1].startsWith("node:"))continue;let n=Math.max(r[1].lastIndexOf("/"),r[1].lastIndexOf("\\"));return n>0?r[1].slice(0,n+1):null}return null})();function bd(t){if(!t.startsWith("file:"))return t;try{return fileURLToPath(t)}catch{return t}}function xd(){let e=new Error().stack;if(!e)return null;let r=e.split(`
4321
+ `);return Vs(r,true)??Vs(r,false)}function Vs(t,e){for(let r of t){if(e&&js!==null&&r.includes(js)||r.includes("node_modules")||r.includes("node:internal")||r.includes(" at new Error")||r.includes(" at captureAssertionLocation")||r.includes(" at findFrame")||r.includes(" at expectWrapper")||r.includes(" at wrappedMatcher"))continue;let n=r.match(/at\s+(?:.*?\s+\()?(.+?):(\d+):(\d+)\)?$/);if(n)return {file:bd(n[1]),line:parseInt(n[2],10),column:parseInt(n[3],10)}}return null}function wd(t,e){if(!t)return "custom";let r=t.toLowerCase();return r.includes(".status()")&&!r.includes(".statustext()")?r.includes(".ok()")?"statusOk":"status":r.includes(".ok()")?"statusOk":r.includes(".headers()")||r.includes(".headersarray()")?"header":r.includes("tomatchobject")||r.includes("toequal")?"bodyMatch":r.includes("tocontain")||r.includes("tostringcontaining")?"bodyContains":r.includes(".json()")||r.includes(".text()")?"bodyField":"custom"}function kd(t,e,r){let n=function(i,...d){let l=t(),c=e();if(!l||!c)return r(i,...d);let u=null;if(i!=null&&typeof i=="object"){let m=i[yr];typeof m=="string"&&(u=m);}if(!u&&i!==null&&i!==void 0&&typeof i=="object"&&(u=te.get(i)??null),u||(u=c.getCallIdForValue(i)),u||(u=l.getCurrentCallId()),!u)return r(i,...d);let f=xd(),p;if(f){let m=yd(f.file);m&&f.line>0&&f.line<=m.length&&(p=m[f.line-1].trim());}let g=r(i,...d);return u?Ks(g,{tracker:l,callId:u,received:i,location:f??{file:"unknown",line:0},expression:p}):g},s=r,a=n;for(let o of Object.keys(s))a[o]=s[o];return n}var $s=Symbol.for("testrelic.activeAssertionContext");function vr(){let t=globalThis,e=t[$s];return e||(e={tracker:null,apiTracker:null},t[$s]=e),e}function le(t,e){let r=vr();r.tracker=t,r.apiTracker=e;}function Ye(t){return kd(()=>vr().tracker,()=>vr().apiTracker,t)}function Ks(t,e){return new Proxy(t,{get(r,n,s){let a=Reflect.get(r,n,s);return typeof a!="function"||typeof n=="symbol"?a:n==="not"?Ks(a,e):function(...i){let d=i[0],l="passed",c=e.received,u=null;try{let f=a.apply(this??r,i);return f&&typeof f=="object"&&typeof f.then=="function"?f.then(()=>{mr(e,n,d,c,"passed");},p=>{throw mr(e,n,d,c,"failed"),p}):f}catch(f){if(l="failed",u=f,f instanceof Error&&f.message){let p=f.message.match(/Received:\s*(.+)/);p&&(c=p[1]);}throw f}finally{(u!==null||l==="passed")&&mr(e,n,d,c,l);}}}})}function mr(t,e,r,n,s){let a=wd(t.expression,t.received),o={callId:t.callId,type:a,expected:Ws(r),actual:Ws(n),status:s,location:t.location,expression:t.expression};t.tracker.recordAssertion(o);}function Ws(t){if(t==null)return t;if(typeof t=="string")return se(t);if(typeof t=="number"||typeof t=="boolean")return t;try{return JSON.stringify(t),t}catch{return String(t)}}var Js="[REDACTED]";function xr(t,e){if(t===null||e.length===0)return t;let r=new Set(e.map(s=>s.toLowerCase())),n={};for(let s of Object.keys(t))Object.prototype.hasOwnProperty.call(t,s)&&(n[s]=r.has(s.toLowerCase())?Js:t[s]);return n}function Xe(t,e){if(t===null||e.length===0)return t;let r;try{r=JSON.parse(t);}catch{return t}if(typeof r!="object"||r===null)return t;let n=new Set(e.map(a=>a.toLowerCase())),s=br(r,n);return JSON.stringify(s)}function br(t,e){if(Array.isArray(t))return t.map(r=>br(r,e));if(typeof t=="object"&&t!==null){let r={};for(let n of Object.keys(t)){if(!Object.prototype.hasOwnProperty.call(t,n))continue;let s=t[n];e.has(n.toLowerCase())?r[n]=Js:r[n]=br(s,e);}return r}return t}var Cd=new Set(["access_token","api_key","token","sig"]);function wr(t){if(!t)return t;let e;try{e=new URL(t);}catch{return t}let r=false;for(let n of [...e.searchParams.keys()])Cd.has(n.toLowerCase())&&(e.searchParams.delete(n),r=true);return r?e.toString():t}function Xs(t,e,r){if(r.length>0){for(let n of r)if(Ys(t,n))return false}if(e.length>0){for(let n of e)if(Ys(t,n))return true;return false}return true}function Ys(t,e){try{return e instanceof RegExp?e.test(t):Td(e).test(t)}catch{return console.warn(`[testrelic] Invalid URL filter pattern: ${String(e)}`),false}}function Td(t){let e="",r=0;for(;r<t.length;){let n=t[r];n==="*"&&t[r+1]==="*"?(e+=".*",r+=2,t[r]==="/"&&r++):n==="*"?(e+="[^/]*",r++):n==="?"?(e+="[^/]",r++):".+^${}()|[]\\".includes(n)?(e+="\\"+n,r++):(e+=n,r++);}return new RegExp(e)}var Sd=["get","post","put","patch","delete","head","fetch"],Rd=["text/","application/json","application/xml","application/javascript","application/x-www-form-urlencoded","application/graphql"];function Ad(t){let e=t.toLowerCase();return Rd.some(r=>e.includes(r))}function _d(t){if(!t)return null;if(t.data!==void 0&&t.data!==null){let e=t.data;return typeof e=="string"?e:Buffer.isBuffer(e)?e.toString("base64"):JSON.stringify(e)}if(t.form!==void 0&&t.form!==null)return JSON.stringify(t.form);if(t.multipart!==void 0&&t.multipart!==null){let e=t.multipart,r={};for(let[n,s]of Object.entries(e))typeof s=="string"||typeof s=="number"||typeof s=="boolean"?r[n]=String(s):s&&typeof s=="object"&&"name"in s?r[n]=`[file: ${s.name}]`:r[n]="[binary]";return JSON.stringify(r)}return null}function Zs(t){return t?"\u2022\u2022\u2022\u2022":""}function Id(t){if(!t)return [];let e;for(let[n,s]of Object.entries(t))if(n.toLowerCase()==="cookie"){e=s;break}if(!e)return [];let r=[];for(let n of e.split(";")){let s=n.trim();if(!s)continue;let a=s.indexOf("="),o=a===-1?s:s.slice(0,a).trim(),i=a===-1?"":s.slice(a+1).trim();o&&r.push({name:o,value:Zs(i)});}return r}function Md(t){let e=[];for(let r of t)for(let n of r.split(/,(?=\s*[A-Za-z0-9_.\-]+=)/)){let s=n.trim();if(!s)continue;let a=s.split(";"),o=a[0].indexOf("=");if(o===-1)continue;let i=a[0].slice(0,o).trim(),d=a[0].slice(o+1).trim(),l=a.slice(1).map(c=>c.trim()).filter(Boolean).join(" \xB7 ")||void 0;i&&e.push({name:i,value:Zs(d),attributes:l});}return e}var Ze=class Ze{constructor(e,r,n){this.originals=new Map;this.apiCallWriter=null;this.statusMix={"2xx":0,"3xx":0,"4xx":0,"5xx":0,error:0};this.callCounter=0;this.apiCallCount=0;this.disposed=false;this._lastCallId=null;this.primitiveCallIds=new Map;this.context=e,this.assertionTracker=r??null,this.apiConfig=n??Ze.DEFAULT_API_CONFIG;try{this.apiCallWriter=new q("api-calls");}catch{}}get lastCallId(){return this._lastCallId}getCallIdForValue(e){return e!=null&&typeof e=="object"?te.get(e)??null:this.primitiveCallIds.get(e)??null}intercept(){for(let r of Sd){let n=this.context[r].bind(this.context);this.originals.set(r,n),this.context[r]=this.createWrapper(r,n);}let e=this.context.dispose.bind(this.context);this.originals.set("dispose",e),this.context.dispose=async r=>(this.disposed=true,e(r));}tallyStatus(e){if(e===null||e<=0){this.statusMix.error++;return}if(e>=500){this.statusMix["5xx"]++;return}if(e>=400){this.statusMix["4xx"]++;return}if(e>=300){this.statusMix["3xx"]++;return}if(e>=200){this.statusMix["2xx"]++;return}this.statusMix.error++;}getFileData(){return this.apiCallWriter?.close(),{apiCallsFile:this.apiCallWriter?.getCount()?this.apiCallWriter.getPath():null,apiCallsCount:this.apiCallWriter?.getCount()??0,apiStatusMix:{...this.statusMix}}}flushLegacyAnnotations(e){}dispose(){for(let[e,r]of this.originals)this.context[e]=r;this.originals.clear(),this.apiCallWriter?.close(),this.callCounter=0,this.apiCallCount=0,this._lastCallId=null,this.primitiveCallIds.clear();}get isDisposed(){return this.disposed}getCapturedCallCount(){return this.apiCallCount}tagResponseMethods(e,r){let n=this,s=e.headers.bind(e);e.headers=function(){let p=s();return te.set(p,r),p};let a=e.headersArray.bind(e);e.headersArray=function(){let p=a();return te.set(p,r),p};let o=e.json.bind(e);e.json=async function(){let p=await o();return p!=null&&typeof p=="object"&&te.set(p,r),p};let i=e.status.bind(e);e.status=function(){let p=i();return n.primitiveCallIds.set(p,r),p};let d=e.statusText.bind(e);e.statusText=function(){let p=d();return n.primitiveCallIds.set(p,r),p};let l=e.ok.bind(e);e.ok=function(){let p=l();return n.primitiveCallIds.set(p,r),p};let c=e.text.bind(e);e.text=async function(){let p=await c();return n.primitiveCallIds.set(p,r),p};let u=e.body.bind(e);e.body=async function(){let p=await u();return te.set(p,r),p};}createWrapper(e,r){let n=this;return async function(a,o){if(!Xs(a,n.apiConfig.apiIncludeUrls,n.apiConfig.apiExcludeUrls))return r(a,o);let i=`api-call-${n.callCounter++}`,d=new Date().toISOString(),l=e==="fetch"?(o?.method??"GET").toUpperCase():e.toUpperCase(),c=_d(o),u=performance.now();n._lastCallId=i,n.assertionTracker&&n.assertionTracker.setCurrentCallId(i);let f;try{f=await r(a,o);}catch(p){let g=performance.now();try{let m=n.apiConfig.captureRequestBody?Xe(c,n.apiConfig.redactBodyFields):null,h={id:i,timestamp:d,method:l,url:wr(a),requestHeaders:null,requestBody:m,responseStatusCode:null,responseStatusText:null,responseHeaders:null,responseBody:null,responseTimeMs:Math.round((g-u)*100)/100,isBinary:!1,error:p instanceof Error?p.message:String(p)};n.apiCallWriter&&(n.apiCallWriter.append(h),n.apiCallCount++,n.tallyStatus(null));}catch{}throw p}try{let p=performance.now(),g=f.headers(),m=g["content-type"]??null,h=m?!Ad(m):!1,v=null;n.apiConfig.captureRequestHeaders&&o?.headers&&(v=o.headers);let y=null;n.apiConfig.captureResponseHeaders&&(y=g);let b=n.apiConfig.captureRequestBody?c:null,k=null;if(n.apiConfig.captureResponseBody)try{h?k=(await f.body()).toString("base64"):k=await f.text();}catch{}let A=[],E=[];try{if(n.apiConfig.captureRequestHeaders&&(A=Id(o?.headers)),n.apiConfig.captureResponseHeaders){let w=f.headersArray().filter(S=>S.name.toLowerCase()==="set-cookie").map(S=>S.value);E=Md(w);}}catch{}v=xr(v,n.apiConfig.redactHeaders),y=xr(y,n.apiConfig.redactHeaders),b=Xe(b,n.apiConfig.redactBodyFields),k=Xe(k,n.apiConfig.redactBodyFields);let T={id:i,timestamp:d,method:l,url:wr(f.url()),requestHeaders:v,requestBody:b,responseStatusCode:f.status(),responseStatusText:f.statusText(),responseHeaders:y,responseBody:k,requestCookies:A.length?A:null,responseCookies:E.length?E:null,responseTimeMs:Math.round((p-u)*100)/100,isBinary:h,error:null};n.apiCallWriter&&(n.apiCallWriter.append(T),n.apiCallCount++,n.tallyStatus(T.responseStatusCode));}catch{}try{f[yr]=i,n.tagResponseMethods(f,i);}catch{}return f}}};Ze.DEFAULT_API_CONFIG=Object.freeze({trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]});var de=Ze;var Dd=[".png",".jpg",".jpeg"];function Fd(t){let e=t.toLowerCase();return Dd.some(r=>e.endsWith(r))?t:`${t}.png`}function Bd(){try{return test.info()}catch{return null}}function Od(t,e){for(let r=e;r<t.length;r++){let n=/^(.+)-(expected|actual|diff|previous)\.[A-Za-z0-9]+$/.exec(t[r]?.name??"");if(n)return n[1]}return null}async function qd(t,e){if(!Ne(t))return De("Attaching the baseline of a passing comparison"),null;try{let r=t.snapshotPath(e,{kind:"screenshot"}),n=extname(r)||".png",s=basename(r,n);return await t.attach(`${s}-expected${n}`,{path:r}),s}catch{return null}}async function Hd(t,e){try{await t.attach(qt,{body:Buffer.from(JSON.stringify([e])),contentType:"application/json"});}catch{}}async function Cr(t,e,r={}){let n={...r};delete n.tags;let s=Fd(e),a=Bd(),o=a?.attachments.length??0,i=true,d="";try{await expect(t).toHaveScreenshot(s,n);}catch(l){i=false,d=l instanceof Error?l.message:String(l);}if(a){let l=i?await qd(a,s):Od(a.attachments,o);if(l){let c=!i&&!Ee(d)&&!a.attachments.some((u,f)=>f>=o&&u.name.startsWith(`${l}-diff.`));await Hd(a,{stem:l,name:e,status:i?"passed":c?"new":"failed"});}}return {pass:i,name:"toMatchVisualBaseline",message:()=>i?`Expected "${e}" to differ from its visual baseline, but it matched.`:d}}expect.extend({toMatchVisualBaseline:Cr});function jd(){try{return test.info()}catch{return null}}On(Ye(expect),jd);var Vd="__testrelic_api_config",$d="__testrelic_config_trackApiCalls";function Tr(t){let e=t.annotations.find(n=>n.type===Vd&&n.description!==void 0);if(e)try{return JSON.parse(e.description,Wd)}catch{}let r=t.annotations.find(n=>n.type===$d&&n.description!==void 0);return r?{trackApiCalls:r.description!=="false",captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}:{trackApiCalls:true,captureRequestHeaders:true,captureResponseHeaders:true,captureRequestBody:true,captureResponseBody:true,captureAssertions:true,redactHeaders:["authorization","cookie","set-cookie","x-api-key"],redactBodyFields:["password","secret","token","apiKey","api_key"],apiIncludeUrls:[],apiExcludeUrls:[]}}function Wd(t,e){if(typeof e=="object"&&e!==null&&e.__regexp===true&&typeof e.source=="string"){let{source:r,flags:n}=e;return new RegExp(r,n)}return e}var na={page:async({page:t},e,r)=>{let n=new Je(t);try{await n.init();}catch{}await e(t);try{let{navigations:s,networkRequestsFile:a,networkRequestsCount:o,consoleLogsFile:i,consoleLogsCount:d}=await n.getFileData(),l={testRelicData:!0,version:PAYLOAD_VERSION,navigations:s,apiAssertions:[],networkRequestsFile:a,networkRequestsCount:o,consoleLogsFile:i,consoleLogsCount:d,apiCallsFile:null,apiCallsCount:0};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(l)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}n.dispose();},request:async({request:t},e,r)=>{let n=Tr(r);if(!n.trackApiCalls){await e(t);return}let s=new oe,a=new de(t,s,n);a.intercept(),le(s,a);try{await e(t);}finally{le(null,null);}try{let{apiCallsFile:o,apiCallsCount:i,apiStatusMix:d}=a.getFileData(),l=n.captureAssertions?s.getData():[],c={testRelicData:!0,version:PAYLOAD_VERSION,navigations:[],apiAssertions:l,networkRequestsFile:null,networkRequestsCount:0,consoleLogsFile:null,consoleLogsCount:0,apiCallsFile:o,apiCallsCount:i,apiStatusMix:d};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(c)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}a.dispose(),s.dispose();}};test.extend(na);Ye(expect);var Xd={request:async({request:t},e,r)=>{let n=Tr(r);if(!n.trackApiCalls){await e(t);return}let s=new oe,a=new de(t,s,n);a.intercept(),le(s,a);let o=null;try{o=new q("api-console");}catch{}let i=0,d=process.stdout.write,l=process.stderr.write;process.stdout.write=function(c,...u){try{let f=typeof c=="string"?c:Buffer.isBuffer(c)?c.toString("utf-8"):String(c);!f.startsWith("[testrelic]")&&!f.startsWith("\u2139 TestRelic")&&!f.startsWith("\u2713 TestRelic")&&!f.startsWith("\u26A0 TestRelic")&&o&&(o.append({level:"stdout",text:f.replace(/\n$/,""),timestamp:new Date().toISOString(),location:null}),i++);}catch{}return d.apply(process.stdout,[c,...u])},process.stderr.write=function(c,...u){try{let f=typeof c=="string"?c:Buffer.isBuffer(c)?c.toString("utf-8"):String(c);!f.startsWith("[testrelic]")&&!f.startsWith("\u26A0 TestRelic")&&o&&(o.append({level:"stderr",text:f.replace(/\n$/,""),timestamp:new Date().toISOString(),location:null}),i++);}catch{}return l.apply(process.stderr,[c,...u])};try{await e(t);}finally{process.stdout.write=d,process.stderr.write=l,le(null,null);}try{o?.close();let{apiCallsFile:c,apiCallsCount:u,apiStatusMix:f}=a.getFileData(),p=n.captureAssertions?s.getData():[],g={testRelicData:!0,version:PAYLOAD_VERSION,navigations:[],apiAssertions:p,networkRequestsFile:null,networkRequestsCount:0,consoleLogsFile:i>0?o?.getPath()??null:null,consoleLogsCount:i,apiCallsFile:c,apiCallsCount:u,apiStatusMix:f};await r.attach(ATTACHMENT_NAME,{body:Buffer.from(JSON.stringify(g)),contentType:ATTACHMENT_CONTENT_TYPE});}catch{}a.dispose(),s.dispose();}};var sa="@testrelic/playwright-analytics";function Qd(t){return Array.isArray(t)?t.some(e=>Array.isArray(e)&&typeof e[0]=="string"&&e[0]===sa):false}function ec(t,e){let n={video:"on",screenshot:"on",trace:"on",...t.use??{}},s;Array.isArray(t.reporter)?s=t.reporter:typeof t.reporter=="string"?s=[[t.reporter]]:s=[["list"]];let a=Qd(s)?s:[...s,[sa,e??{}]];return defineConfig({...t,use:n,reporter:a})}var aa=false;function kg(t,e,r="manual_record"){if(!t||!t.annotations){aa||(aa=true,process.stderr.write(`[testrelic] recordNavigation: reporter not active, navigation not recorded
4322
+ `));return}let n={url:e,navigationType:r,timestamp:new Date().toISOString()};t.annotations.push({type:"lambdatest-navigation",description:JSON.stringify(n)});}export{Ce as SCHEMA_VERSION,Ke as default,ec as defineConfig,kg as recordNavigation,Xd as testRelicApiFixture,na as testRelicFixture};//# sourceMappingURL=index.js.map
2671
4323
  //# sourceMappingURL=index.js.map