@testrelic/playwright-analytics 1.3.0 → 2.0.0
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/api-fixture.cjs +2 -0
- package/dist/api-fixture.cjs.map +1 -0
- package/dist/api-fixture.d.cts +25 -0
- package/dist/api-fixture.d.ts +25 -0
- package/dist/api-fixture.js +2 -0
- package/dist/api-fixture.js.map +1 -0
- package/dist/cli.cjs +41 -6
- package/dist/fixture.cjs +1 -1
- package/dist/fixture.cjs.map +1 -1
- package/dist/fixture.d.cts +47 -1
- package/dist/fixture.d.ts +47 -1
- package/dist/fixture.js +1 -1
- package/dist/fixture.js.map +1 -1
- package/dist/index.cjs +223 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +223 -66
- package/dist/index.js.map +1 -1
- package/dist/merge.cjs +1 -1
- package/dist/merge.cjs.map +1 -1
- package/dist/merge.js +1 -1
- package/dist/merge.js.map +1 -1
- package/package.json +23 -13
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/schema.ts","../src/code-extractor.ts","../src/redaction.ts","../src/ci-detector.ts","../src/html-css.ts","../src/html-logo.ts","../src/html-js-render.ts","../src/html-js-network.ts","../src/html-js-filters.ts","../src/html-js-interactions.ts","../src/html-template.ts","../src/browser-open.ts","../src/html-report.ts","../src/artifact-manager.ts","../src/reporter.ts","../src/index.ts"],"names":["DEFAULT_REDACTION_PATTERNS","resolveConfig","options","isValidConfig","createError","ErrorCode","target","outputPath","SCHEMA_VERSION","extractCodeSnippet","filePath","line","contextLines","lines","readFileSync","startLine","endLine","snippetLines","i","marker","lineNum","createRedactor","patterns","text","result","pattern","escaped","flags","cloned","detectGitHubActions","env","detectGitLabCI","detectJenkins","branch","detectCircleCI","detectors","detectCI","envVars","detect","CSS","LOGO_SVG_RAW","LOGO_SVG","JS_RENDER","JS_NETWORK","JS_FILTERS","JS_INTERACTIONS","JS","renderHtmlDocument","reportJson","safeJson","openInBrowser","platform","command","exec","err","generateHtmlReport","report","writeHtmlReport","config","html","dir","dirname","mkdirSync","tmpPath","writeFileSync","renameSync","absolutePath","resolve","sanitizeFolderName","title","name","copyArtifacts","attachments","testTitle","retryCount","outputDir","screenshot","a","video","folderName","artifactDir","join","existsSync","destName","extname","copyFileSync","mapPlaywrightStatus","status","generateTestId","suiteName","input","createHash","getSuiteName","titlePath","getRetryStatus","results","passedIndex","r","getTestType","tags","typeOrder","type","tag","TestRelicReporter","_suite","randomUUID","test","lastResult","outcome","startedAt","completedAt","navigations","n","networkRequests","networkReqAnnotation","failure","firstError","redact","errorLine","codeSnippet","specFile","relative","testId","testType","isFlaky","retryStatus","expectedStatus","actualStatus","artifacts","lastTitle","_result","startedAtTime","totalDuration","timeline","summary","entries","nav","nextNav","navTime","duration","b","passed","failed","flaky","skipped","timedout","json","warned","recordNavigation","testInfo","url","navigationType","annotation"],"mappings":"gNAQO,IAAMA,EAAAA,CAAkD,CAC7D,oBACA,iCAAA,CACA,0DAAA,CACA,mBACF,CAAA,CAiBO,SAASC,CAAAA,CAAcC,CAAAA,CAAmD,CAC/E,GAAIA,IAAY,MAAA,EAAa,CAACC,kBAAAA,CAAcD,CAAO,EACjD,MAAME,gBAAAA,CAAYC,cAAAA,CAAU,cAAA,CAAgB,gCAAgC,CAAA,CAI9E,IAAMC,CAAAA,CAAS,MAAA,CAAO,OAAO,IAAI,CAAA,CACjCA,CAAAA,CAAO,UAAA,CAAaJ,GAAS,UAAA,EAAc,wCAAA,CAC3CI,CAAAA,CAAO,iBAAA,CAAoBJ,GAAS,iBAAA,EAAqB,KAAA,CACzDI,CAAAA,CAAO,mBAAA,CAAsBJ,GAAS,mBAAA,EAAuB,IAAA,CAC7DI,EAAO,gBAAA,CAAmBJ,CAAAA,EAAS,kBAAoB,CAAA,CACvDI,CAAAA,CAAO,mBAAA,CAAsBJ,CAAAA,EAAS,qBAAuB,IAAA,CAC7DI,CAAAA,CAAO,eAAA,CAAkBJ,CAAAA,EAAS,iBAAmB,IAAA,CACrDI,CAAAA,CAAO,cAAA,CAAiB,CACtB,GAAGN,EAAAA,CACH,GAAIE,CAAAA,EAAS,cAAA,EAAkB,EACjC,CAAA,CACAI,CAAAA,CAAO,SAAA,CAAYJ,GAAS,SAAA,EAAa,IAAA,CACzCI,CAAAA,CAAO,QAAA,CAAWJ,GAAS,QAAA,EAAY,IAAA,CACvC,IAAMK,CAAAA,CAAaD,EAAO,UAAA,CAC1B,OAAAA,EAAO,UAAA,CAAaJ,CAAAA,EAAS,YAAc,IAAA,CAC3CI,CAAAA,CAAO,cAAA,CAAiBJ,CAAAA,EAAS,gBAAkBK,CAAAA,CAAW,OAAA,CAAQ,SAAA,CAAW,OAAO,EACxFD,CAAAA,CAAO,gBAAA,CAAmBJ,CAAAA,EAAS,gBAAA,EAAoB,KAEhD,MAAA,CAAO,MAAA,CAAOI,CAAM,CAC7B,CCpDO,IAAME,CAAAA,CAAiB,QCIvB,SAASC,CAAAA,CACdC,EACAC,CAAAA,CACAC,CAAAA,CACe,CACf,GAAI,CAEF,IAAMC,CAAAA,CADUC,gBAAaJ,CAAAA,CAAU,OAAO,EACxB,KAAA,CAAM;AAAA,CAAI,CAAA,CAEhC,GAAIC,CAAAA,CAAO,CAAA,EAAKA,EAAOE,CAAAA,CAAM,MAAA,CAAQ,OAAO,IAAA,CAE5C,IAAME,CAAAA,CAAY,IAAA,CAAK,GAAA,CAAI,EAAGJ,CAAAA,CAAOC,CAAY,CAAA,CAC3CI,CAAAA,CAAU,IAAA,CAAK,GAAA,CAAIH,CAAAA,CAAM,MAAA,CAAQF,EAAOC,CAAY,CAAA,CAEpDK,CAAAA,CAAyB,EAAC,CAChC,IAAA,IAASC,CAAAA,CAAIH,CAAAA,CAAWG,GAAKF,CAAAA,CAASE,CAAAA,EAAAA,CAAK,CACzC,IAAMC,CAAAA,CAASD,CAAAA,GAAMP,CAAAA,CAAO,GAAA,CAAM,IAC5BS,CAAAA,CAAU,MAAA,CAAOF,CAAC,CAAA,CAAE,QAAA,CAAS,MAAA,CAAOF,CAAO,CAAA,CAAE,OAAQ,GAAG,CAAA,CAC9DC,CAAAA,CAAa,IAAA,CAAK,CAAA,EAAGE,CAAM,CAAA,CAAA,EAAIC,CAAO,MAAMP,CAAAA,CAAMK,CAAAA,CAAI,CAAC,CAAC,CAAA,CAAE,EAC5D,CAEA,OAAOD,EAAa,IAAA,CAAK;AAAA,CAAI,CAC/B,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CCpBO,SAASI,CAAAA,CACdC,CAAAA,CAC0B,CAC1B,OAAQC,CAAAA,EAAyB,CAC/B,IAAIC,CAAAA,CAASD,CAAAA,CACb,IAAA,IAAWE,CAAAA,IAAWH,CAAAA,CACpB,GAAI,OAAOG,CAAAA,EAAY,QAAA,CAAU,CAE/B,IAAMC,CAAAA,CAAUD,CAAAA,CAAQ,QAAQ,qBAAA,CAAuB,MAAM,CAAA,CAC7DD,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,IAAI,MAAA,CAAOE,CAAAA,CAAS,GAAG,CAAA,CAAG,YAAY,EAChE,CAAA,KAAO,CAEL,IAAMC,CAAAA,CAAQF,CAAAA,CAAQ,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,CAAIA,CAAAA,CAAQ,KAAA,CAAQA,CAAAA,CAAQ,KAAA,CAAQ,GAAA,CACtEG,CAAAA,CAAS,IAAI,MAAA,CAAOH,EAAQ,MAAA,CAAQE,CAAK,CAAA,CAC/CH,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQI,CAAAA,CAAQ,YAAY,EAC9C,CAEF,OAAOJ,CACT,CACF,CCtBA,SAASK,EAAAA,CAAoBC,CAAAA,CAA4D,CACvF,OAAIA,CAAAA,CAAI,cAAA,GAAmB,MAAA,CAAe,IAAA,CACnC,CACL,QAAA,CAAU,gBAAA,CACV,OAAA,CAASA,CAAAA,CAAI,aAAA,EAAiB,IAAA,CAC9B,UAAWA,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC7B,MAAA,CAAQA,CAAAA,CAAI,eAAA,EAAmB,IACjC,CACF,CAEA,SAASC,EAAAA,CAAeD,CAAAA,CAA4D,CAClF,OAAIA,CAAAA,CAAI,SAAA,GAAc,MAAA,CAAe,IAAA,CAC9B,CACL,QAAA,CAAU,WAAA,CACV,OAAA,CAASA,CAAAA,CAAI,cAAA,EAAkB,IAAA,CAC/B,SAAA,CAAWA,CAAAA,CAAI,aAAA,EAAiB,IAAA,CAChC,MAAA,CAAQA,EAAI,gBAAA,EAAoBA,CAAAA,CAAI,kBAAA,EAAsB,IAC5D,CACF,CAEA,SAASE,EAAAA,CAAcF,CAAAA,CAA4D,CACjF,GAAI,CAACA,CAAAA,CAAI,WAAA,CAAa,OAAO,IAAA,CAC7B,IAAIG,CAAAA,CAASH,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC/B,OAAIG,CAAAA,EAAQ,UAAA,CAAW,SAAS,CAAA,GAC9BA,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAAM,CAAgB,GAEjC,CACL,QAAA,CAAU,SAAA,CACV,OAAA,CAASH,CAAAA,CAAI,QAAA,EAAY,IAAA,CACzB,SAAA,CAAWA,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC7B,MAAA,CAAAG,CACF,CACF,CAEA,SAASC,EAAAA,CAAeJ,CAAAA,CAA4D,CAClF,OAAIA,CAAAA,CAAI,QAAA,GAAa,MAAA,CAAe,IAAA,CAC7B,CACL,QAAA,CAAU,UAAA,CACV,OAAA,CAASA,CAAAA,CAAI,gBAAA,EAAoB,KACjC,SAAA,CAAWA,CAAAA,CAAI,WAAA,EAAe,IAAA,CAC9B,MAAA,CAAQA,CAAAA,CAAI,aAAA,EAAiB,IAC/B,CACF,CAEA,IAAMK,EAAAA,CAAwB,CAC5BN,EAAAA,CACAE,EAAAA,CACAC,EAAAA,CACAE,EACF,CAAA,CAEO,SAASE,CAAAA,CAASN,CAAAA,CAA6D,CACpF,IAAMO,CAAAA,CAAiB,OAAA,CAAQ,GAAA,CAC/B,IAAA,IAAWC,CAAAA,IAAUH,EAAAA,CAAW,CAC9B,IAAMX,CAAAA,CAASc,CAAAA,CAAOD,CAAO,CAAA,CAC7B,GAAIb,CAAAA,CAAQ,OAAOA,CACrB,CACA,OAAO,IACT,CC9DO,IAAMe,CAAAA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACEZ,CAAA,CAAA,IAAMC,CAAAA,CAAe,CAAA;AAAA;AAAA;AAAA;AAAA,8BAAA,CAAA,CAMfC,CAAAA,CAAW,CAAA;AAAA;AAAA;AAAA;ACRjB,8BAAA,CAAA,CAAA,IAAMC,CAAAA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;ACDlB,CAAA,CAAA,IAAMC,CAAAA,CAAa;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACInB,CAAA,CAAA,IAAMC,CAAAA,CAAa;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;ACDnB,CAAA,CAAA,IAAMC,CAAAA,CAAkB;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACO/B,CAAA,CAAA,IAAMC,EAAAA,CAAK;AAAA;AAAA;AAAA,EAAA,EAGPJ,CAAS;AAAA,EAAA,EACTC,CAAU;AAAA,EAAA,EACVC,CAAU;AAAA,EAAA,EACVC,CAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAAA,CAAA,CASZ,SAASE,CAAAA,CAAmBC,CAAAA,CAA4B,CAG7D,IAAMC,CAAAA,CAAWD,CAAAA,CAAW,OAAA,CAAQ,MAAA,CAAQ,MAAM,CAAA,CAClD,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sEAAA,EAQ+D,OAAO,IAAA,CAAKR,CAAY,CAAA,CAAE,QAAA,CAAS,QAAQ,CAAC,CAAA;AAAA,OAAA,EAC3GD,CAAG,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAA,EAOJE,CAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iDAAA,EA0BmCQ,CAAQ,CAAA;AAAA,QAAA,EACjDH,EAAE,CAAA;AAAA;AAAA,OAAA,CAGZ,CCtEO,SAASI,EAAcxC,CAAAA,CAAwB,CACpD,GAAI,CACF,IAAMyC,EAAW,OAAA,CAAQ,QAAA,CACrBC,EAEAD,CAAAA,GAAa,QAAA,CACfC,EAAU,CAAA,MAAA,EAAS1C,CAAQ,CAAA,CAAA,CAAA,CAClByC,CAAAA,GAAa,QACtBC,CAAAA,CAAU,CAAA,UAAA,EAAa1C,CAAQ,CAAA,CAAA,CAAA,CAE/B0C,CAAAA,CAAU,aAAa1C,CAAQ,CAAA,CAAA,CAAA,CAGjC2C,mBAAKD,CAAAA,CAAUE,CAAAA,EAAQ,CACjBA,CAAAA,EACF,OAAA,CAAQ,OAAO,KAAA,CACb,CAAA,oCAAA,EAAuCA,EAAI,OAAO;AAAA,CACpD,EAEJ,CAAC,EACH,MAAQ,CAER,CACF,CCwBO,SAASC,EAAAA,CAAmBC,EAA+B,CAChE,IAAMR,EAAa,IAAA,CAAK,SAAA,CAAUQ,CAAM,CAAA,CACxC,OAAOT,EAAmBC,CAAU,CACtC,CAEO,SAASS,CAAAA,CAAgBD,EAAuBE,CAAAA,CAA8B,CACnF,GAAI,CACF,IAAMC,EAAOJ,EAAAA,CAAmBC,CAAM,EAChCjD,CAAAA,CAAamD,CAAAA,CAAO,eACpBE,CAAAA,CAAMC,YAAAA,CAAQtD,CAAU,CAAA,CAE9BuD,YAAAA,CAAUF,EAAK,CAAE,SAAA,CAAW,CAAA,CAAK,CAAC,EAGlC,IAAMG,CAAAA,CAAUxD,EAAa,MAAA,CAK7B,GAJAyD,iBAAcD,CAAAA,CAASJ,CAAAA,CAAM,OAAO,CAAA,CACpCM,aAAAA,CAAWF,EAASxD,CAAU,CAAA,CAG1BmD,EAAO,UAAA,EAAcF,CAAAA,CAAO,KAAO,IAAA,CAAM,CAC3C,IAAMU,CAAAA,CAAeC,YAAAA,CAAQ5D,CAAU,CAAA,CACvC2C,CAAAA,CAAcgB,CAAY,EAC5B,CACF,OAASZ,CAAAA,CAAK,CAEZ,QAAQ,MAAA,CAAO,KAAA,CACb,4CAA4CA,CAAAA,YAAe,KAAA,CAAQA,EAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAC;AAAA,CAC9F,EACF,CACF,CCzDO,SAASc,EAAAA,CAAmBC,CAAAA,CAAuB,CACxD,IAAIC,CAAAA,CAAOD,EACR,OAAA,CAAQ,mBAAA,CAAqB,GAAG,CAAA,CAChC,QAAQ,MAAA,CAAQ,GAAG,CAAA,CACnB,OAAA,CAAQ,SAAU,GAAG,CAAA,CACrB,OAAA,CAAQ,UAAA,CAAY,EAAE,CAAA,CAEzB,OAAIC,CAAAA,CAAK,OAAS,GAAA,GAChBA,CAAAA,CAAOA,CAAAA,CAAK,SAAA,CAAU,EAAG,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAA,CAAA,CAG1CA,CAAAA,EAAQ,cACjB,CAOO,SAASC,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,EACAC,CAAAA,CACsB,CACtB,IAAMC,CAAAA,CAAaJ,EAAY,IAAA,CAC5BK,CAAAA,EAAMA,CAAAA,CAAE,IAAA,GAAS,cAAgBA,CAAAA,CAAE,IACtC,CAAA,CACMC,CAAAA,CAAQN,CAAAA,CAAY,IAAA,CACvBK,CAAAA,EAAMA,CAAAA,CAAE,OAAS,OAAA,EAAWA,CAAAA,CAAE,IACjC,CAAA,CAEA,GAAI,CAACD,CAAAA,EAAc,CAACE,CAAAA,CAClB,OAAO,IAAA,CAGT,IAAIC,CAAAA,CAAaX,EAAAA,CAAmBK,CAAS,CAAA,CACzCC,CAAAA,CAAa,CAAA,GACfK,GAAc,CAAA,QAAA,EAAWL,CAAU,CAAA,CAAA,CAAA,CAGrC,IAAMM,EAAcC,SAAAA,CAAKN,CAAAA,CAAW,WAAA,CAAaI,CAAU,EACrDvD,CAAAA,CAAkD,EAAC,CAEzD,GAAI,CACFsC,YAAAA,CAAUkB,CAAAA,CAAa,CAAE,UAAW,CAAA,CAAK,CAAC,EAC5C,CAAA,KAAQ,CAEN,OAAO,IACT,CAEA,GAAIJ,GAAY,IAAA,CACd,GAAI,CACF,GAAIM,aAAAA,CAAWN,CAAAA,CAAW,IAAI,CAAA,CAAG,CAE/B,IAAMO,CAAAA,CAAW,CAAA,UAAA,EADLC,YAAAA,CAAQR,EAAW,IAAI,CAAA,EAAK,MACP,CAAA,CAAA,CACjCS,gBAAaT,CAAAA,CAAW,IAAA,CAAMK,SAAAA,CAAKD,CAAAA,CAAaG,CAAQ,CAAC,CAAA,CACzD3D,CAAAA,CAAO,WAAa,CAAA,UAAA,EAAauD,CAAU,CAAA,CAAA,EAAII,CAAQ,GACzD,CACF,CAAA,KAAQ,CAER,CAGF,GAAIL,CAAAA,EAAO,IAAA,CACT,GAAI,CACF,GAAII,aAAAA,CAAWJ,CAAAA,CAAM,IAAI,EAAG,CAE1B,IAAMK,CAAAA,CAAW,CAAA,KAAA,EADLC,aAAQN,CAAAA,CAAM,IAAI,CAAA,EAAK,OACP,GAC5BO,eAAAA,CAAaP,CAAAA,CAAM,IAAA,CAAMG,SAAAA,CAAKD,CAAAA,CAAaG,CAAQ,CAAC,CAAA,CACpD3D,EAAO,KAAA,CAAQ,CAAA,UAAA,EAAauD,CAAU,CAAA,CAAA,EAAII,CAAQ,CAAA,EACpD,CACF,CAAA,KAAQ,CAER,CAGF,OAAI,CAAC3D,CAAAA,CAAO,UAAA,EAAc,CAACA,CAAAA,CAAO,KAAA,CACzB,IAAA,CAGFA,CACT,CC9BA,SAAS8D,CAAAA,CAAoBC,CAAAA,CAA4B,CACvD,OAAQA,CAAAA,EACN,KAAK,SAAU,OAAO,QAAA,CACtB,KAAK,QAAA,CAAU,OAAO,QAAA,CACtB,KAAK,UAAA,CAAY,OAAO,UAAA,CACxB,KAAK,SAAA,CAAW,OAAO,UACvB,KAAK,aAAA,CAAe,OAAO,QAAA,CAC3B,QAAS,OAAO,QAClB,CACF,CAEA,SAASC,EAAAA,CAAe9E,CAAAA,CAAkB+E,CAAAA,CAAmBpB,EAAuB,CAClF,IAAMqB,CAAAA,CAAQ,CAAA,EAAGhF,CAAQ,CAAA,EAAA,EAAK+E,CAAS,CAAA,EAAA,EAAKpB,CAAK,GACjD,OAAOsB,iBAAAA,CAAW,QAAQ,CAAA,CAAE,MAAA,CAAOD,CAAK,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,SAAA,CAAU,CAAA,CAAG,EAAE,CACzE,CAEA,SAASE,EAAAA,CAAaC,CAAAA,CAA6B,CAGjD,OAAIA,CAAAA,CAAU,MAAA,EAAU,CAAA,CAAU,EAAA,CAC3BA,CAAAA,CAAUA,CAAAA,CAAU,MAAA,CAAS,CAAC,CACvC,CAEA,SAASC,EAAAA,CAAeC,EAAwC,CAC9D,IAAMC,CAAAA,CAAcD,CAAAA,CAAQ,UAAWE,CAAAA,EAAMA,CAAAA,CAAE,MAAA,GAAW,QAAQ,CAAA,CAClE,OAAOD,CAAAA,CAAc,CAAA,CAAI,mBAAmBA,CAAW,CAAA,CAAA,CAAK,IAC9D,CAEA,SAASE,EAAAA,CAAYC,CAAAA,CAAgBzF,CAAAA,CAA4B,CAC/D,IAAM0F,CAAAA,CAAwB,CAAC,KAAA,CAAO,KAAA,CAAO,MAAM,CAAA,CACnD,IAAA,IAAWC,CAAAA,IAAQD,EACjB,GAAID,CAAAA,CAAK,IAAA,CAAMG,CAAAA,EAAQA,IAAQ,CAAA,CAAA,EAAID,CAAI,CAAA,CAAA,EAAMC,CAAAA,GAAQD,CAAI,CAAA,CACvD,OAAOA,CAAAA,CAGX,IAAA,IAAWA,CAAAA,IAAQD,CAAAA,CACjB,GAAI1F,CAAAA,CAAS,SAAS,CAAA,CAAA,EAAI2F,CAAI,CAAA,CAAA,CAAG,CAAA,CAC/B,OAAOA,CAAAA,CAGX,OAAO,SACT,KA2BqBE,CAAAA,CAArB,KAAuC,CAOrC,WAAA,CAAYrG,CAAAA,CAAmC,CAL/C,IAAA,CAAQ,OAAA,CAAU,GAClB,IAAA,CAAQ,SAAA,CAAY,EAAA,CACpB,IAAA,CAAQ,UAAY,EAAA,CACpB,IAAA,CAAQ,cAAA,CAAkC,GAGxC,IAAA,CAAK,MAAA,CAASD,CAAAA,CAAcC,CAAO,EACrC,CAEA,OAAA,CAAQwD,CAAAA,CAAsB8C,EAAuB,CACnD,GAAI,CACF,IAAA,CAAK,QAAU9C,CAAAA,CAAO,OAAA,CACtB,IAAA,CAAK,SAAA,CAAY,IAAI,IAAA,EAAK,CAAE,WAAA,EAAY,CACxC,IAAA,CAAK,SAAA,CAAY,IAAA,CAAK,MAAA,CAAO,WAAa+C,iBAAAA,GAC5C,CAAA,KAAQ,CAER,CACF,CAEA,SAAA,CAAUC,CAAAA,CAAkBlF,CAAAA,CAA4B,CACtD,GAAI,CACF,IAAMmF,CAAAA,CAAanF,CAAAA,CACboF,CAAAA,CAAUF,CAAAA,CAAK,OAAA,GAGjBnB,CAAAA,CACAqB,CAAAA,GAAY,OAAA,CACdrB,CAAAA,CAAS,QACAqB,CAAAA,GAAY,SAAA,CACrBrB,CAAAA,CAAS,SAAA,CAETA,EAASD,CAAAA,CAAoBqB,CAAAA,CAAW,MAAM,CAAA,CAGhD,IAAME,CAAAA,CAAYF,CAAAA,CAAW,SAAA,CAAU,aAAY,CAC7CG,CAAAA,CAAc,IAAI,IAAA,CAAKH,EAAW,SAAA,CAAU,OAAA,EAAQ,CAAIA,CAAAA,CAAW,QAAQ,CAAA,CAAE,WAAA,EAAY,CAGzFR,CAAAA,CAAOO,CAAAA,CAAK,IAAA,CACd,CAAC,GAAGA,EAAK,IAAI,CAAA,CACbA,CAAAA,CAAK,WAAA,CACF,OAAQ7B,CAAAA,EAAMA,CAAAA,CAAE,IAAA,GAAS,KAAK,EAC9B,GAAA,CAAKA,CAAAA,EAAMA,CAAAA,CAAE,WAAA,EAAe,EAAE,CAAA,CAG/BkC,CAAAA,CAAcL,CAAAA,CAAK,YACtB,MAAA,CAAQ7B,CAAAA,EAAMA,CAAAA,CAAE,IAAA,GAAS,yBAA2BA,CAAAA,CAAE,WAAW,CAAA,CACjE,GAAA,CAAKA,GAAM,CACV,GAAI,CACF,OAAO,IAAA,CAAK,KAAA,CAAMA,CAAAA,CAAE,WAAY,CAClC,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CAAC,CAAA,CACA,MAAA,CAAQmC,CAAAA,EAAiCA,IAAM,IAAI,CAAA,CAGlDC,CAAAA,CAAmD,IAAA,CACjDC,CAAAA,CAAuBR,CAAAA,CAAK,WAAA,CAAY,IAAA,CAC3C7B,GAAMA,CAAAA,CAAE,IAAA,GAAS,8BAAA,EAAkCA,CAAAA,CAAE,WACxD,CAAA,CACA,GAAIqC,CAAAA,CACF,GAAI,CACFD,CAAAA,CAAkB,IAAA,CAAK,KAAA,CAAMC,CAAAA,CAAqB,WAAY,EAChE,CAAA,KAAQ,CAER,CAIF,IAAIC,CAAAA,CAAoC,IAAA,CACxC,GAAI5B,IAAW,QAAA,EAAYA,CAAAA,GAAW,OAAA,CAAS,CAI7C,IAAM6B,CAAAA,CAAAA,CAHS7B,CAAAA,GAAW,OAAA,CACrBmB,CAAAA,CAAK,OAAA,CAAQ,IAAA,CAAMT,CAAAA,EAAMA,CAAAA,CAAE,SAAW,QAAQ,CAAA,EAAG,MAAA,EAAU,GAC5DU,CAAAA,CAAW,MAAA,EACW,CAAC,CAAA,CAC3B,GAAIS,CAAAA,CAAY,CACd,IAAMC,CAAAA,CAAShG,CAAAA,CAAe,IAAA,CAAK,MAAA,CAAO,cAAc,EAClDiG,CAAAA,CAAYF,CAAAA,CAAW,QAAA,EAAU,IAAA,EAAQ,KAC3CG,CAAAA,CAA6B,IAAA,CAC7B,IAAA,CAAK,MAAA,CAAO,qBAAuBD,CAAAA,GAAc,IAAA,EAAQF,CAAAA,CAAW,QAAA,EAAU,OAChFG,CAAAA,CAAc9G,CAAAA,CACZ2G,CAAAA,CAAW,QAAA,CAAS,KACpBE,CAAAA,CACA,IAAA,CAAK,MAAA,CAAO,gBACd,EACIC,CAAAA,GAAaA,CAAAA,CAAcF,CAAAA,CAAOE,CAAW,IAGnDJ,CAAAA,CAAU,CACR,OAAA,CAASE,CAAAA,CAAOD,CAAAA,CAAW,OAAA,EAAW,eAAe,CAAA,CACrD,KAAME,CAAAA,CACN,IAAA,CAAMC,CAAAA,CACN,KAAA,CAAO,KAAK,MAAA,CAAO,iBAAA,EAAqBH,CAAAA,CAAW,KAAA,CAAQC,EAAOD,CAAAA,CAAW,KAAK,CAAA,CAAY,IAChG,EACF,CACF,CAEA,IAAMvB,EAAYa,CAAAA,CAAK,SAAA,EAAU,CAAE,MAAA,CAAO,OAAO,CAAA,CAC3Cc,CAAAA,CAAWC,aAAAA,CAAS,IAAA,CAAK,SAAW,GAAA,CAAKf,CAAAA,CAAK,QAAA,CAAS,IAAI,CAAA,CAG3DjB,CAAAA,CAAYG,EAAAA,CAAaC,CAAS,EAClCxB,CAAAA,CAAQwB,CAAAA,CAAU,IAAA,CAAK,KAAK,EAC5BnF,CAAAA,CAAW8G,CAAAA,CACXE,CAAAA,CAASlC,EAAAA,CAAe9E,EAAU+E,CAAAA,CAAWpB,CAAK,CAAA,CAClDsD,CAAAA,CAAWzB,EAAAA,CAAYC,CAAAA,CAAMzF,CAAQ,CAAA,CACrCkH,EAAUhB,CAAAA,GAAY,OAAA,CACtBiB,CAAAA,CAAc/B,EAAAA,CAAeY,EAAK,OAAO,CAAA,CACzCoB,CAAAA,CAAiBxC,CAAAA,CAAoBoB,EAAK,cAAc,CAAA,CACxDqB,CAAAA,CAAezC,CAAAA,CAAoBqB,CAAAA,CAAW,MAAM,CAAA,CAGtDqB,CAAAA,CAAkC,KACtC,GAAI,IAAA,CAAK,MAAA,CAAO,gBAAA,EAAoBzC,IAAW,SAAA,EAAaoB,CAAAA,CAAW,WAAA,CAAa,CAClF,IAAMhC,CAAAA,CAAYd,YAAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,UAAU,CAAA,CAC1CoE,CAAAA,CAAYpC,CAAAA,CAAUA,EAAU,MAAA,CAAS,CAAC,CAAA,EAAKa,CAAAA,CAAK,MAC1DsB,CAAAA,CAAYzD,CAAAA,CAAcoC,CAAAA,CAAW,WAAA,CAAasB,EAAWtB,CAAAA,CAAW,KAAA,CAAOhC,CAAS,EAC1F,CAEA,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,CACvB,SAAA,CAAAkB,CAAAA,CACA,KAAA,CAAAxB,CAAAA,CACA,OAAAkB,CAAAA,CACA,QAAA,CAAUoB,CAAAA,CAAW,QAAA,CACrB,UAAAE,CAAAA,CACA,WAAA,CAAAC,CAAAA,CACA,UAAA,CAAYJ,CAAAA,CAAK,OAAA,CAAQ,MAAA,CAAS,CAAA,CAClC,KAAAP,CAAAA,CACA,OAAA,CAAAgB,CAAAA,CACA,QAAA,CAAAK,EACA,WAAA,CAAAT,CAAAA,CACA,MAAA,CAAAW,CAAAA,CACA,SAAAhH,CAAAA,CACA,SAAA,CAAA+E,CAAAA,CACA,QAAA,CAAAkC,CAAAA,CACA,OAAA,CAAAC,CAAAA,CACA,WAAA,CAAAC,EACA,cAAA,CAAAC,CAAAA,CACA,YAAA,CAAAC,CAAAA,CACA,UAAAC,CAAAA,CACA,eAAA,CAAAf,CACF,CAAC,EACH,CAAA,KAAQ,CAER,CACF,CAEA,KAAA,CAAMiB,CAAAA,CAA6B,CACjC,GAAI,CACF,IAAMpB,CAAAA,CAAc,IAAI,IAAA,GAAO,WAAA,EAAY,CACrCqB,CAAAA,CAAgB,IAAI,KAAK,IAAA,CAAK,SAAS,CAAA,CAAE,OAAA,EAAQ,CAEjDC,CAAAA,CADkB,IAAI,IAAA,CAAKtB,CAAW,CAAA,CAAE,OAAA,EAAQ,CACdqB,CAAAA,CAGlCE,EAAW,IAAA,CAAK,aAAA,EAAc,CAG9BC,CAAAA,CAAU,KAAK,YAAA,EAAa,CAE5B9E,CAAAA,CAAwB,CAC5B,aAAA,CAAehD,CAAAA,CACf,SAAA,CAAW,IAAA,CAAK,UAChB,SAAA,CAAW,IAAA,CAAK,SAAA,CAChB,WAAA,CAAAsG,EACA,aAAA,CAAAsB,CAAAA,CACA,OAAA,CAAAE,CAAAA,CACA,GAAIlG,CAAAA,EAAS,CACb,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,QAAA,CACtB,QAAA,CAAAiG,CAAAA,CACA,YAAa,IACf,CAAA,CAEA,IAAA,CAAK,WAAA,CAAY7E,CAAM,CAAA,CACvBC,CAAAA,CAAgBD,CAAAA,CAAQ,IAAA,CAAK,MAAM,EACrC,CAAA,KAAQ,CAER,CACF,CAEA,aAAA,EAAyB,CACvB,OAAO,MACT,CAEQ,aAAA,EAAiC,CACvC,IAAM+E,EAA2B,EAAC,CAElC,IAAA,IAAW7B,CAAAA,IAAQ,KAAK,cAAA,CAAgB,CACtC,GAAIA,CAAAA,CAAK,WAAA,CAAY,MAAA,GAAW,CAAA,CAAG,CAEjC6B,EAAQ,IAAA,CAAK,CACX,GAAA,CAAK,aAAA,CACL,eAAgB,OAAA,CAChB,SAAA,CAAW7B,CAAAA,CAAK,SAAA,CAChB,SAAUA,CAAAA,CAAK,QAAA,CACf,QAAA,CAAUA,CAAAA,CAAK,SACf,kBAAA,CAAoB,IAAA,CACpB,aAAA,CAAe,IAAA,CACf,aAAc,IAAA,CACd,KAAA,CAAO,CAAC,IAAA,CAAK,aAAaA,CAAI,CAAC,CACjC,CAAC,EACD,QACF,CAGA,IAAA,IAASxF,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAIwF,CAAAA,CAAK,WAAA,CAAY,OAAQxF,CAAAA,EAAAA,CAAK,CAChD,IAAMsH,CAAAA,CAAM9B,EAAK,WAAA,CAAYxF,CAAC,CAAA,CACxBuH,CAAAA,CAAU/B,EAAK,WAAA,CAAYxF,CAAAA,CAAI,CAAC,CAAA,CAGtC,GAAI,IAAA,CAAK,MAAA,CAAO,eAAA,GAAoB,MAAQ,CAAC,IAAA,CAAK,MAAA,CAAO,eAAA,CAAgB,SAASsH,CAAAA,CAAI,cAAc,CAAA,CAClG,SAGF,IAAME,CAAAA,CAAU,IAAI,IAAA,CAAKF,CAAAA,CAAI,SAAS,CAAA,CAAE,OAAA,EAAQ,CAI1CG,GAHWF,CAAAA,CACb,IAAI,IAAA,CAAKA,CAAAA,CAAQ,SAAS,CAAA,CAAE,OAAA,EAAQ,CACpC,IAAI,KAAK/B,CAAAA,CAAK,WAAW,CAAA,CAAE,OAAA,EAAQ,EACXgC,CAAAA,CAE5BH,CAAAA,CAAQ,IAAA,CAAK,CACX,GAAA,CAAKC,CAAAA,CAAI,GAAA,CACT,cAAA,CAAgBA,EAAI,cAAA,CACpB,SAAA,CAAWA,CAAAA,CAAI,SAAA,CACf,SAAU,IAAA,CAAK,GAAA,CAAI,CAAA,CAAGG,CAAQ,CAAA,CAC9B,QAAA,CAAUjC,CAAAA,CAAK,QAAA,CACf,mBAAoB8B,CAAAA,CAAI,kBAAA,EAAsB,IAAA,CAC9C,aAAA,CAAeA,EAAI,aAAA,EAAiB,IAAA,CACpC,YAAA,CAAcA,CAAAA,CAAI,cAAgB,IAAA,CAClC,KAAA,CAAO,CAAC,IAAA,CAAK,YAAA,CAAa9B,CAAI,CAAC,CACjC,CAAC,EACH,CACF,CAGA,OAAA6B,EAAQ,IAAA,CAAK,CAAC,CAAA,CAAGK,CAAAA,GAAM,IAAI,IAAA,CAAK,CAAA,CAAE,SAAS,CAAA,CAAE,OAAA,EAAQ,CAAI,IAAI,IAAA,CAAKA,EAAE,SAAS,CAAA,CAAE,OAAA,EAAS,EAEjFL,CACT,CAEQ,YAAA,EAAwB,CAC9B,IAAIM,CAAAA,CAAS,CAAA,CACTC,CAAAA,CAAS,CAAA,CACTC,CAAAA,CAAQ,CAAA,CACRC,CAAAA,CAAU,CAAA,CACVC,EAAW,CAAA,CAEf,IAAA,IAAWvC,CAAAA,IAAQ,IAAA,CAAK,eACtB,OAAQA,CAAAA,CAAK,MAAA,EACX,KAAK,QAAA,CAAUmC,CAAAA,EAAAA,CAAU,MACzB,KAAK,QAAA,CAAUC,CAAAA,EAAAA,CAAU,MACzB,KAAK,QAASC,CAAAA,EAAAA,CAAS,MACvB,KAAK,SAAA,CAAWC,IAAW,MAC3B,KAAK,UAAA,CAAYC,CAAAA,EAAAA,CAAY,KAC/B,CAGF,OAAO,CACL,KAAA,CAAO,IAAA,CAAK,cAAA,CAAe,MAAA,CAC3B,MAAA,CAAAJ,EACA,MAAA,CAAAC,CAAAA,CACA,KAAA,CAAAC,CAAAA,CACA,QAAAC,CAAAA,CACA,QAAA,CAAAC,CACF,CACF,CAEQ,YAAA,CAAavC,CAAAA,CAAmC,CACtD,OAAO,CACL,KAAA,CAAOA,CAAAA,CAAK,KAAA,CACZ,OAAQA,CAAAA,CAAK,MAAA,CACb,QAAA,CAAUA,CAAAA,CAAK,SACf,SAAA,CAAWA,CAAAA,CAAK,SAAA,CAChB,WAAA,CAAaA,EAAK,WAAA,CAClB,UAAA,CAAYA,CAAAA,CAAK,UAAA,CACjB,IAAA,CAAMA,CAAAA,CAAK,IAAA,CACX,OAAA,CAASA,EAAK,OAAA,CACd,MAAA,CAAQA,CAAAA,CAAK,MAAA,CACb,SAAUA,CAAAA,CAAK,QAAA,CACf,SAAA,CAAWA,CAAAA,CAAK,UAChB,QAAA,CAAUA,CAAAA,CAAK,QAAA,CACf,OAAA,CAASA,CAAAA,CAAK,OAAA,CACd,WAAA,CAAaA,CAAAA,CAAK,YAClB,cAAA,CAAgBA,CAAAA,CAAK,cAAA,CACrB,YAAA,CAAcA,EAAK,YAAA,CACnB,SAAA,CAAWA,CAAAA,CAAK,SAAA,CAChB,gBAAiBA,CAAAA,CAAK,eACxB,CACF,CAEQ,WAAA,CAAYlD,CAAAA,CAA6B,CAC/C,GAAI,CACF,IAAM0F,CAAAA,CAAO,IAAA,CAAK,SAAA,CAAU1F,EAAQ,IAAA,CAAM,CAAC,CAAA,CACrCjD,CAAAA,CAAa,KAAK,MAAA,CAAO,UAAA,CACzBqD,CAAAA,CAAMC,YAAAA,CAAQtD,CAAU,CAAA,CAE9BuD,YAAAA,CAAUF,CAAAA,CAAK,CAAE,SAAA,CAAW,CAAA,CAAK,CAAC,CAAA,CAGlC,IAAMG,CAAAA,CAAUxD,CAAAA,CAAa,MAAA,CAC7ByD,gBAAAA,CAAcD,EAASmF,CAAAA,CAAM,OAAO,CAAA,CACpCjF,aAAAA,CAAWF,EAASxD,CAAU,EAChC,CAAA,MAAS+C,CAAAA,CAAK,CAEZ,OAAA,CAAQ,MAAA,CAAO,KAAA,CACb,CAAA,oCAAA,EAAuCA,aAAe,KAAA,CAAQA,CAAAA,CAAI,OAAA,CAAU,MAAA,CAAOA,CAAG,CAAC;AAAA,CACzF,EACF,CACF,CACF,ECnbA,IAAI6F,CAAAA,CAAS,KAAA,CASN,SAASC,EAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CAAiC,eAAA,CAC3B,CACN,GAAI,CAACF,CAAAA,EAAY,CAACA,CAAAA,CAAS,WAAA,CAAa,CACjCF,CAAAA,GACHA,CAAAA,CAAS,IAAA,CACT,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA;AAAA,CAA8E,CAAA,CAAA,CAErG,MACF,CAEA,IAAMK,CAAAA,CAAmC,CACvC,GAAA,CAAAF,CAAAA,CACA,cAAA,CAAAC,CAAAA,CACA,SAAA,CAAW,IAAI,IAAA,EAAK,CAAE,WAAA,EACxB,CAAA,CAEAF,CAAAA,CAAS,WAAA,CAAY,IAAA,CAAK,CACxB,IAAA,CAAM,uBAAA,CACN,WAAA,CAAa,IAAA,CAAK,SAAA,CAAUG,CAAU,CACxC,CAAC,EACH","file":"index.cjs","sourcesContent":["/**\n * Config resolution with prototype pollution prevention.\n * Merges user options into defaults, freezes the result.\n */\n\nimport type { ReporterConfig, NavigationType } from '@testrelic/core';\nimport { isValidConfig, createError, ErrorCode } from '@testrelic/core';\n\nexport const DEFAULT_REDACTION_PATTERNS: (string | RegExp)[] = [\n /AKIA[A-Z0-9]{16}/g,\n /Bearer\\s+[A-Za-z0-9\\-._~+/]+=*/g,\n /-----BEGIN\\s+(RSA\\s+)?PRIVATE\\sKEY-----[\\s\\S]*?-----END/g,\n /\\/\\/[^:]+:[^@]+@/g,\n];\n\nexport interface ResolvedConfig {\n readonly outputPath: string;\n readonly includeStackTrace: boolean;\n readonly includeCodeSnippets: boolean;\n readonly codeContextLines: number;\n readonly includeNetworkStats: boolean;\n readonly navigationTypes: NavigationType[] | null;\n readonly redactPatterns: (string | RegExp)[];\n readonly testRunId: string | null;\n readonly metadata: Record<string, unknown> | null;\n readonly openReport: boolean;\n readonly htmlReportPath: string;\n readonly includeArtifacts: boolean;\n}\n\nexport function resolveConfig(options?: Partial<ReporterConfig>): ResolvedConfig {\n if (options !== undefined && !isValidConfig(options)) {\n throw createError(ErrorCode.CONFIG_INVALID, 'Invalid reporter configuration');\n }\n\n // Prototype pollution prevention: merge onto Object.create(null)\n const target = Object.create(null) as Record<string, unknown>;\n target.outputPath = options?.outputPath ?? './test-results/analytics-timeline.json';\n target.includeStackTrace = options?.includeStackTrace ?? false;\n target.includeCodeSnippets = options?.includeCodeSnippets ?? true;\n target.codeContextLines = options?.codeContextLines ?? 3;\n target.includeNetworkStats = options?.includeNetworkStats ?? true;\n target.navigationTypes = options?.navigationTypes ?? null;\n target.redactPatterns = [\n ...DEFAULT_REDACTION_PATTERNS,\n ...(options?.redactPatterns ?? []),\n ];\n target.testRunId = options?.testRunId ?? null;\n target.metadata = options?.metadata ?? null;\n const outputPath = target.outputPath as string;\n target.openReport = options?.openReport ?? true;\n target.htmlReportPath = options?.htmlReportPath ?? outputPath.replace(/\\.json$/, '.html');\n target.includeArtifacts = options?.includeArtifacts ?? true;\n\n return Object.freeze(target) as unknown as ResolvedConfig;\n}\n","/**\n * JSON schema version for the analytics timeline output.\n */\nexport const SCHEMA_VERSION = '1.2.0';\n","/**\n * Source file reading and code snippet extraction for failure diagnostics.\n * Never throws — returns null on any error.\n */\n\nimport { readFileSync } from 'node:fs';\n\nexport function extractCodeSnippet(\n filePath: string,\n line: number,\n contextLines: number,\n): string | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n if (line < 1 || line > lines.length) return null;\n\n const startLine = Math.max(1, line - contextLines);\n const endLine = Math.min(lines.length, line + contextLines);\n\n const snippetLines: string[] = [];\n for (let i = startLine; i <= endLine; i++) {\n const marker = i === line ? '>' : ' ';\n const lineNum = String(i).padStart(String(endLine).length, ' ');\n snippetLines.push(`${marker} ${lineNum} | ${lines[i - 1]}`);\n }\n\n return snippetLines.join('\\n');\n } catch {\n return null;\n }\n}\n","/**\n * Pattern-based redaction engine.\n * Replaces sensitive data with [REDACTED] before writing reports.\n */\n\nexport const DEFAULT_REDACTION_PATTERNS: (string | RegExp)[] = [\n /AKIA[A-Z0-9]{16}/g,\n /Bearer\\s+[A-Za-z0-9\\-._~+/]+=*/g,\n /-----BEGIN\\s+(RSA\\s+)?PRIVATE\\sKEY-----[\\s\\S]*?-----END/g,\n /\\/\\/[^:]+:[^@]+@/g,\n];\n\nexport function createRedactor(\n patterns: (string | RegExp)[],\n): (text: string) => string {\n return (text: string): string => {\n let result = text;\n for (const pattern of patterns) {\n if (typeof pattern === 'string') {\n // Escape special regex characters and create a global regex\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n result = result.replace(new RegExp(escaped, 'g'), '[REDACTED]');\n } else {\n // Clone the regex to ensure global flag and reset lastIndex\n const flags = pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g';\n const cloned = new RegExp(pattern.source, flags);\n result = result.replace(cloned, '[REDACTED]');\n }\n }\n return result;\n };\n}\n","/**\n * CI provider auto-detection via environment variables.\n * Strategy pattern: check providers in priority order, first match wins.\n */\n\nimport type { CIMetadata, CIProvider } from '@testrelic/core';\n\ntype DetectFn = (env: Record<string, string | undefined>) => CIMetadata | null;\n\nfunction detectGitHubActions(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.GITHUB_ACTIONS !== 'true') return null;\n return {\n provider: 'github-actions',\n buildId: env.GITHUB_RUN_ID ?? null,\n commitSha: env.GITHUB_SHA ?? null,\n branch: env.GITHUB_REF_NAME ?? null,\n };\n}\n\nfunction detectGitLabCI(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.GITLAB_CI !== 'true') return null;\n return {\n provider: 'gitlab-ci',\n buildId: env.CI_PIPELINE_ID ?? null,\n commitSha: env.CI_COMMIT_SHA ?? null,\n branch: env.CI_COMMIT_BRANCH ?? env.CI_COMMIT_REF_NAME ?? null,\n };\n}\n\nfunction detectJenkins(env: Record<string, string | undefined>): CIMetadata | null {\n if (!env.JENKINS_URL) return null;\n let branch = env.GIT_BRANCH ?? null;\n if (branch?.startsWith('origin/')) {\n branch = branch.slice('origin/'.length);\n }\n return {\n provider: 'jenkins',\n buildId: env.BUILD_ID ?? null,\n commitSha: env.GIT_COMMIT ?? null,\n branch,\n };\n}\n\nfunction detectCircleCI(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.CIRCLECI !== 'true') return null;\n return {\n provider: 'circleci',\n buildId: env.CIRCLE_BUILD_NUM ?? null,\n commitSha: env.CIRCLE_SHA1 ?? null,\n branch: env.CIRCLE_BRANCH ?? null,\n };\n}\n\nconst detectors: DetectFn[] = [\n detectGitHubActions,\n detectGitLabCI,\n detectJenkins,\n detectCircleCI,\n];\n\nexport function detectCI(env?: Record<string, string | undefined>): CIMetadata | null {\n const envVars = env ?? process.env;\n for (const detect of detectors) {\n const result = detect(envVars);\n if (result) return result;\n }\n return null;\n}\n","/**\n * Client-side CSS for TestRelic Report.\n * Covers layout, theming, filter bar, test grid, drawer, network DevTools,\n * and all component styles.\n */\nexport const CSS = `\n/* Theme Variables */\n:root,[data-theme=\"dark\"]{\n --bg:#0f1117;--bg-1:#161b22;--bg-2:#1c2128;--bg-3:#21262d;--bg-code:#13111c;\n --fg:#e6edf3;--fg-1:#8b949e;--fg-2:#484f58;--fg-code:#d4d4d4;--fg-err:#f8d7da;\n --bd:rgba(255,255,255,0.06);--bd-s:rgba(255,255,255,0.04);--bd-m:rgba(255,255,255,0.08);--bd-l:rgba(255,255,255,0.1);--bd-xs:rgba(255,255,255,0.03);\n --hvr:rgba(255,255,255,0.025);--hvr-s:rgba(255,255,255,0.02);\n --overlay-bg:rgba(0,0,0,.55);--shadow-c:rgba(0,0,0,.35);\n --lb-bg:rgba(0,0,0,.92);--lb-shadow:rgba(0,0,0,.6);--lb-btn:rgba(255,255,255,.1);--lb-btn-h:rgba(255,255,255,.2);\n --scroll-thumb:#21262d;\n}\n[data-theme=\"light\"]{\n --bg:#ffffff;--bg-1:#f6f8fa;--bg-2:#eaeef2;--bg-3:#d0d7de;--bg-code:#f6f8fa;\n --fg:#1f2328;--fg-1:#656d76;--fg-2:#8b949e;--fg-code:#24292e;--fg-err:#82071e;\n --bd:rgba(0,0,0,0.08);--bd-s:rgba(0,0,0,0.04);--bd-m:rgba(0,0,0,0.12);--bd-l:rgba(0,0,0,0.15);--bd-xs:rgba(0,0,0,0.03);\n --hvr:rgba(0,0,0,0.04);--hvr-s:rgba(0,0,0,0.03);\n --overlay-bg:rgba(0,0,0,.3);--shadow-c:rgba(0,0,0,.1);\n --lb-bg:rgba(0,0,0,.85);--lb-shadow:rgba(0,0,0,.3);--lb-btn:rgba(0,0,0,.12);--lb-btn-h:rgba(0,0,0,.2);\n --scroll-thumb:#d0d7de;\n}\n\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nbody{\n font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;\n background:var(--bg);color:var(--fg);line-height:1.5;\n -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;\n}\n\n/* ── Centered Container ── */\n.wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n/* ── Header ── */\n.top-bar{display:flex;align-items:center;gap:14px;margin-bottom:20px;flex-wrap:wrap}\n.brand{display:flex;align-items:center;gap:10px}\n.brand svg{flex-shrink:0}\n.brand-text{display:flex;flex-direction:column}\n.brand-title{font-size:17px;font-weight:700;color:var(--fg);line-height:1.2}\n.brand-sub{font-size:10px;font-weight:600;color:#03b79c;letter-spacing:.06em;text-transform:uppercase;margin-top:1px}\n.run-meta{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:11px;color:var(--fg-1);margin-left:auto}\n.run-meta-item{display:flex;align-items:center;gap:4px}\n.run-meta-label{font-weight:600}\n.run-id-tag{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;background:var(--bg-2);padding:1px 6px;border-radius:4px;color:var(--fg-1)}\n.ci-tag{display:inline-flex;align-items:center;gap:4px;background:rgba(59,130,246,0.12);color:#60a5fa;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600}\n.merged-tag{background:rgba(245,158,11,0.12);color:#fbbf24;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600}\n\n/* ── Theme Toggle ── */\n.theme-toggle{display:inline-flex;border:1px solid var(--bd-m);border-radius:8px;overflow:hidden;background:var(--bg);flex-shrink:0}\n.theme-btn{padding:6px 10px;border:none;background:transparent;color:var(--fg-2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;font-family:inherit}\n.theme-btn:not(:last-child){border-right:1px solid var(--bd-m)}\n.theme-btn.active{background:var(--bg-3);color:var(--fg)}\n.theme-btn:hover:not(.active){color:var(--fg-1);background:var(--hvr)}\n.theme-btn svg{width:14px;height:14px}\n\n/* ── CTA Button ── */\n.cta-btn{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:500;color:#e6edf3;background:linear-gradient(135deg,rgba(3,183,156,0.15),rgba(14,165,233,0.15));border:1px solid rgba(3,183,156,0.3);padding:6px 14px;border-radius:8px;text-decoration:none;white-space:nowrap;flex-shrink:0;transition:all .2s ease;letter-spacing:.01em}\n.cta-btn:hover{background:linear-gradient(135deg,rgba(3,183,156,0.25),rgba(14,165,233,0.25));border-color:rgba(3,183,156,0.5);box-shadow:0 0 16px rgba(3,183,156,0.15);color:#fff}\n.cta-btn strong{font-weight:700;color:#2dd4a8}\n.cta-btn:hover strong{color:#5eead4}\n.cta-icon{width:13px;height:13px;color:#2dd4a8;flex-shrink:0}\n.cta-sep{color:var(--fg-2);margin:0 1px}\n.cta-arrow{width:11px;height:11px;color:#2dd4a8;flex-shrink:0;transition:transform .2s}\n.cta-btn:hover .cta-arrow{transform:translateX(2px)}\n[data-theme=\"light\"] .cta-btn{color:#1f2328;background:linear-gradient(135deg,rgba(3,183,156,0.08),rgba(14,165,233,0.08));border-color:rgba(3,183,156,0.25)}\n[data-theme=\"light\"] .cta-btn:hover{background:linear-gradient(135deg,rgba(3,183,156,0.15),rgba(14,165,233,0.15));border-color:rgba(3,183,156,0.4);color:#0f172a}\n[data-theme=\"light\"] .cta-btn strong{color:#059669}\n[data-theme=\"light\"] .cta-icon,[data-theme=\"light\"] .cta-arrow{color:#059669}\n\n/* ── Summary Strip ── */\n.summary-strip{display:flex;gap:8px;margin-bottom:20px}\n.summary-chip{flex:1;text-align:center;padding:14px 4px;border-radius:10px;cursor:default;border:1px solid var(--bd-s)}\n.summary-chip .s-count{font-size:22px;font-weight:800;line-height:1}\n.summary-chip .s-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;margin-top:4px;color:var(--fg-2)}\n.s-total{background:rgba(99,102,241,0.06)}.s-total .s-count{color:#818cf8}\n.s-passed{background:rgba(34,197,94,0.06)}.s-passed .s-count{color:#22c55e}\n.s-failed{background:rgba(239,68,68,0.06)}.s-failed .s-count{color:#ef4444}\n.s-flaky{background:rgba(245,158,11,0.06)}.s-flaky .s-count{color:#f59e0b}\n.s-skipped{background:rgba(107,114,128,0.06)}.s-skipped .s-count{color:#6b7280}\n.s-timedout{background:rgba(249,115,22,0.06)}.s-timedout .s-count{color:#f97316}\n\n/* ── Filter Bar ── */\n.filter-bar{display:flex;align-items:center;gap:12px;margin-bottom:24px;flex-wrap:wrap}\n.filter-chips{display:flex;gap:5px;flex-wrap:wrap}\n.filter-chip{\n font-size:12px;font-weight:500;padding:5px 14px;border-radius:9999px;\n border:1px solid var(--bd-m);background:transparent;color:var(--fg-1);\n cursor:pointer;transition:all .15s;font-family:inherit;white-space:nowrap;\n}\n.filter-chip:hover{border-color:#03b79c;color:var(--fg)}\n.filter-chip.active{background:rgba(3,183,156,0.12);border-color:#03b79c;color:#2dd4a8}\n.chip-count{font-weight:700;margin-left:3px}\n.filter-chip--zero{opacity:.35;pointer-events:none}\n.filter-section{display:flex;align-items:center;gap:5px;flex-wrap:wrap}\n.filter-section-label{font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin-right:4px}\n.filter-chip--dimmed{opacity:.45}\n.filter-actions{display:flex;align-items:center;gap:12px;margin-left:auto}\n.filter-clear{font-size:11px;font-weight:600;color:#03b79c;background:none;border:none;cursor:pointer;padding:4px 8px;border-radius:6px;font-family:inherit;transition:background .15s}\n.filter-clear:hover{background:rgba(3,183,156,0.1)}\n.filter-indicator{font-size:11px;color:var(--fg-2)}\n.search-box{position:relative;margin-left:auto;width:220px;flex-shrink:0}\n.search-input{\n width:100%;padding:6px 10px 6px 32px;\n border:1px solid var(--bd-m);border-radius:8px;\n background:var(--bg-1);color:var(--fg);font-size:12px;font-family:inherit;outline:none;transition:border-color .15s;\n}\n.search-input::placeholder{color:var(--fg-2)}\n.search-input:focus{border-color:#03b79c}\n.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--fg-2);pointer-events:none}\n\n/* ── File Groups & Test Rows ── */\n.file-group{margin-bottom:16px}\n.file-group-header{\n font-size:11px;font-weight:600;\n font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;\n color:var(--fg-2);padding:0 4px 6px;\n}\n.file-group-card{\n border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-1);\n}\n.test-row{\n display:flex;align-items:center;gap:12px;padding:12px 18px;cursor:pointer;\n transition:background .1s;border-bottom:1px solid var(--bd-s);\n}\n.test-row:last-child{border-bottom:none}\n.test-row:hover{background:var(--hvr)}\n.test-row.active{background:rgba(3,183,156,0.07)}\n\n.status-indicator{\n width:10px;height:10px;border-radius:50%;flex-shrink:0;\n}\n.si-passed{background:#22c55e}.si-failed{background:#ef4444}\n.si-flaky{background:#f59e0b}.si-skipped{background:#6b7280}.si-timedout{background:#f97316}\n\n.test-row-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}\n.test-row-title{font-size:14px;font-weight:500;color:var(--fg);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.test-row-badges{display:flex;gap:5px;align-items:center;flex-wrap:wrap}\n.trb{font-size:10px;font-weight:600;padding:1px 7px;border-radius:4px}\n.trb-type{background:rgba(139,92,246,0.12);color:#a78bfa}\n.trb-tag{background:rgba(59,130,246,0.12);color:#60a5fa}\n.trb-retry{background:rgba(245,158,11,0.12);color:#fbbf24}\n.trb-flaky{background:rgba(245,158,11,0.12);color:#fbbf24}\n.test-row-dur{font-size:13px;font-weight:700;color:var(--fg-1);flex-shrink:0;white-space:nowrap}\n.test-row-arrow{color:var(--fg-2);flex-shrink:0;transition:color .15s}\n.test-row:hover .test-row-arrow{color:var(--fg-1)}\n\n.no-tests{text-align:center;padding:48px 20px;color:var(--fg-2);font-size:14px}\n\n/* ── Drawer Overlay ── */\n.drawer-backdrop{\n position:fixed;inset:0;background:var(--overlay-bg);z-index:100;\n opacity:0;pointer-events:none;transition:opacity .25s ease;\n}\n.drawer-backdrop.open{opacity:1;pointer-events:auto}\n\n/* ── Drawer Panel ── */\n.drawer{\n position:fixed;top:0;right:0;bottom:0;width:50vw;max-width:50vw;z-index:110;\n background:var(--bg-1);border-left:1px solid var(--bd);\n transform:translateX(100%);transition:transform .3s cubic-bezier(.4,0,.2,1);\n display:flex;flex-direction:column;overflow:hidden;\n box-shadow:-8px 0 40px var(--shadow-c);\n}\n.drawer.open{transform:translateX(0)}\n.drawer-bar{\n display:flex;align-items:center;justify-content:space-between;\n padding:14px 20px;border-bottom:1px solid var(--bd);\n background:linear-gradient(180deg,rgba(3,183,156,0.04) 0%,transparent 100%);flex-shrink:0;\n}\n.drawer-bar-title{font-size:12px;font-weight:600;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em}\n.drawer-close{\n width:32px;height:32px;border-radius:8px;border:1px solid var(--bd-m);\n background:transparent;color:var(--fg-1);font-size:18px;cursor:pointer;\n display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s;\n}\n.drawer-close:hover{background:var(--bd);color:var(--fg)}\n.drawer-body{flex:1;overflow-y:auto;padding:24px 28px;scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}\n.drawer-body::-webkit-scrollbar{width:5px}\n.drawer-body::-webkit-scrollbar-track{background:transparent}\n.drawer-body::-webkit-scrollbar-thumb{background:var(--bg-3);border-radius:3px}\n\n/* ── Drawer Sections ── */\n.drawer-sections{display:flex;flex-direction:column;gap:24px}\n.drawer-section{min-width:0}\n\n/* ── Segmented Control ── */\n.seg-ctrl{display:inline-flex;border:1px solid var(--bd-l);border-radius:8px;overflow:hidden;background:var(--bg);margin-bottom:12px}\n.seg-btn{padding:7px 16px;font-size:11px;font-weight:600;color:var(--fg-2);background:transparent;border:none;cursor:pointer;font-family:inherit;transition:all .15s;white-space:nowrap;display:flex;align-items:center;gap:5px}\n.seg-btn:not(:last-child){border-right:1px solid var(--bd-l)}\n.seg-btn.active{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.seg-btn:hover:not(.active){color:var(--fg-1);background:var(--bd-xs)}\n.seg-pane{display:none}\n.seg-pane.active{display:block;animation:trFadeIn .15s ease-out}\n\n/* ── Artifact Column ── */\n.artifact-col-img{max-width:100%;border-radius:8px;cursor:pointer;border:1px solid var(--bd);transition:border-color .15s,box-shadow .15s;object-fit:contain;display:block}\n.artifact-col-img:hover{border-color:#03b79c;box-shadow:0 0 0 3px rgba(3,183,156,0.15)}\n.artifact-col-caption{font-size:10px;color:var(--fg-2);margin-top:6px;display:flex;align-items:center;gap:4px}\n.artifact-col-caption svg{opacity:.5}\n.artifact-col-video{border-radius:8px;overflow:hidden;border:1px solid var(--bd);background:#000}\n.artifact-col-video video{width:100%;display:block}\n.artifact-empty{padding:24px 14px;text-align:center;font-size:12px;color:var(--fg-2);font-style:italic;border:1px dashed var(--bd-m);border-radius:10px}\n\n/* ── Network Overview ── */\n.net-overview{border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.net-section-hd{padding:8px 12px;font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.06em;border-top:1px solid var(--bd-s);display:flex;align-items:center;gap:6px}\n.net-section-hd svg{opacity:.5}\n\n/* ── Resource Bar Chart ── */\n.net-bar-wrap{padding:10px 12px 4px}\n.net-bar{display:flex;height:20px;border-radius:5px;overflow:hidden;background:var(--bg-3)}\n.net-bar-seg{min-width:2px;position:relative;transition:opacity .15s}\n.net-bar-seg:hover{opacity:.8}\n.net-bar--mini{height:6px;border-radius:3px;margin-bottom:4px}\n.net-bar--mini .net-bar-seg{min-width:1px}\n.net-bar-legend{display:flex;flex-wrap:wrap;gap:4px 12px;padding:4px 12px 8px;font-size:10px}\n.net-legend-item{display:flex;align-items:center;gap:4px;color:var(--fg-1)}\n.net-legend-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}\n.net-legend-count{font-weight:700;color:var(--fg)}\n\n/* ── Network Step Cards ── */\n.net-steps-list{border-top:1px solid var(--bd-s)}\n.net-step-card{padding:10px 12px;border-bottom:1px solid var(--bd-xs)}\n.net-step-card:last-child{border-bottom:none}\n.net-step-card-head{display:flex;align-items:center;gap:8px;margin-bottom:6px}\n.net-step-num{width:20px;height:20px;border-radius:50%;background:var(--bg-3);color:var(--fg-1);font-size:9px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.net-step-url{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.net-step-stats{flex-shrink:0;color:var(--fg-2);font-size:10px;white-space:nowrap}\n.net-step-timing{display:flex;gap:10px;font-size:9px;color:var(--fg-2);flex-wrap:wrap}\n.net-step-timing span{display:flex;align-items:center;gap:3px}\n.timing-label{font-weight:700;text-transform:uppercase;letter-spacing:.04em;font-size:8px}\n.timing-val{font-weight:600;color:var(--fg-1)}\n\n/* ── Failed URL badges ── */\n.fail-url-badge{display:inline-flex;align-items:center;font-size:10px;font-weight:700;padding:1px 5px;border-radius:3px;margin-right:5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.fail-url-badge--4xx{background:rgba(245,158,11,0.12);color:#f59e0b}\n.fail-url-badge--5xx{background:rgba(239,68,68,0.12);color:#ef4444}\n.fail-url-badge--other{background:rgba(107,114,128,0.1);color:#6b7280}\n\n/* ── Drawer Content — Test Detail ── */\n.test-detail{animation:trFadeIn .2s ease-out}\n@keyframes trFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}\n\n.test-detail-header{margin-bottom:22px;padding-bottom:18px;border-bottom:1px solid var(--bd)}\n.test-detail-top{display:flex;align-items:flex-start;gap:12px;margin-bottom:10px}\n.detail-status-badge{\n font-size:11px;font-weight:700;padding:4px 14px;border-radius:9999px;\n text-transform:uppercase;letter-spacing:.04em;flex-shrink:0;margin-top:2px;\n}\n.dbg-passed{background:rgba(34,197,94,0.1);color:#22c55e;border:1px solid rgba(34,197,94,0.2)}\n.dbg-failed{background:rgba(239,68,68,0.1);color:#ef4444;border:1px solid rgba(239,68,68,0.2)}\n.dbg-flaky{background:rgba(245,158,11,0.1);color:#f59e0b;border:1px solid rgba(245,158,11,0.2)}\n.dbg-skipped{background:rgba(107,114,128,0.1);color:#6b7280;border:1px solid rgba(107,114,128,0.2)}\n.dbg-timedout{background:rgba(249,115,22,0.1);color:#f97316;border:1px solid rgba(249,115,22,0.2)}\n\n.detail-title-section{flex:1;min-width:0}\n.detail-title{font-size:16px;font-weight:700;color:var(--fg);line-height:1.35;margin-bottom:6px}\n.detail-meta{display:flex;flex-wrap:wrap;gap:5px;align-items:center}\n.dm-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:4px}\n.dm-type{background:rgba(139,92,246,0.12);color:#a78bfa}\n.dm-tag{background:rgba(59,130,246,0.12);color:#60a5fa}\n.dm-retry{background:rgba(245,158,11,0.12);color:#fbbf24}\n.dm-flaky{background:rgba(245,158,11,0.12);color:#fbbf24}\n.detail-sub-meta{display:flex;gap:14px;font-size:11px;color:var(--fg-1);flex-wrap:wrap;margin-top:6px}\n.meta-k{font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;font-size:10px;margin-right:3px}\n.detail-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;color:var(--fg-1);background:var(--bg-2);padding:1px 6px;border-radius:4px}\n.detail-file{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--fg-1)}\n.detail-suite{font-size:11px;color:var(--fg-1)}\n.detail-duration{font-size:24px;font-weight:800;color:var(--fg);flex-shrink:0;white-space:nowrap}\n\n/* ── Failure Panel ── */\n.failure-panel{margin-bottom:20px;border:1px solid rgba(239,68,68,0.2);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.failure-panel-header{display:flex;align-items:center;gap:8px;padding:10px 14px;background:rgba(239,68,68,0.08);font-size:12px;font-weight:600;color:#ef4444}\n.failure-message{padding:12px 14px;background:var(--bg-code);color:var(--fg-err);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;line-height:1.6;overflow-x:auto}\n.code-snippet{background:var(--bg-code);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow-x:auto;border-top:1px solid var(--bd-s)}\n.code-line{display:flex;padding:0 14px;line-height:1.8}\n.code-line.highlight{background:rgba(239,68,68,0.12)}\n.line-num{color:var(--fg-2);min-width:36px;text-align:right;padding-right:14px;user-select:none;flex-shrink:0}\n.line-code{color:var(--fg-code);white-space:pre;overflow-x:auto}\n.line-marker{padding:4px 14px;font-size:10px;color:var(--fg-2);background:var(--bg-2);border-top:1px solid var(--bd-s)}\n.stack-toggle-btn{font-size:11px;color:var(--fg-1);cursor:pointer;padding:8px 14px;border:none;background:var(--bg-3);width:100%;text-align:left;font-family:inherit;border-top:1px solid var(--bd-s);transition:background .15s}\n.stack-toggle-btn:hover{background:var(--bg-2);color:var(--fg)}\n.stack-trace{display:none;padding:12px 14px;background:var(--bg-2);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;color:var(--fg-2);border-top:1px solid var(--bd-s);max-height:260px;overflow-y:auto}\n.stack-trace.show{display:block}\n\n/* ── Section Label ── */\n.section-label{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:14px;display:flex;align-items:center;gap:8px}\n.section-label::before{content:'';width:3px;height:14px;background:#03b79c;border-radius:2px}\n\n/* ── Artifacts Panel ── */\n.artifacts-panel{margin-bottom:20px;border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.artifacts-toolbar{display:flex;align-items:center;gap:0;padding:0;background:var(--bg-3);border-bottom:1px solid var(--bd-s)}\n.artifacts-label{display:flex;align-items:center;gap:6px;padding:9px 14px;font-size:11px;font-weight:600;color:var(--fg-1);text-transform:uppercase;letter-spacing:.04em;border-right:1px solid var(--bd-s);flex-shrink:0}\n.artifact-tab{padding:9px 16px;font-size:12px;font-weight:500;color:var(--fg-2);cursor:pointer;border:none;background:transparent;font-family:inherit;transition:color .15s,background .15s;position:relative;display:flex;align-items:center;gap:6px}\n.artifact-tab:hover{color:var(--fg-1);background:var(--hvr-s)}\n.artifact-tab.active{color:var(--fg);background:var(--bd-xs)}\n.artifact-tab.active::after{content:'';position:absolute;bottom:0;left:8px;right:8px;height:2px;background:#03b79c;border-radius:1px}\n.artifact-tab svg{flex-shrink:0;opacity:.6}\n.artifact-tab.active svg{opacity:1}\n.artifact-pane{display:none;padding:14px}\n.artifact-pane.active{display:block;animation:trFadeIn .15s ease-out}\n.screenshot-pane{display:flex;flex-direction:column;align-items:center;gap:8px}\n.artifact-thumb{max-height:260px;max-width:100%;width:auto;border-radius:8px;cursor:pointer;border:1px solid var(--bd);transition:border-color .15s,box-shadow .15s,transform .15s;object-fit:contain;display:block}\n.artifact-thumb:hover{border-color:#03b79c;box-shadow:0 0 0 3px rgba(3,183,156,0.15);transform:scale(1.01)}\n.artifact-caption{font-size:11px;color:var(--fg-2);display:flex;align-items:center;gap:4px}\n.artifact-caption svg{opacity:.5}\n.video-pane{display:flex;flex-direction:column;gap:0}\n.video-player{border-radius:8px;overflow:hidden;border:1px solid var(--bd);background:#000}\n.video-player video{width:100%;max-height:340px;display:block}\n\n/* ── Navigation Timeline ── */\n.nav-timeline{position:relative;margin-bottom:20px}\n.nav-step{display:flex;gap:12px;position:relative}\n.step-connector{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:28px}\n.step-node{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0;z-index:1;border:2px solid;transition:transform .15s}\n.step-node:hover{transform:scale(1.1)}\n.node-ok{background:rgba(3,183,156,0.1);border-color:rgba(3,183,156,0.35);color:#2dd4a8}\n.node-warn{background:rgba(245,158,11,0.1);border-color:rgba(245,158,11,0.35);color:#f59e0b}\n.step-line{width:2px;flex:1;min-height:10px;background:linear-gradient(to bottom,rgba(3,183,156,0.35),rgba(3,183,156,0.08))}\n.nav-step:last-child .step-line{display:none}\n.step-content{flex:1;min-width:0;padding-bottom:14px}\n.nav-step:last-child .step-content{padding-bottom:0}\n.step-header{display:flex;align-items:center;gap:7px;cursor:pointer;padding:4px 8px;border-radius:6px;margin:-4px -8px;transition:background .1s;flex-wrap:wrap}\n.step-header:hover{background:var(--hvr-s)}\n.step-url{font-size:12px;font-weight:500;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0}\n.nav-badge{font-size:9px;font-weight:600;padding:2px 7px;border-radius:9999px;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0;white-space:nowrap}\n.nb-goto{background:rgba(59,130,246,0.12);color:#60a5fa}\n.nb-navigation{background:rgba(99,102,241,0.12);color:#818cf8}\n.nb-page_load{background:rgba(129,140,248,0.12);color:#a5b4fc}\n.nb-back{background:rgba(34,211,238,0.12);color:#22d3ee}\n.nb-forward{background:rgba(34,211,238,0.12);color:#67e8f9}\n.nb-spa_route{background:rgba(139,92,246,0.12);color:#a78bfa}\n.nb-spa_replace{background:rgba(167,139,250,0.12);color:#c4b5fd}\n.nb-hash_change{background:rgba(236,72,153,0.12);color:#f472b6}\n.nb-popstate{background:rgba(244,114,182,0.12);color:#f9a8d4}\n.nb-link_click{background:rgba(251,113,133,0.12);color:#fb7185}\n.nb-form_submit{background:rgba(244,63,94,0.12);color:#fb7185}\n.nb-redirect{background:rgba(249,115,22,0.12);color:#fb923c}\n.nb-refresh{background:rgba(56,189,248,0.12);color:#38bdf8}\n.nb-dummy{background:rgba(107,114,128,0.06);color:var(--fg-2)}\n.nb-fallback{background:rgba(107,114,128,0.06);color:var(--fg-2)}\n.nb-manual_record{background:rgba(234,179,8,0.12);color:#facc15}\n.step-meta{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--fg-2);margin-left:auto;flex-shrink:0}\n\n/* Step expandable detail */\n.step-detail{display:none;margin-top:8px;border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-2)}\n.step-detail.show{display:block;animation:trSlideDown .2s ease-out}\n@keyframes trSlideDown{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n.net-header{display:flex;align-items:center;gap:6px;padding:7px 12px;font-size:11px;font-weight:600;color:var(--fg-1);background:var(--bg-3);border-bottom:1px solid var(--bd-s)}\n.net-header.has-fails{color:#ef4444;background:rgba(239,68,68,0.08)}\n.net-stats-row{display:flex;gap:8px;padding:8px 12px;flex-wrap:wrap}\n.net-stat{display:flex;flex-direction:column;align-items:center;gap:1px;padding:5px 10px;border-radius:6px;background:var(--bg-3);min-width:50px}\n.net-stat .nv{font-weight:700;font-size:14px;color:var(--fg);line-height:1}\n.net-stat .nl{color:var(--fg-2);font-size:8px;font-weight:600;text-transform:uppercase;letter-spacing:.06em}\n.net-stat.net-fail{background:rgba(239,68,68,0.08)}.net-stat.net-fail .nv{color:#ef4444}\n.resource-row{display:flex;gap:4px;padding:7px 12px;flex-wrap:wrap;border-top:1px solid var(--bd-xs)}\n.res-type{display:flex;align-items:center;gap:3px;font-size:10px;padding:2px 7px;border-radius:4px;background:var(--bg-3);color:var(--fg-1);font-weight:500}\n.res-type .rc{font-weight:700}\n.res-type--zero{opacity:.25}\n.fail-urls{padding:7px 12px;border-top:1px solid rgba(239,68,68,0.12);background:rgba(239,68,68,0.06);max-height:160px;overflow-y:auto}\n.fail-url{font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#ef4444;padding:2px 0;word-break:break-all}\n.fail-url-status{font-weight:700;margin-right:4px}\n\n/* ── Network DevTools Panel ── */\n.ndt{border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-1)}\n.ndt-toolbar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--bg-2);border-bottom:1px solid var(--bd);flex-wrap:wrap}\n.ndt-filter{display:flex;gap:2px;flex-wrap:wrap}\n.ndt-filter-btn{font-size:10px;padding:2px 8px;border-radius:4px;border:1px solid transparent;background:none;color:var(--fg-2);cursor:pointer;font-weight:500;transition:all .15s}\n.ndt-filter-btn:hover{background:var(--hvr);color:var(--fg-1)}\n.ndt-filter-btn.active{background:var(--bg-3);color:var(--fg);border-color:var(--bd-m);font-weight:600}\n.ndt-search{flex:1;min-width:100px;max-width:220px;font-size:10px;padding:3px 8px;border-radius:4px;border:1px solid var(--bd);background:var(--bg);color:var(--fg);outline:none;font-family:inherit}\n.ndt-search:focus{border-color:rgba(3,183,156,0.4)}\n.ndt-count{font-size:10px;color:var(--fg-2);margin-left:auto;white-space:nowrap}\n.ndt-list{max-height:600px;overflow-y:auto}\n.ndt-list::-webkit-scrollbar{width:5px}.ndt-list::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-row{display:grid;grid-template-columns:42px 50px 1fr 52px 56px 56px;align-items:center;gap:4px;padding:5px 10px;border-bottom:1px solid var(--bd-xs);cursor:pointer;font-size:11px;transition:background .1s}\n.ndt-row:hover{background:var(--hvr)}\n.ndt-row.ndt-row--open{background:var(--bg-2)}\n.ndt-row.ndt-row--fail{background:rgba(239,68,68,0.04)}\n.ndt-row.ndt-row--fail:hover{background:rgba(239,68,68,0.08)}\n.ndt-status{font-weight:700;font-size:10px;padding:1px 5px;border-radius:3px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-status--2xx{background:rgba(34,197,94,0.12);color:#22c55e}\n.ndt-status--3xx{background:rgba(59,130,246,0.12);color:#60a5fa}\n.ndt-status--4xx{background:rgba(249,115,22,0.12);color:#fb923c}\n.ndt-status--5xx{background:rgba(239,68,68,0.12);color:#ef4444}\n.ndt-status--0{background:rgba(239,68,68,0.12);color:#ef4444}\n.ndt-method{font-weight:600;font-size:10px;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-transform:uppercase}\n.ndt-url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.ndt-type{font-size:9px;padding:1px 5px;border-radius:3px;background:var(--bg-3);color:var(--fg-2);text-align:center;font-weight:500}\n.ndt-size{font-size:10px;color:var(--fg-2);text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-time{font-size:10px;color:var(--fg-2);text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-detail{display:none;border-bottom:1px solid var(--bd);background:var(--bg);animation:trSlideDown .15s ease-out}\n.ndt-detail.show{display:block}\n.ndt-detail-tabs{display:flex;gap:0;border-bottom:1px solid var(--bd);background:var(--bg-2)}\n.ndt-detail-tab{font-size:10px;padding:6px 14px;border:none;background:none;color:var(--fg-2);cursor:pointer;font-weight:500;border-bottom:2px solid transparent;transition:all .15s}\n.ndt-detail-tab:hover{color:var(--fg-1);background:var(--hvr)}\n.ndt-detail-tab.active{color:#03b79c;border-bottom-color:#03b79c;font-weight:600}\n.ndt-detail-pane{display:none;padding:10px 14px;max-height:360px;overflow-y:auto}\n.ndt-detail-pane::-webkit-scrollbar{width:5px}.ndt-detail-pane::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-detail-pane.active{display:block}\n.ndt-general{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:11px}\n.ndt-general-k{color:var(--fg-2);font-weight:600;white-space:nowrap}\n.ndt-general-v{color:var(--fg);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.ndt-general-v.ndt-err{color:#ef4444}\n.ndt-headers-tbl{width:100%;border-collapse:collapse;font-size:10px}\n.ndt-headers-tbl th{text-align:left;padding:4px 8px;background:var(--bg-2);color:var(--fg-2);font-weight:600;border-bottom:1px solid var(--bd);font-size:9px;text-transform:uppercase;letter-spacing:.04em}\n.ndt-headers-tbl td{padding:4px 8px;border-bottom:1px solid var(--bd-xs);vertical-align:top}\n.ndt-headers-tbl td:first-child{color:var(--fg-1);font-weight:600;white-space:nowrap;width:180px}\n.ndt-headers-tbl td:last-child{color:var(--fg);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-body-wrap{position:relative}\n.ndt-body-pre{margin:0;padding:10px;background:var(--bg-code);border-radius:6px;border:1px solid var(--bd);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--fg-code);white-space:pre-wrap;word-break:break-all;max-height:320px;overflow-y:auto;line-height:1.5;tab-size:2}\n.ndt-body-pre::-webkit-scrollbar{width:5px}.ndt-body-pre::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-body-empty{padding:16px;text-align:center;font-size:11px;color:var(--fg-2);font-style:italic}\n.ndt-body-truncated{display:inline-block;margin-top:6px;font-size:9px;padding:2px 8px;border-radius:4px;background:rgba(249,115,22,0.1);color:#fb923c;font-weight:500}\n.ndt-hdr-section{margin-bottom:10px}\n.ndt-hdr-label{font-size:10px;font-weight:600;color:var(--fg-1);margin-bottom:4px;display:flex;align-items:center;gap:5px}\n.ndt-hdr-label svg{opacity:.5}\n\n/* ── Lightbox ── */\n.lightbox-overlay{position:fixed;inset:0;background:var(--lb-bg);z-index:1000;display:flex;align-items:center;justify-content:center;cursor:zoom-out;animation:trFadeIn .15s}\n.lightbox-overlay img{max-width:92vw;max-height:92vh;border-radius:8px;box-shadow:0 8px 40px var(--lb-shadow)}\n.lightbox-close{position:fixed;top:16px;right:16px;z-index:1001;width:40px;height:40px;border-radius:50%;border:none;background:var(--lb-btn);color:#fff;font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;backdrop-filter:blur(8px)}\n.lightbox-close:hover{background:var(--lb-btn-h)}\n\n/* ── Responsive ── */\n@media(max-width:1100px){\n .drawer{width:60vw;max-width:60vw}\n}\n@media(max-width:800px){\n .drawer{width:85vw;max-width:85vw}\n}\n@media(max-width:640px){\n .wrap{padding:16px 12px 48px}\n .top-bar{flex-direction:column;align-items:flex-start;gap:8px}\n .run-meta{margin-left:0}\n .summary-strip{flex-wrap:wrap}\n .summary-chip{min-width:70px}\n .filter-bar{flex-direction:column;align-items:stretch}\n .search-box{width:100%;margin-left:0}\n .drawer{width:100%;max-width:100%}\n .cta-btn{font-size:10px;padding:5px 10px;order:10}\n .detail-title{font-size:14px}\n .detail-duration{font-size:20px}\n}\n\n/* ── Print ── */\n@media print{\n body{background:#fff;color:#000}\n .drawer-backdrop,.drawer{display:none}\n .lightbox-overlay,.lightbox-close{display:none}\n .test-row-arrow{display:none}\n .summary-chip,.filter-chip,.status-indicator,.detail-status-badge,.dm-badge,.nav-badge,.trb,.step-node{\n print-color-adjust:exact;-webkit-print-color-adjust:exact;\n }\n}\n`;\n","/**\n * TestRelic Logo SVG\n *\n * Inline SVG logo used in the HTML report header and empty states.\n */\n\n/** Raw SVG string for favicon data URI (no presentational size overrides). */\nexport const LOGO_SVG_RAW = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 196 247\" fill=\"none\">\n<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)\"/>\n<defs><linearGradient id=\"paint0_linear_55_11\" x1=\"98\" y1=\"0.000244141\" x2=\"98\" y2=\"247\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#03B79C\"/><stop offset=\"0.504808\" stop-color=\"#4EDAA4\"/><stop offset=\"0.865285\" stop-color=\"#84F3AA\"/>\n</linearGradient></defs></svg>`;\n\nexport const LOGO_SVG = `<svg width=\"32\" height=\"40\" viewBox=\"0 0 196 247\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<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)\"/>\n<defs><linearGradient id=\"paint0_linear_55_11\" x1=\"98\" y1=\"0.000244141\" x2=\"98\" y2=\"247\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#03B79C\"/><stop offset=\"0.504808\" stop-color=\"#4EDAA4\"/><stop offset=\"0.865285\" stop-color=\"#84F3AA\"/>\n</linearGradient></defs></svg>`;\n","/**\n * Client-side JavaScript: Core Rendering.\n * Utilities, data transformation, summary, test grid, drawer, and artifacts.\n */\n\nexport const JS_RENDER = `\n /* ── Utilities ── */\n function esc(s){if(!s)return '';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"').replace(/'/g,''')}\n function stripAnsi(s){return s?s.replace(/\\\\u001b\\\\[[0-9;]*m/g,'').replace(/\\\\x1b\\\\[[0-9;]*m/g,''):''}\n function fmtDur(ms){if(ms<1000)return Math.round(ms)+'ms';if(ms<60000)return (ms/1000).toFixed(1)+'s';var m=Math.floor(ms/60000),s=Math.round((ms%60000)/1000);return s>0?m+'m '+s+'s':m+'m'}\n function fmtBytes(b){if(b===0)return '0 B';if(b<1024)return b+' B';if(b<1048576)return (b/1024).toFixed(1)+' KB';return (b/1048576).toFixed(1)+' MB'}\n function fmtDate(iso){try{return new Date(iso).toLocaleString()}catch(e){return iso}}\n function shortTitle(t){var p=t.split(' > ');return p.length>1?p[p.length-1]:t}\n\n /* ── Data Transformation ── */\n var testMap={};\n for(var i=0;i<data.timeline.length;i++){\n var entry=data.timeline[i];\n for(var j=0;j<entry.tests.length;j++){\n var t=entry.tests[j];\n if(!testMap[t.testId]){\n testMap[t.testId]={\n testId:t.testId,title:t.title,status:t.status,duration:t.duration,\n startedAt:t.startedAt,completedAt:t.completedAt,retryCount:t.retryCount,\n tags:t.tags,failure:t.failure,filePath:t.filePath,suiteName:t.suiteName,\n testType:t.testType,isFlaky:t.isFlaky,retryStatus:t.retryStatus,\n expectedStatus:t.expectedStatus,actualStatus:t.actualStatus,\n artifacts:t.artifacts,networkRequests:t.networkRequests||[],steps:[]\n };\n }\n testMap[t.testId].steps.push({\n url:entry.url,navigationType:entry.navigationType,\n visitedAt:entry.visitedAt,duration:entry.duration,specFile:entry.specFile,\n domContentLoadedAt:entry.domContentLoadedAt,networkIdleAt:entry.networkIdleAt,\n networkStats:entry.networkStats\n });\n }\n }\n var tests=[];\n for(var id in testMap){if(testMap.hasOwnProperty(id))tests.push(testMap[id])}\n tests.sort(function(a,b){\n if(a.filePath!==b.filePath)return a.filePath<b.filePath?-1:1;\n return a.startedAt<b.startedAt?-1:1;\n });\n\n /* ── State ── */\n var searchQuery='';\n\n /* ── DOM Refs ── */\n var runMetaEl=document.getElementById('run-meta');\n var summaryEl=document.getElementById('summary-strip');\n var filterEl=document.getElementById('filter-bar');\n var testGridEl=document.getElementById('test-grid');\n var drawerEl=document.getElementById('drawer');\n var drawerBodyEl=document.getElementById('drawer-body');\n var drawerBackdropEl=document.getElementById('drawer-backdrop');\n\n /* ── Render: Run Meta ── */\n function renderRunMeta(){\n var h='';\n h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Run</span><span class=\"run-id-tag\">'+esc(data.testRunId.substring(0,8))+'</span></div>';\n h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Duration</span>'+fmtDur(data.totalDuration)+'</div>';\n h+='<div class=\"run-meta-item\">'+fmtDate(data.startedAt)+'</div>';\n if(data.ci){\n h+='<div class=\"run-meta-item\"><span class=\"ci-tag\">'+esc(data.ci.provider)+'</span></div>';\n if(data.ci.branch)h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Branch</span>'+esc(data.ci.branch)+'</div>';\n if(data.ci.commitSha)h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Commit</span><span class=\"run-id-tag\">'+esc(data.ci.commitSha.substring(0,8))+'</span></div>';\n }\n if(data.shardRunIds&&data.shardRunIds.length>0){\n h+='<div class=\"run-meta-item\"><span class=\"merged-tag\">Merged ('+data.shardRunIds.length+' shards)</span></div>';\n }\n runMetaEl.innerHTML=h;\n }\n\n /* ── Render: Summary ── */\n function renderSummary(){\n var s=data.summary;var h='';\n h+='<div class=\"summary-chip s-total\"><div class=\"s-count\">'+s.total+'</div><div class=\"s-label\">Total</div></div>';\n h+='<div class=\"summary-chip s-passed\"><div class=\"s-count\">'+s.passed+'</div><div class=\"s-label\">Passed</div></div>';\n h+='<div class=\"summary-chip s-failed\"><div class=\"s-count\">'+s.failed+'</div><div class=\"s-label\">Failed</div></div>';\n h+='<div class=\"summary-chip s-flaky\"><div class=\"s-count\">'+s.flaky+'</div><div class=\"s-label\">Flaky</div></div>';\n h+='<div class=\"summary-chip s-skipped\"><div class=\"s-count\">'+s.skipped+'</div><div class=\"s-label\">Skipped</div></div>';\n 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>';\n summaryEl.innerHTML=h;\n }\n\n /* ── Render: Test Grid ── */\n function renderTestGrid(){\n var filtered=getFilteredTests();\n if(filtered.length===0){testGridEl.innerHTML='<div class=\"no-tests\">No tests match your filters</div>';return;}\n var h='';var lastFile='';\n for(var i=0;i<filtered.length;i++){\n var t=filtered[i];\n if(t.filePath!==lastFile){\n if(lastFile)h+='</div></div>';\n h+='<div class=\"file-group\"><div class=\"file-group-header\">'+esc(t.filePath)+'</div><div class=\"file-group-card\">';\n lastFile=t.filePath;\n }\n h+='<div class=\"test-row\" data-testid=\"'+esc(t.testId)+'\">';\n h+='<div class=\"status-indicator si-'+t.status+'\"></div>';\n h+='<div class=\"test-row-info\">';\n h+='<div class=\"test-row-title\" title=\"'+esc(t.title)+'\">'+esc(shortTitle(t.title))+'</div>';\n var hasBadges=(t.testType&&t.testType!=='unknown')||t.isFlaky||(t.tags&&t.tags.length>0);\n if(hasBadges){\n h+='<div class=\"test-row-badges\">';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"trb trb-type\">'+esc(t.testType)+'</span>';\n if(t.isFlaky)h+='<span class=\"trb trb-flaky\">flaky</span>';\n if(t.retryCount>0)h+='<span class=\"trb trb-retry\">'+t.retryCount+' retries</span>';\n if(t.tags){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti])h+='<span class=\"trb trb-tag\">'+esc(t.tags[ti])+'</span>';}}\n h+='</div>';\n }\n h+='</div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(t.duration)+'</span>';\n 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>';\n h+='</div>';\n }\n if(lastFile)h+='</div></div>';\n testGridEl.innerHTML=h;\n }\n\n /* ── Render helpers (code, artifacts) ── */\n\n function renderCodeSnippet(code){\n var lines=code.split('\\\\n');var out='<div class=\"code-snippet\">';\n for(var k=0;k<lines.length;k++){var raw=lines[k];var numMatch=raw.match(/^\\\\s*(>?\\\\s*)(\\\\d+)\\\\s*\\\\|/);var lineNum=numMatch?numMatch[2]:'';var isHighlight=raw.trimStart().startsWith('>');var codePart=raw.replace(/^\\\\s*>?\\\\s*\\\\d+\\\\s*\\\\|/,'');out+='<div class=\"code-line'+(isHighlight?' highlight':'')+'\"><span class=\"line-num\">'+esc(lineNum)+'</span><span class=\"line-code\">'+esc(codePart)+'</span></div>';}\n out+='</div>';return out;\n }\n\n function renderArtifactColumn(artifacts){\n if(!artifacts)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var hasS=!!artifacts.screenshot,hasV=!!artifacts.video;\n if(!hasS&&!hasV)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var h='';\n if(hasS&&hasV){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"artifacts\">';\n 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>';\n 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>';\n h+='</div>';\n }\n 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>';}\n 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>';}\n return h;\n }\n\n /* ── Render: Drawer Content ── */\n function renderDrawer(testId){\n var t=testMap[testId];\n if(!t){drawerBodyEl.innerHTML='';return;}\n var h='<div class=\"test-detail\">';\n\n /* ── Top: Metadata ── */\n h+='<div class=\"test-detail-header\"><div class=\"test-detail-top\">';\n h+='<span class=\"detail-status-badge dbg-'+t.status+'\">'+t.status+'</span>';\n h+='<div class=\"detail-title-section\"><div class=\"detail-title\">'+esc(t.title)+'</div><div class=\"detail-meta\">';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"dm-badge dm-type\">'+esc(t.testType)+'</span>';\n if(t.isFlaky)h+='<span class=\"dm-badge dm-flaky\">Flaky</span>';\n if(t.retryCount>0)h+='<span class=\"dm-badge dm-retry\">'+t.retryCount+' retries</span>';\n if(t.retryStatus)h+='<span class=\"dm-badge dm-retry\">'+esc(t.retryStatus)+'</span>';\n if(t.tags&&t.tags.length>0){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti])h+='<span class=\"dm-badge dm-tag\">'+esc(t.tags[ti])+'</span>';}}\n if(t.expectedStatus&&t.actualStatus&&t.expectedStatus!==t.actualStatus)h+='<span class=\"dm-badge\" style=\"background:rgba(239,68,68,0.12);color:#ef4444\">expected: '+esc(t.expectedStatus)+' actual: '+esc(t.actualStatus)+'</span>';\n h+='</div>';\n if(t.testId||t.filePath||t.suiteName){h+='<div class=\"detail-sub-meta\">';if(t.testId)h+='<span><span class=\"meta-k\">ID</span><span class=\"detail-id\" title=\"'+esc(t.testId)+'\">'+esc(t.testId.substring(0,12))+'</span></span>';if(t.filePath)h+='<span><span class=\"meta-k\">File</span><span class=\"detail-file\">'+esc(t.filePath)+'</span></span>';if(t.suiteName)h+='<span><span class=\"meta-k\">Suite</span><span class=\"detail-suite\">'+esc(t.suiteName)+'</span></span>';h+='</div>';}\n h+='</div><div class=\"detail-duration\">'+fmtDur(t.duration)+'</div></div></div>';\n\n /* ── Failure panel (full width) ── */\n if((t.status==='failed'||t.status==='flaky')&&t.failure)h+=renderFailure(t.failure);\n 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>';\n\n /* ── Stacked sections: 1) Artifacts 2) Timeline / Network ── */\n var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video);\n var hasSteps=t.steps&&t.steps.length>0;\n if(hasArt||hasSteps){\n h+='<div class=\"drawer-sections\">';\n if(hasArt)h+='<div class=\"drawer-section\">'+renderArtifactColumn(t.artifacts)+'</div>';\n if(hasSteps)h+='<div class=\"drawer-section\">'+renderTimelineColumn(t.steps,t.networkRequests)+'</div>';\n h+='</div>';\n }\n\n h+='</div>';\n drawerBodyEl.innerHTML=h;\n drawerBodyEl.scrollTop=0;\n }\n\n /* ── Drawer open / close ── */\n function openDrawer(testId){\n renderDrawer(testId);\n drawerEl.classList.add('open');\n drawerBackdropEl.classList.add('open');\n document.body.style.overflow='hidden';\n }\n function closeDrawer(){\n drawerEl.classList.remove('open');\n drawerBackdropEl.classList.remove('open');\n document.body.style.overflow='';\n /* Pause any playing video */\n var v=drawerBodyEl.querySelector('video');\n if(v)v.pause();\n }\n\n /* ── Search ── */\n function doSearch(q){searchQuery=q;applyFilters();}\n`;\n","/**\n * Client-side JavaScript: Network.\n * Resource visualization, navigation timeline, Network DevTools panel, and failure rendering.\n */\nexport const JS_NETWORK = `\n /* ── Network Visualization Helpers ── */\n var RES_COLORS={xhr:'#60a5fa',document:'#818cf8',script:'#fbbf24',stylesheet:'#a78bfa',image:'#34d399',font:'#f472b6',other:'#6b7280'};\n var RES_LABELS={xhr:'XHR',document:'Doc',script:'JS',stylesheet:'CSS',image:'Img',font:'Font',other:'Other'};\n var RES_ORDER=['xhr','document','script','stylesheet','image','font','other'];\n\n function renderResBar(byType,total,mini){\n if(!byType||total===0)return '';\n var h='<div class=\"net-bar'+(mini?' net-bar--mini':'')+'\">';\n for(var i=0;i<RES_ORDER.length;i++){var k=RES_ORDER[i];var cnt=byType[k]||0;if(cnt===0)continue;var pct=(cnt/total*100).toFixed(1);h+='<div class=\"net-bar-seg\" style=\"width:'+pct+'%;background:'+RES_COLORS[k]+'\" title=\"'+RES_LABELS[k]+': '+cnt+' ('+(cnt/total*100).toFixed(0)+'%)\"></div>';}\n h+='</div>';return h;\n }\n\n function renderResLegend(byType){\n if(!byType)return '';\n var h='<div class=\"net-bar-legend\">';\n for(var i=0;i<RES_ORDER.length;i++){var k=RES_ORDER[i];var cnt=byType[k]||0;if(cnt===0)continue;h+='<span class=\"net-legend-item\"><span class=\"net-legend-dot\" style=\"background:'+RES_COLORS[k]+'\"></span>'+RES_LABELS[k]+' <span class=\"net-legend-count\">'+cnt+'</span></span>';}\n h+='</div>';return h;\n }\n\n function calcStepTiming(step){\n var t={};\n try{\n if(step.visitedAt&&step.domContentLoadedAt){var dcl=new Date(step.domContentLoadedAt).getTime()-new Date(step.visitedAt).getTime();if(dcl>=0)t.dcl=dcl;}\n if(step.visitedAt&&step.networkIdleAt){var idle=new Date(step.networkIdleAt).getTime()-new Date(step.visitedAt).getTime();if(idle>=0)t.idle=idle;}\n }catch(e){}\n return t;\n }\n\n function failStatusClass(code){var n=parseInt(code,10);if(n>=400&&n<500)return 'fail-url-badge--4xx';if(n>=500)return 'fail-url-badge--5xx';return 'fail-url-badge--other';}\n\n function renderFailedUrls(failedUrls){\n if(!failedUrls||failedUrls.length===0)return '';\n var h='<div class=\"fail-urls\">';\n for(var fi=0;fi<failedUrls.length;fi++){\n var furl=failedUrls[fi];var spIdx=furl.indexOf(' ');\n var fStatus=spIdx>0?furl.substring(0,spIdx):'';var fPath=spIdx>0?furl.substring(spIdx+1):furl;\n h+='<div class=\"fail-url\"><span class=\"fail-url-badge '+failStatusClass(fStatus)+'\">'+esc(fStatus)+'</span>'+esc(fPath)+'</div>';\n }\n h+='</div>';return h;\n }\n\n function renderNetworkPanel(stats){\n if(!stats)return '<div style=\"padding:7px 12px;font-size:11px;color:var(--fg-2);font-style:italic\">No network data</div>';\n var hasFail=stats.failedRequests>0;\n var h='<div class=\"net-header'+(hasFail?' has-fails':'')+'\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>Network</div>';\n h+='<div class=\"net-stats-row\"><div class=\"net-stat\"><span class=\"nv\">'+stats.totalRequests+'</span><span class=\"nl\">Requests</span></div><div class=\"net-stat'+(hasFail?' net-fail':'')+'\"><span class=\"nv\">'+stats.failedRequests+'</span><span class=\"nl\">Failed</span></div><div class=\"net-stat\"><span class=\"nv\">'+fmtBytes(stats.totalBytes)+'</span><span class=\"nl\">Transfer</span></div></div>';\n if(stats.byType&&stats.totalRequests>0){h+='<div class=\"net-bar-wrap\">'+renderResBar(stats.byType,stats.totalRequests,false)+'</div>';h+=renderResLegend(stats.byType);}\n if(hasFail&&stats.failedRequestUrls&&stats.failedRequestUrls.length>0)h+=renderFailedUrls(stats.failedRequestUrls);\n return h;\n }\n\n function renderNavTimeline(steps){\n if(!steps||steps.length===0)return '';\n var h='<div class=\"nav-timeline\">';\n for(var i=0;i<steps.length;i++){\n var s=steps[i];var hasNetFail=s.networkStats&&s.networkStats.failedRequests>0;\n h+='<div class=\"nav-step\"><div class=\"step-connector\"><div class=\"step-node '+(hasNetFail?'node-warn':'node-ok')+'\">'+(i+1)+'</div>';\n if(i<steps.length-1)h+='<div class=\"step-line\"></div>';\n h+='</div><div class=\"step-content\"><div class=\"step-header\" data-step=\"'+i+'\"><span class=\"step-url\" title=\"'+esc(s.url)+'\">'+esc(s.url)+'</span><span class=\"nav-badge nb-'+s.navigationType+'\">'+esc(s.navigationType.replace(/_/g,' '))+'</span><span class=\"step-meta\"><span>'+fmtDur(s.duration)+'</span>';\n if(s.networkStats)h+=' <span style=\"color:var(--fg-2)\">'+s.networkStats.totalRequests+' req</span>';\n h+='</span></div><div class=\"step-detail\" id=\"step-detail-'+i+'\">'+renderNetworkPanel(s.networkStats)+'</div></div></div>';\n }\n h+='</div>';return h;\n }\n\n /* ── Network DevTools Panel ── */\n function ndtStatusClass(code){var n=parseInt(code,10);if(n===0)return 'ndt-status--0';if(n<300)return 'ndt-status--2xx';if(n<400)return 'ndt-status--3xx';if(n<500)return 'ndt-status--4xx';return 'ndt-status--5xx';}\n function urlPath(u){try{var p=new URL(u);return p.pathname+p.search;}catch(e){return u;}}\n function prettyBody(body,ct){\n if(!body)return null;\n if(ct&&(ct.indexOf('json')>=0||ct.indexOf('javascript')>=0)){try{return JSON.stringify(JSON.parse(body),null,2);}catch(e){}}\n return body;\n }\n\n function renderNdtHeaders(headers,label){\n if(!headers)return '<div class=\"ndt-body-empty\">No '+label+' headers captured</div>';\n var keys=[];for(var k in headers){if(headers.hasOwnProperty(k))keys.push(k);}\n if(keys.length===0)return '<div class=\"ndt-body-empty\">No '+label+' headers captured</div>';\n keys.sort();\n var h='<table class=\"ndt-headers-tbl\"><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody>';\n for(var i=0;i<keys.length;i++){h+='<tr><td>'+esc(keys[i])+'</td><td>'+esc(headers[keys[i]])+'</td></tr>';}\n h+='</tbody></table>';return h;\n }\n\n function renderNdtBody(body,truncated,isBinary,ct,emptyLabel){\n if(isBinary)return '<div class=\"ndt-body-empty\">Binary content — not displayed</div>';\n if(!body)return '<div class=\"ndt-body-empty\">'+emptyLabel+'</div>';\n var pretty=prettyBody(body,ct);\n var h='<div class=\"ndt-body-wrap\"><pre class=\"ndt-body-pre\">'+esc(pretty||body)+'</pre>';\n if(truncated)h+='<span class=\"ndt-body-truncated\">Response truncated (body exceeded capture limit)</span>';\n h+='</div>';return h;\n }\n\n function renderNdtDetail(req,idx){\n var did='ndt-d-'+idx;\n var h='<div class=\"ndt-detail\" id=\"'+did+'\">';\n h+='<div class=\"ndt-detail-tabs\">';\n h+='<button class=\"ndt-detail-tab active\" data-ndt-tab=\"general\" data-ndt-target=\"'+did+'\">General</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-headers\" data-ndt-target=\"'+did+'\">Request Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-body\" data-ndt-target=\"'+did+'\">Payload</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-headers\" data-ndt-target=\"'+did+'\">Response Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-body\" data-ndt-target=\"'+did+'\">Response</button>';\n h+='</div>';\n\n /* General tab */\n h+='<div class=\"ndt-detail-pane active\" data-ndt-pane=\"general\">';\n h+='<div class=\"ndt-general\">';\n h+='<span class=\"ndt-general-k\">Request URL</span><span class=\"ndt-general-v\">'+esc(req.url)+'</span>';\n h+='<span class=\"ndt-general-k\">Method</span><span class=\"ndt-general-v\">'+esc(req.method)+'</span>';\n h+='<span class=\"ndt-general-k\">Status Code</span><span class=\"ndt-general-v'+(req.statusCode>=400||req.statusCode===0?' ndt-err':'')+'\">'+req.statusCode+(req.error?' ('+esc(req.error)+')':'')+'</span>';\n h+='<span class=\"ndt-general-k\">Resource Type</span><span class=\"ndt-general-v\">'+esc(req.resourceType)+'</span>';\n if(req.contentType)h+='<span class=\"ndt-general-k\">Content-Type</span><span class=\"ndt-general-v\">'+esc(req.contentType)+'</span>';\n h+='<span class=\"ndt-general-k\">Response Size</span><span class=\"ndt-general-v\">'+fmtBytes(req.responseSize||0)+'</span>';\n h+='<span class=\"ndt-general-k\">Response Time</span><span class=\"ndt-general-v\">'+fmtDur(req.responseTimeMs)+'</span>';\n if(req.startedAt)h+='<span class=\"ndt-general-k\">Started At</span><span class=\"ndt-general-v\">'+fmtDate(req.startedAt)+'</span>';\n if(req.error)h+='<span class=\"ndt-general-k\">Error</span><span class=\"ndt-general-v ndt-err\">'+esc(req.error)+'</span>';\n h+='</div></div>';\n\n /* Request Headers tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-headers\">'+renderNdtHeaders(req.requestHeaders,'request')+'</div>';\n\n /* Payload tab (request body) */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-body\">'+renderNdtBody(req.requestBody,req.requestBodyTruncated,false,req.contentType,'No request body')+'</div>';\n\n /* Response Headers tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-headers\">'+renderNdtHeaders(req.responseHeaders,'response')+'</div>';\n\n /* Response tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-body\">'+renderNdtBody(req.responseBody,req.responseBodyTruncated,req.isBinary,req.contentType,'No response body captured')+'</div>';\n\n h+='</div>';return h;\n }\n\n function renderNetworkDevTools(reqs){\n if(!reqs||reqs.length===0)return '<div class=\"artifact-empty\">No network requests captured</div>';\n var uid='ndt-'+Math.random().toString(36).substr(2,6);\n var h='<div class=\"ndt\" id=\"'+uid+'\">';\n\n /* Toolbar with type filters and search */\n h+='<div class=\"ndt-toolbar\">';\n h+='<div class=\"ndt-filter\">';\n h+='<button class=\"ndt-filter-btn active\" data-ndt-filter=\"all\">All</button>';\n var types=['xhr','document','script','stylesheet','image','font','other'];\n var typeLabels={xhr:'XHR',document:'Doc',script:'JS',stylesheet:'CSS',image:'Img',font:'Font',other:'Other'};\n for(var ti=0;ti<types.length;ti++){\n var cnt=0;for(var ci=0;ci<reqs.length;ci++){if(reqs[ci].resourceType===types[ti])cnt++;}\n if(cnt>0)h+='<button class=\"ndt-filter-btn\" data-ndt-filter=\"'+types[ti]+'\">'+typeLabels[types[ti]]+' <span style=\"opacity:.6\">'+cnt+'</span></button>';\n }\n h+='</div>';\n h+='<input class=\"ndt-search\" placeholder=\"Filter URLs...\" data-ndt-search=\"'+uid+'\">';\n h+='<span class=\"ndt-count\" data-ndt-count=\"'+uid+'\">'+reqs.length+' requests</span>';\n h+='</div>';\n\n /* Request list */\n h+='<div class=\"ndt-list\" data-ndt-list=\"'+uid+'\">';\n for(var i=0;i<reqs.length;i++){\n var r=reqs[i];\n var isFail=r.statusCode>=400||r.statusCode===0;\n h+='<div class=\"ndt-row'+(isFail?' ndt-row--fail':'')+'\" data-ndt-idx=\"'+i+'\" data-ndt-type=\"'+esc(r.resourceType)+'\" data-ndt-url=\"'+esc(r.url.toLowerCase())+'\">';\n h+='<span class=\"ndt-status '+ndtStatusClass(r.statusCode)+'\">'+r.statusCode+'</span>';\n h+='<span class=\"ndt-method\">'+esc(r.method)+'</span>';\n h+='<span class=\"ndt-url\" title=\"'+esc(r.url)+'\">'+esc(urlPath(r.url))+'</span>';\n h+='<span class=\"ndt-type\">'+esc(RES_LABELS[r.resourceType]||r.resourceType)+'</span>';\n h+='<span class=\"ndt-size\">'+fmtBytes(r.responseSize||0)+'</span>';\n h+='<span class=\"ndt-time\">'+fmtDur(r.responseTimeMs)+'</span>';\n h+='</div>';\n h+=renderNdtDetail(r,i);\n }\n h+='</div></div>';return h;\n }\n\n function renderTimelineColumn(steps,networkRequests){\n if(!steps||steps.length===0)return '<div class=\"artifact-empty\">No navigation data</div>';\n var hasReqs=networkRequests&&networkRequests.length>0;\n var h='<div class=\"seg-ctrl\" data-seg-group=\"right-panel\">';\n h+='<button class=\"seg-btn active\" data-seg=\"timeline\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></svg>Timeline ('+steps.length+')</button>';\n h+='<button class=\"seg-btn\" data-seg=\"network\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>Network'+(hasReqs?' ('+networkRequests.length+')':'')+'</button>';\n h+='</div>';\n h+='<div class=\"seg-pane active\" data-seg-pane=\"timeline\">'+renderNavTimeline(steps)+'</div>';\n h+='<div class=\"seg-pane\" data-seg-pane=\"network\">'+renderNetworkDevTools(networkRequests)+'</div>';\n return h;\n }\n\n function renderFailure(failure){\n if(!failure)return '';\n var sid='s-'+Math.random().toString(36).substr(2,8);\n 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</div>';\n h+='<div class=\"failure-message\">'+esc(stripAnsi(failure.message))+'</div>';\n if(failure.code)h+=renderCodeSnippet(failure.code);\n if(failure.line!==null&&failure.line!==undefined)h+='<div class=\"line-marker\">Line '+failure.line+'</div>';\n 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>';}\n h+='</div>';return h;\n }\n\n /* ── Network DevTools Filter Logic ── */\n function ndtApplyFilters(ndt,filterType,searchVal){\n var list=ndt.querySelector('.ndt-list');if(!list)return;\n var searchInput=ndt.querySelector('.ndt-search');\n var q=(searchVal!==null&&searchVal!==undefined)?searchVal:(searchInput?searchInput.value:'');\n q=q.toLowerCase();\n var rows=list.querySelectorAll('.ndt-row');\n var visible=0;\n for(var i=0;i<rows.length;i++){\n var row=rows[i];\n var type=row.getAttribute('data-ndt-type');\n var url=row.getAttribute('data-ndt-url')||'';\n var show=true;\n if(filterType&&filterType!=='all'&&type!==filterType)show=false;\n if(show&&q&&url.indexOf(q)<0)show=false;\n row.style.display=show?'':'none';\n /* Also hide detail if row is hidden */\n var det=row.nextElementSibling;\n if(det&&det.classList.contains('ndt-detail')){if(!show){det.classList.remove('show');row.classList.remove('ndt-row--open');}}\n if(show)visible++;\n }\n var countEl=ndt.querySelector('.ndt-count');\n if(countEl)countEl.textContent=visible+' / '+rows.length+' requests';\n }\n`;\n","/**\n * Client-side JavaScript: Filter Bar & Multi-Dimensional Filter Logic\n *\n * Renders a filter bar with status, type, and spec file filter chips.\n * Manages filter state and applies filtering via re-rendering.\n * OR logic within dimensions, AND logic across dimensions, plus text search.\n */\n\nexport const JS_FILTERS = `\nvar _filterState={status:{},type:{},specFile:{}};\nvar _totalTests=0;\nvar _originalSummary=null;\n\nfunction renderFilterBar(){\n var statusCounts={passed:0,failed:0,flaky:0,skipped:0,timedout:0};\n var typeCounts={},specCounts={};\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(statusCounts[t.status]!==undefined)statusCounts[t.status]++;\n var tp=t.testType||'unknown';\n typeCounts[tp]=(typeCounts[tp]||0)+1;\n if(t.filePath)specCounts[t.filePath]=(specCounts[t.filePath]||0)+1;\n }\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var hasAny=hasStatus||hasType||hasSpec;\n\n var h='<div class=\"filter-chips\">';\n\n /* Status section */\n h+='<div class=\"filter-section\"><span class=\"filter-section-label\">Status</span>';\n var statuses=['passed','failed','flaky','skipped','timedout'];\n var statusLabels={passed:'Passed',failed:'Failed',flaky:'Flaky',skipped:'Skipped',timedout:'Timed Out'};\n for(var i=0;i<statuses.length;i++){\n var s=statuses[i];var cnt=statusCounts[s]||0;\n var cls='filter-chip'+(!!_filterState.status[s]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n h+='<button class=\"'+cls+'\" data-dimension=\"status\" data-value=\"'+s+'\">'+statusLabels[s]+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n h+='</div>';\n\n /* Type section */\n var types=Object.keys(typeCounts).sort();\n if(types.length>0){\n h+='<div class=\"filter-section\"><span class=\"filter-section-label\">Type</span>';\n for(var i=0;i<types.length;i++){\n var tp=types[i];var cnt=typeCounts[tp]||0;\n var cls='filter-chip'+(!!_filterState.type[tp]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n h+='<button class=\"'+cls+'\" data-dimension=\"type\" data-value=\"'+esc(tp)+'\">'+esc(tp)+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n h+='</div>';\n }\n\n /* Spec file section (only if multiple files) */\n var specs=Object.keys(specCounts).sort();\n if(specs.length>1){\n h+='<div class=\"filter-section\"><span class=\"filter-section-label\">File</span>';\n for(var i=0;i<specs.length;i++){\n var sp=specs[i];var cnt=specCounts[sp]||0;\n var short=sp.split('/').pop()||sp;\n var cls='filter-chip'+(!!_filterState.specFile[sp]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n h+='<button class=\"'+cls+'\" data-dimension=\"specFile\" data-value=\"'+esc(sp)+'\" title=\"'+esc(sp)+'\">'+esc(short)+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n h+='</div>';\n }\n h+='</div>';\n\n /* Search box */\n h+='<div class=\"search-box\"><svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"M21 21l-4.35-4.35\"/></svg><input class=\"search-input\" id=\"search-input\" type=\"text\" placeholder=\"Search tests...\" value=\"'+esc(searchQuery)+'\"></div>';\n\n /* Actions row */\n h+='<div class=\"filter-actions\">';\n h+='<button class=\"filter-clear\" id=\"filter-clear\" style=\"display:'+(hasAny?'inline-block':'none')+'\">Clear all</button>';\n h+='<span class=\"filter-indicator\" id=\"filter-indicator\" style=\"display:'+(hasAny?'inline':'none')+'\">';\n h+='Showing <span id=\"filter-shown\">0</span> of <span id=\"filter-total\">'+_totalTests+'</span> tests</span>';\n h+='</div>';\n\n filterEl.innerHTML=h;\n}\n\nfunction initFilters(){\n _filterState={status:{},type:{},specFile:{}};\n _totalTests=tests.length;\n _originalSummary={total:data.summary.total,passed:data.summary.passed,\n failed:data.summary.failed,flaky:data.summary.flaky,\n skipped:data.summary.skipped,timedout:data.summary.timedout||0};\n}\n\nfunction getFilteredTests(){\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var out=[];\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(hasStatus&&!_filterState.status[t.status])continue;\n if(hasType&&!_filterState.type[t.testType||'unknown'])continue;\n if(hasSpec&&!_filterState.specFile[t.filePath])continue;\n if(searchQuery){\n var q=searchQuery.toLowerCase();\n var match=t.title.toLowerCase().indexOf(q)>=0||t.filePath.toLowerCase().indexOf(q)>=0||(t.suiteName&&t.suiteName.toLowerCase().indexOf(q)>=0);\n if(!match&&t.tags){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti]&&t.tags[ti].toLowerCase().indexOf(q)>=0){match=true;break}}}\n if(!match)continue;\n }\n out.push(t);\n }\n return out;\n}\n\nfunction toggleFilter(dim,val){\n if(!_filterState[dim])_filterState[dim]={};\n if(_filterState[dim][val]){\n delete _filterState[dim][val];\n }else{\n _filterState[dim][val]=true;\n }\n applyFilters();\n}\n\nfunction clearAllFilters(){\n _filterState={status:{},type:{},specFile:{}};\n searchQuery='';\n applyFilters();\n}\n\nfunction applyFilters(){\n renderFilterBar();\n renderTestGrid();\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var hasAny=hasStatus||hasType||hasSpec;\n if(hasAny){\n var filtered=getFilteredTests();\n var byStatus={passed:0,failed:0,flaky:0,skipped:0,timedout:0};\n for(var i=0;i<filtered.length;i++){\n var st=filtered[i].status;\n if(byStatus[st]!==undefined)byStatus[st]++;\n }\n _updateCard('s-total',filtered.length);\n _updateCard('s-passed',byStatus.passed);\n _updateCard('s-failed',byStatus.failed);\n _updateCard('s-flaky',byStatus.flaky);\n _updateCard('s-skipped',byStatus.skipped);\n _updateCard('s-timedout',byStatus.timedout);\n var shown=document.getElementById('filter-shown');\n if(shown)shown.textContent=''+filtered.length;\n }else if(_originalSummary){\n _updateCard('s-total',_originalSummary.total);\n _updateCard('s-passed',_originalSummary.passed);\n _updateCard('s-failed',_originalSummary.failed);\n _updateCard('s-flaky',_originalSummary.flaky);\n _updateCard('s-skipped',_originalSummary.skipped);\n _updateCard('s-timedout',_originalSummary.timedout);\n }\n}\n\nfunction _updateCard(cls,val){\n var el=document.querySelector('.'+cls+' .s-count');\n if(el)el.textContent=''+val;\n}\n`;\n","/**\n * Client-side JavaScript: Interactions\n *\n * Event delegation, theme management, and lightbox for the HTML report.\n * Uses event delegation (document-level listeners) instead of inline handlers.\n */\n\nexport const JS_INTERACTIONS = `\n /* ── Event Delegation ── */\n document.addEventListener('click',function(e){\n var target=e.target;\n\n /* Test row click → open drawer */\n var row=target.closest('.test-row');\n if(row){openDrawer(row.getAttribute('data-testid'));return;}\n\n /* Filter chip (multi-dimensional) */\n var chip=target.closest('.filter-chip');\n if(chip){\n var dim=chip.getAttribute('data-dimension');\n var val=chip.getAttribute('data-value');\n if(dim&&val)toggleFilter(dim,val);\n return;\n }\n\n /* Clear all filters */\n if(target.closest('.filter-clear')){clearAllFilters();return;}\n\n /* Step header → expand/collapse */\n var stepH=target.closest('[data-step]');\n if(stepH){var sd=document.getElementById('step-detail-'+stepH.getAttribute('data-step'));if(sd)sd.classList.toggle('show');return;}\n\n /* Screenshot → lightbox */\n if(target.classList.contains('artifact-thumb')||target.classList.contains('artifact-col-img')){openLightbox(target.src);return;}\n\n /* Segmented control switch */\n var segBtn=target.closest('.seg-btn');\n if(segBtn){\n var group=segBtn.parentElement;\n if(group&&group.classList.contains('seg-ctrl')){\n var col=group.parentElement;\n var btns=group.querySelectorAll('.seg-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n segBtn.classList.add('active');\n var panes=col.querySelectorAll('.seg-pane');\n for(var pi=0;pi<panes.length;pi++){panes[pi].classList.remove('active');var vid=panes[pi].querySelector('video');if(vid)vid.pause();}\n var tp=col.querySelector('[data-seg-pane=\"'+segBtn.getAttribute('data-seg')+'\"]');\n if(tp)tp.classList.add('active');\n }\n return;\n }\n\n /* Network DevTools: request row → expand/collapse detail */\n var ndtRow=target.closest('.ndt-row');\n if(ndtRow){\n var idx=ndtRow.getAttribute('data-ndt-idx');\n var det=document.getElementById('ndt-d-'+idx);\n if(det){\n var isOpen=det.classList.toggle('show');\n ndtRow.classList.toggle('ndt-row--open',isOpen);\n /* Close other open details */\n var list=ndtRow.parentElement;\n if(list){var others=list.querySelectorAll('.ndt-detail.show');for(var oi=0;oi<others.length;oi++){if(others[oi]!==det){others[oi].classList.remove('show');var pr=others[oi].previousElementSibling;if(pr)pr.classList.remove('ndt-row--open');}}}\n }\n return;\n }\n\n /* Network DevTools: detail tab switch */\n var ndtTab=target.closest('.ndt-detail-tab');\n if(ndtTab){\n var tgt=ndtTab.getAttribute('data-ndt-target');\n var pane=ndtTab.getAttribute('data-ndt-tab');\n var det=document.getElementById(tgt);\n if(det){\n var tabs=det.querySelectorAll('.ndt-detail-tab');\n for(var ti=0;ti<tabs.length;ti++)tabs[ti].classList.remove('active');\n ndtTab.classList.add('active');\n var panes=det.querySelectorAll('.ndt-detail-pane');\n for(var pi=0;pi<panes.length;pi++)panes[pi].classList.remove('active');\n var tp=det.querySelector('[data-ndt-pane=\"'+pane+'\"]');\n if(tp)tp.classList.add('active');\n }\n return;\n }\n\n /* Network DevTools: type filter */\n var ndtFilterBtn=target.closest('.ndt-filter-btn');\n if(ndtFilterBtn){\n var filterType=ndtFilterBtn.getAttribute('data-ndt-filter');\n var toolbar=ndtFilterBtn.closest('.ndt-toolbar');\n if(toolbar){\n var btns=toolbar.querySelectorAll('.ndt-filter-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n ndtFilterBtn.classList.add('active');\n var ndt=toolbar.closest('.ndt');\n if(ndt){ndtApplyFilters(ndt,filterType,null);}\n }\n return;\n }\n\n /* Stack trace toggle */\n if(target.classList.contains('stack-toggle-btn')){var se=document.getElementById(target.getAttribute('data-stack'));if(se){var sv=se.classList.toggle('show');target.textContent=sv?'Hide stack trace':'Show stack trace';}return;}\n\n /* Lightbox close */\n if(target.classList.contains('lightbox-overlay')||target.classList.contains('lightbox-close')){closeLightbox();return;}\n\n /* Theme toggle */\n var themeBtn=target.closest('.theme-btn');\n if(themeBtn){setTheme(themeBtn.getAttribute('data-theme-val'));return;}\n\n /* Drawer close */\n if(target.closest('#drawer-close')){closeDrawer();return;}\n if(target.closest('#drawer-backdrop')){closeDrawer();return;}\n });\n\n /* Search input — main test search + network filter search */\n document.addEventListener('input',function(e){\n if(e.target.id==='search-input')doSearch(e.target.value);\n if(e.target.hasAttribute('data-ndt-search')){\n var ndt=e.target.closest('.ndt');\n if(ndt){\n var activeBtn=ndt.querySelector('.ndt-filter-btn.active');\n var filterType=activeBtn?activeBtn.getAttribute('data-ndt-filter'):'all';\n ndtApplyFilters(ndt,filterType,e.target.value);\n }\n }\n });\n\n /* Keyboard */\n document.addEventListener('keydown',function(e){\n if(e.key==='Escape'){closeLightbox();closeDrawer();}\n });\n\n /* ── Theme Management ── */\n function getThemePref(){return localStorage.getItem('tr-theme')||'system'}\n function resolveTheme(pref){return pref==='system'?(window.matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):pref}\n function applyTheme(){\n var pref=getThemePref();\n document.documentElement.setAttribute('data-theme',resolveTheme(pref));\n var btns=document.querySelectorAll('.theme-btn');\n for(var i=0;i<btns.length;i++){btns[i].classList.toggle('active',btns[i].getAttribute('data-theme-val')===pref);}\n }\n function setTheme(pref){localStorage.setItem('tr-theme',pref);applyTheme();}\n try{window.matchMedia('(prefers-color-scheme:light)').addEventListener('change',function(){if(getThemePref()==='system')applyTheme();});}catch(e){}\n\n /* ── Lightbox ── */\n function openLightbox(src){\n var o=document.createElement('div');o.className='lightbox-overlay';\n o.innerHTML='<img src=\"'+src.replace(/\"/g,'"')+'\" alt=\"Screenshot\">';\n var b=document.createElement('button');b.className='lightbox-close';b.innerHTML='×';\n document.body.appendChild(o);document.body.appendChild(b);\n }\n function closeLightbox(){\n var o=document.querySelector('.lightbox-overlay');if(o)o.remove();\n var b=document.querySelector('.lightbox-close');if(b)b.remove();\n }\n`;\n","/**\n * HTML Template for TestRelic Analytics Report\n *\n * Orchestrator: imports CSS, logo, and JS modules; assembles them\n * into a single self-contained HTML document.\n */\n\nimport { CSS } from './html-css.js';\nimport { LOGO_SVG, LOGO_SVG_RAW } from './html-logo.js';\nimport { JS_RENDER } from './html-js-render.js';\nimport { JS_NETWORK } from './html-js-network.js';\nimport { JS_FILTERS } from './html-js-filters.js';\nimport { JS_INTERACTIONS } from './html-js-interactions.js';\n\nconst JS = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n ${JS_RENDER}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_INTERACTIONS}\n applyTheme();\n renderRunMeta();\n renderSummary();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n})();`;\n\nexport function renderHtmlDocument(reportJson: string): string {\n // Escape </ sequences to prevent HTML parser from closing <script> tags prematurely.\n // JSON spec allows \\/ as an escape for /, so <\\/ is transparent to JSON.parse.\n const safeJson = reportJson.replace(/<\\//g, '<\\\\/');\n return `<!-- TestRelic Analytics Report — self-contained, no external dependencies -->\n<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<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';\">\n<title>TestRelic Analytics Report</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}</style>\n<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>\n</head>\n<body>\n<div class=\"wrap\">\n <div class=\"top-bar\">\n <div class=\"brand\">\n ${LOGO_SVG}\n <div class=\"brand-text\">\n <span class=\"brand-title\">TestRelic</span>\n <span class=\"brand-sub\">Analytics Report</span>\n </div>\n </div>\n <div class=\"run-meta\" id=\"run-meta\"></div>\n <div class=\"theme-toggle\" id=\"theme-toggle\">\n <button class=\"theme-btn\" data-theme-val=\"system\" title=\"System\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/></svg></button>\n <button class=\"theme-btn\" data-theme-val=\"light\" title=\"Light\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/></svg></button>\n <button class=\"theme-btn\" data-theme-val=\"dark\" title=\"Dark\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg></button>\n </div>\n <a class=\"cta-btn\" href=\"https://testrelic.co\" 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\">–</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>\n </div>\n <div id=\"summary-strip\" class=\"summary-strip\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n</div>\n<div class=\"drawer-backdrop\" id=\"drawer-backdrop\"></div>\n<aside class=\"drawer\" id=\"drawer\">\n <div class=\"drawer-bar\">\n <span class=\"drawer-bar-title\">Test Details</span>\n <button class=\"drawer-close\" id=\"drawer-close\">×</button>\n </div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n</aside>\n<script id=\"report-data\" type=\"application/json\">${safeJson}</script>\n<script>${JS}</script>\n</body>\n</html>`;\n}\n","/**\n * Cross-platform browser auto-open utility.\n *\n * Opens a file in the user's default browser.\n * Fire-and-forget — never throws, errors logged to stderr.\n */\n\nimport { exec } from 'node:child_process';\n\nexport function openInBrowser(filePath: string): void {\n try {\n const platform = process.platform;\n let command: string;\n\n if (platform === 'darwin') {\n command = `open \"${filePath}\"`;\n } else if (platform === 'win32') {\n command = `start \"\" \"${filePath}\"`;\n } else {\n command = `xdg-open \"${filePath}\"`;\n }\n\n exec(command, (err) => {\n if (err) {\n process.stderr.write(\n `[testrelic] Failed to open browser: ${err.message}\\n`,\n );\n }\n });\n } catch {\n // Never crash the reporter\n }\n}\n","/**\n * HTML Report Generator\n *\n * Generates a self-contained HTML report from a TestRunReport.\n * Utility functions for HTML escaping, ANSI stripping, and formatting.\n */\n\nimport { writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { TestRunReport } from '@testrelic/core';\nimport type { ResolvedConfig } from './config.js';\nimport { renderHtmlDocument } from './html-template.js';\nimport { openInBrowser } from './browser-open.js';\n\n// ---------------------------------------------------------------------------\n// Utility Functions\n// ---------------------------------------------------------------------------\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\nexport function escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (ch) => HTML_ESCAPE_MAP[ch] ?? ch);\n}\n\n// SAFETY: Regex targets all ANSI SGR escape sequences (color/style codes)\nconst ANSI_REGEX = /\\u001b\\[[0-9;]*m/g;\n\nexport function stripAnsi(str: string): string {\n return str.replace(ANSI_REGEX, '');\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${Math.round(ms)}ms`;\n if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;\n const minutes = Math.floor(ms / 60_000);\n const seconds = Math.round((ms % 60_000) / 1000);\n return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ---------------------------------------------------------------------------\n// HTML Report Generation\n// ---------------------------------------------------------------------------\n\nexport function generateHtmlReport(report: TestRunReport): string {\n const reportJson = JSON.stringify(report);\n return renderHtmlDocument(reportJson);\n}\n\nexport function writeHtmlReport(report: TestRunReport, config: ResolvedConfig): void {\n try {\n const html = generateHtmlReport(report);\n const outputPath = config.htmlReportPath;\n const dir = dirname(outputPath);\n\n mkdirSync(dir, { recursive: true });\n\n // Atomic write: write to temp, then rename\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, html, 'utf-8');\n renameSync(tmpPath, outputPath);\n\n // Auto-open in browser if configured and not in CI\n if (config.openReport && report.ci === null) {\n const absolutePath = resolve(outputPath);\n openInBrowser(absolutePath);\n }\n } catch (err) {\n // FR-026: log to stderr, never crash\n process.stderr.write(\n `[testrelic] Failed to write HTML report: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n}\n","/**\n * ArtifactManager — copies test artifacts to structured output folders.\n *\n * Handles screenshot/video files from Playwright's result.attachments,\n * organizing them into per-test folders with sanitized names.\n */\n\nimport { mkdirSync, copyFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestArtifacts } from '@testrelic/core';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * Sanitize a test title into a filesystem-safe folder name.\n *\n * Rules:\n * 1. Replace characters not in [a-zA-Z0-9-_ ] with hyphens\n * 2. Replace spaces with hyphens\n * 3. Collapse consecutive hyphens\n * 4. Trim leading/trailing hyphens\n * 5. Truncate to 100 characters\n */\nexport function sanitizeFolderName(title: string): string {\n let name = title\n .replace(/[^a-zA-Z0-9\\-_ ]/g, '-')\n .replace(/\\s+/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '');\n\n if (name.length > 100) {\n name = name.substring(0, 100).replace(/-+$/, '');\n }\n\n return name || 'unnamed-test';\n}\n\n/**\n * Copy test artifacts from Playwright's temp locations to structured output folders.\n *\n * @returns TestArtifacts with relative paths, or null if no artifacts found.\n */\nexport function copyArtifacts(\n attachments: readonly Attachment[],\n testTitle: string,\n retryCount: number,\n outputDir: string,\n): TestArtifacts | null {\n const screenshot = attachments.find(\n (a) => a.name === 'screenshot' && a.path,\n );\n const video = attachments.find(\n (a) => a.name === 'video' && a.path,\n );\n\n if (!screenshot && !video) {\n return null;\n }\n\n let folderName = sanitizeFolderName(testTitle);\n if (retryCount > 0) {\n folderName += `--retry-${retryCount}`;\n }\n\n const artifactDir = join(outputDir, 'artifacts', folderName);\n const result: { screenshot?: string; video?: string } = {};\n\n try {\n mkdirSync(artifactDir, { recursive: true });\n } catch {\n // Cannot create directory — skip artifact capture\n return null;\n }\n\n if (screenshot?.path) {\n try {\n if (existsSync(screenshot.path)) {\n const ext = extname(screenshot.path) || '.png';\n const destName = `screenshot${ext}`;\n copyFileSync(screenshot.path, join(artifactDir, destName));\n result.screenshot = `artifacts/${folderName}/${destName}`;\n }\n } catch {\n // FR-026: never crash — skip this artifact\n }\n }\n\n if (video?.path) {\n try {\n if (existsSync(video.path)) {\n const ext = extname(video.path) || '.webm';\n const destName = `video${ext}`;\n copyFileSync(video.path, join(artifactDir, destName));\n result.video = `artifacts/${folderName}/${destName}`;\n }\n } catch {\n // FR-026: never crash — skip this artifact\n }\n }\n\n if (!result.screenshot && !result.video) {\n return null;\n }\n\n return result;\n}\n","/**\n * TestRelicReporter — Playwright custom reporter\n *\n * Captures test execution data and produces a structured JSON timeline.\n * All hooks are wrapped in try/catch (FR-026): never crashes the test run.\n */\n\nimport { randomUUID, createHash } from 'node:crypto';\nimport { mkdirSync, writeFileSync, renameSync } from 'node:fs';\nimport { dirname, relative } from 'node:path';\nimport type {\n CapturedNetworkRequest,\n ReporterConfig,\n TestRunReport,\n Summary,\n TimelineEntry,\n TestResult as TRTestResult,\n TestArtifacts,\n NavigationAnnotation,\n FailureDiagnostic,\n TestStatus,\n TestType,\n} from '@testrelic/core';\nimport { resolveConfig } from './config.js';\nimport type { ResolvedConfig } from './config.js';\nimport { SCHEMA_VERSION } from './schema.js';\nimport { extractCodeSnippet } from './code-extractor.js';\nimport { createRedactor } from './redaction.js';\nimport { detectCI } from './ci-detector.js';\nimport { writeHtmlReport } from './html-report.js';\nimport { copyArtifacts } from './artifact-manager.js';\n\n// Playwright types — imported for type annotations only\ninterface PwFullConfig {\n rootDir: string;\n [key: string]: unknown;\n}\n\ninterface PwSuite {\n allTests(): PwTestCase[];\n}\n\ninterface PwTestCase {\n title: string;\n titlePath(): string[];\n annotations: Array<{ type: string; description?: string }>;\n tags?: string[];\n location: { file: string; line: number; column: number };\n results: PwTestResult[];\n outcome(): 'expected' | 'unexpected' | 'skipped' | 'flaky';\n expectedStatus: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';\n id: string;\n}\n\ninterface PwTestResult {\n status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';\n duration: number;\n startTime: Date;\n retry: number;\n errors: PwTestError[];\n attachments: Array<{ name: string; contentType: string; path?: string; body?: Buffer }>;\n}\n\ninterface PwTestError {\n message?: string;\n stack?: string;\n location?: { file: string; line: number; column: number };\n snippet?: string;\n}\n\ninterface PwFullResult {\n status: 'passed' | 'failed' | 'timedout' | 'interrupted';\n startTime: Date;\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helper functions (private to this module)\n// ---------------------------------------------------------------------------\n\nfunction mapPlaywrightStatus(status: string): TestStatus {\n switch (status) {\n case 'passed': return 'passed';\n case 'failed': return 'failed';\n case 'timedOut': return 'timedout';\n case 'skipped': return 'skipped';\n case 'interrupted': return 'failed';\n default: return 'failed';\n }\n}\n\nfunction generateTestId(filePath: string, suiteName: string, title: string): string {\n const input = `${filePath}::${suiteName}::${title}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 16);\n}\n\nfunction getSuiteName(titlePath: string[]): string {\n // titlePath: ['', 'project', 'file.spec.ts', ...suites..., 'test title']\n // <= 4 elements means no describe block (root, project, file, test)\n if (titlePath.length <= 4) return '';\n return titlePath[titlePath.length - 2];\n}\n\nfunction getRetryStatus(results: PwTestResult[]): string | null {\n const passedIndex = results.findIndex((r) => r.status === 'passed');\n return passedIndex > 0 ? `passed on retry ${passedIndex}` : null;\n}\n\nfunction getTestType(tags: string[], filePath: string): TestType {\n const typeOrder: TestType[] = ['e2e', 'api', 'unit'];\n for (const type of typeOrder) {\n if (tags.some((tag) => tag === `@${type}` || tag === type)) {\n return type;\n }\n }\n for (const type of typeOrder) {\n if (filePath.includes(`/${type}/`)) {\n return type;\n }\n }\n return 'unknown';\n}\n\n// Internal state for collected test data\ninterface CollectedTest {\n titlePath: string[];\n title: string;\n status: TestStatus;\n duration: number;\n startedAt: string;\n completedAt: string;\n retryCount: number;\n tags: string[];\n failure: FailureDiagnostic | null;\n specFile: string;\n navigations: NavigationAnnotation[];\n testId: string;\n filePath: string;\n suiteName: string;\n testType: TestType;\n isFlaky: boolean;\n retryStatus: string | null;\n expectedStatus: TestStatus;\n actualStatus: TestStatus;\n artifacts: TestArtifacts | null;\n networkRequests: CapturedNetworkRequest[] | null;\n}\n\nexport default class TestRelicReporter {\n private config: ResolvedConfig;\n private rootDir = '';\n private startedAt = '';\n private testRunId = '';\n private collectedTests: CollectedTest[] = [];\n\n constructor(options?: Partial<ReporterConfig>) {\n this.config = resolveConfig(options);\n }\n\n onBegin(config: PwFullConfig, _suite: PwSuite): void {\n try {\n this.rootDir = config.rootDir;\n this.startedAt = new Date().toISOString();\n this.testRunId = this.config.testRunId ?? randomUUID();\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n onTestEnd(test: PwTestCase, result: PwTestResult): void {\n try {\n const lastResult = result;\n const outcome = test.outcome();\n\n // Determine status using expanded mapping\n let status: TestStatus;\n if (outcome === 'flaky') {\n status = 'flaky';\n } else if (outcome === 'skipped') {\n status = 'skipped';\n } else {\n status = mapPlaywrightStatus(lastResult.status);\n }\n\n const startedAt = lastResult.startTime.toISOString();\n const completedAt = new Date(lastResult.startTime.getTime() + lastResult.duration).toISOString();\n\n // Extract tags: prefer test.tags (1.42+), fallback to annotations\n const tags = test.tags\n ? [...test.tags]\n : test.annotations\n .filter((a) => a.type === 'tag')\n .map((a) => a.description ?? '');\n\n // Extract navigation annotations\n const navigations = test.annotations\n .filter((a) => a.type === 'lambdatest-navigation' && a.description)\n .map((a) => {\n try {\n return JSON.parse(a.description!) as NavigationAnnotation;\n } catch {\n return null;\n }\n })\n .filter((n): n is NavigationAnnotation => n !== null);\n\n // Extract captured network requests (from __testrelic_network_requests annotation)\n let networkRequests: CapturedNetworkRequest[] | null = null;\n const networkReqAnnotation = test.annotations.find(\n (a) => a.type === '__testrelic_network_requests' && a.description,\n );\n if (networkReqAnnotation) {\n try {\n networkRequests = JSON.parse(networkReqAnnotation.description!) as CapturedNetworkRequest[];\n } catch {\n // Ignore parse errors\n }\n }\n\n // Build failure diagnostic\n let failure: FailureDiagnostic | null = null;\n if (status === 'failed' || status === 'flaky') {\n const errors = status === 'flaky'\n ? (test.results.find((r) => r.status !== 'passed')?.errors ?? [])\n : lastResult.errors;\n const firstError = errors[0];\n if (firstError) {\n const redact = createRedactor(this.config.redactPatterns);\n const errorLine = firstError.location?.line ?? null;\n let codeSnippet: string | null = null;\n if (this.config.includeCodeSnippets && errorLine !== null && firstError.location?.file) {\n codeSnippet = extractCodeSnippet(\n firstError.location.file,\n errorLine,\n this.config.codeContextLines,\n );\n if (codeSnippet) codeSnippet = redact(codeSnippet);\n }\n\n failure = {\n message: redact(firstError.message ?? 'Unknown error'),\n line: errorLine,\n code: codeSnippet,\n stack: this.config.includeStackTrace ? (firstError.stack ? redact(firstError.stack) : null) : null,\n };\n }\n }\n\n const titlePath = test.titlePath().filter(Boolean);\n const specFile = relative(this.rootDir || '.', test.location.file);\n\n // Extract enhanced metadata\n const suiteName = getSuiteName(titlePath);\n const title = titlePath.join(' > ');\n const filePath = specFile;\n const testId = generateTestId(filePath, suiteName, title);\n const testType = getTestType(tags, filePath);\n const isFlaky = outcome === 'flaky';\n const retryStatus = getRetryStatus(test.results);\n const expectedStatus = mapPlaywrightStatus(test.expectedStatus);\n const actualStatus = mapPlaywrightStatus(lastResult.status);\n\n // Extract artifacts (screenshots/videos) from Playwright attachments\n let artifacts: TestArtifacts | null = null;\n if (this.config.includeArtifacts && status !== 'skipped' && lastResult.attachments) {\n const outputDir = dirname(this.config.outputPath);\n const lastTitle = titlePath[titlePath.length - 1] ?? test.title;\n artifacts = copyArtifacts(lastResult.attachments, lastTitle, lastResult.retry, outputDir);\n }\n\n this.collectedTests.push({\n titlePath,\n title,\n status,\n duration: lastResult.duration,\n startedAt,\n completedAt,\n retryCount: test.results.length - 1,\n tags,\n failure,\n specFile,\n navigations,\n testId,\n filePath,\n suiteName,\n testType,\n isFlaky,\n retryStatus,\n expectedStatus,\n actualStatus,\n artifacts,\n networkRequests,\n });\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n onEnd(_result: PwFullResult): void {\n try {\n const completedAt = new Date().toISOString();\n const startedAtTime = new Date(this.startedAt).getTime();\n const completedAtTime = new Date(completedAt).getTime();\n const totalDuration = completedAtTime - startedAtTime;\n\n // Build timeline from navigation data\n const timeline = this.buildTimeline();\n\n // Build summary\n const summary = this.buildSummary();\n\n const report: TestRunReport = {\n schemaVersion: SCHEMA_VERSION,\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: detectCI(),\n metadata: this.config.metadata,\n timeline,\n shardRunIds: null,\n };\n\n this.writeReport(report);\n writeHtmlReport(report, this.config);\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n printsToStdio(): boolean {\n return false;\n }\n\n private buildTimeline(): TimelineEntry[] {\n const entries: TimelineEntry[] = [];\n\n for (const test of this.collectedTests) {\n if (test.navigations.length === 0) {\n // No navigation data — create dummy entry\n entries.push({\n url: 'about:blank',\n navigationType: 'dummy',\n visitedAt: test.startedAt,\n duration: test.duration,\n specFile: test.specFile,\n domContentLoadedAt: null,\n networkIdleAt: null,\n networkStats: null,\n tests: [this.toTestResult(test)],\n });\n continue;\n }\n\n // Create timeline entry per navigation\n for (let i = 0; i < test.navigations.length; i++) {\n const nav = test.navigations[i];\n const nextNav = test.navigations[i + 1];\n\n // Apply navigation type filter\n if (this.config.navigationTypes !== null && !this.config.navigationTypes.includes(nav.navigationType)) {\n continue;\n }\n\n const navTime = new Date(nav.timestamp).getTime();\n const nextTime = nextNav\n ? new Date(nextNav.timestamp).getTime()\n : new Date(test.completedAt).getTime();\n const duration = nextTime - navTime;\n\n entries.push({\n url: nav.url,\n navigationType: nav.navigationType,\n visitedAt: nav.timestamp,\n duration: Math.max(0, duration),\n specFile: test.specFile,\n domContentLoadedAt: nav.domContentLoadedAt ?? null,\n networkIdleAt: nav.networkIdleAt ?? null,\n networkStats: nav.networkStats ?? null,\n tests: [this.toTestResult(test)],\n });\n }\n }\n\n // Sort chronologically\n entries.sort((a, b) => new Date(a.visitedAt).getTime() - new Date(b.visitedAt).getTime());\n\n return entries;\n }\n\n private buildSummary(): Summary {\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n\n for (const test of this.collectedTests) {\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n }\n\n return {\n total: this.collectedTests.length,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n };\n }\n\n private toTestResult(test: CollectedTest): TRTestResult {\n return {\n title: test.title,\n status: test.status,\n duration: test.duration,\n startedAt: test.startedAt,\n completedAt: test.completedAt,\n retryCount: test.retryCount,\n tags: test.tags,\n failure: test.failure,\n testId: test.testId,\n filePath: test.filePath,\n suiteName: test.suiteName,\n testType: test.testType,\n isFlaky: test.isFlaky,\n retryStatus: test.retryStatus,\n expectedStatus: test.expectedStatus,\n actualStatus: test.actualStatus,\n artifacts: test.artifacts,\n networkRequests: test.networkRequests,\n };\n }\n\n private writeReport(report: TestRunReport): void {\n try {\n const json = JSON.stringify(report, null, 2);\n const outputPath = this.config.outputPath;\n const dir = dirname(outputPath);\n\n mkdirSync(dir, { recursive: true });\n\n // Atomic write: write to temp, then rename\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, outputPath);\n } catch (err) {\n // FR-026: log to stderr, never crash\n process.stderr.write(\n `[testrelic] Failed to write report: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n}\n","/**\n * @testrelic/playwright-analytics — Main entry point\n *\n * Default export: TestRelicReporter class\n * Named export: recordNavigation helper\n */\n\nexport { default } from './reporter.js';\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n CIMetadata,\n TimelineEntry,\n TestResult,\n FailureDiagnostic,\n NetworkStats,\n ReporterConfig,\n} from './types.js';\n\nexport { SCHEMA_VERSION } from './schema.js';\n\nimport type { NavigationType, NavigationAnnotation } from '@testrelic/core';\n\nlet warned = false;\n\n/**\n * Records a manual navigation event in the current test's annotations.\n * Use for navigation the auto-tracker cannot detect (iframes, web workers).\n *\n * Requires access to the current test info. If not available, logs a\n * warning once and no-ops.\n */\nexport function recordNavigation(\n testInfo: { annotations: Array<{ type: string; description?: string }> } | null | undefined,\n url: string,\n navigationType: NavigationType = 'manual_record',\n): void {\n if (!testInfo || !testInfo.annotations) {\n if (!warned) {\n warned = true;\n process.stderr.write('[testrelic] recordNavigation: reporter not active, navigation not recorded\\n');\n }\n return;\n }\n\n const annotation: NavigationAnnotation = {\n url,\n navigationType,\n timestamp: new Date().toISOString(),\n };\n\n testInfo.annotations.push({\n type: 'lambdatest-navigation',\n description: JSON.stringify(annotation),\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/schema.ts","../src/code-extractor.ts","../src/redaction.ts","../src/ci-detector.ts","../src/html-css.ts","../src/html-logo.ts","../src/html-js-render.ts","../src/html-js-network.ts","../src/html-js-filters.ts","../src/html-js-interactions.ts","../src/html-template.ts","../src/browser-open.ts","../src/html-report.ts","../src/artifact-manager.ts","../src/timeline-builder.ts","../src/summary-builder.ts","../src/console-summary.ts","../src/reporter.ts","../src/navigation-tracker.ts","../src/assertion-tracker.ts","../src/api-redactor.ts","../src/api-url-filter.ts","../src/api-request-tracker.ts","../src/fixture.ts","../src/api-fixture.ts","../src/index.ts"],"names":["DEFAULT_REDACTION_PATTERNS","DEFAULT_REDACT_HEADERS","DEFAULT_REDACT_BODY_FIELDS","resolveApiConfig","options","target","resolveConfig","isValidConfig","createError","ErrorCode","outputPath","SCHEMA_VERSION","extractCodeSnippet","filePath","line","contextLines","lines","readFileSync","startLine","endLine","snippetLines","i","marker","lineNum","createRedactor","patterns","text","result","pattern","escaped","flags","cloned","detectGitHubActions","env","detectGitLabCI","detectJenkins","branch","detectCircleCI","detectors","detectCI","envVars","detect","CSS","LOGO_SVG_RAW","LOGO_SVG","JS_RENDER","JS_NETWORK","JS_FILTERS","JS_INTERACTIONS","JS","renderHtmlDocument","reportJson","safeJson","openInBrowser","platform","command","exec","err","generateHtmlReport","report","writeHtmlReport","config","html","dir","dirname","mkdirSync","tmpPath","writeFileSync","renameSync","absolutePath","resolve","sanitizeFolderName","title","name","copyArtifacts","attachments","testTitle","retryCount","outputDir","screenshot","a","video","folderName","artifactDir","join","existsSync","destName","extname","copyFileSync","buildUnifiedTimeline","tests","allSteps","test","identity","buildTestIdentity","testResult","toTestResult","navSteps","buildNavigationSteps","apiSteps","buildApiCallSteps","compareSteps","recalculateNavigationDurations","step","steps","nav","assertions","call","linkedAssertions","response","parseBody","b","timeA","timeB","typeOrder","typeA","typeB","extractCallNumber","callId","match","navTime","nextStepTime","j","t","endTime","body","nearestRankPercentile","sorted","percentile","n","index","stripUrlQuery","rawUrl","parsed","getStatusRange","statusCode","buildEnrichedSummary","timelineLength","passed","failed","flaky","skipped","timedout","allApiCalls","totalApiCalls","apiUrlSet","methodCounts","statusRanges","responseTimes","apiResponseTime","sum","acc","v","totalAssertions","passedAssertions","failedAssertions","assertion","totalNavigations","navUrlSet","pad","padding","printConsoleSummary","summary","quiet","top","bottom","blank","output","parts","methodParts","count","method","statusParts","range","mapPlaywrightStatus","status","generateTestId","suiteName","input","createHash","getSuiteName","titlePath","getRetryStatus","results","passedIndex","getTestType","tags","type","tag","API_CONFIG_ANNOTATION","serializeApiConfig","_key","value","TestRelicReporter","_suite","randomUUID","_result","lastResult","outcome","startedAt","completedAt","navigations","networkRequests","apiCalls","apiAssertions","payloadAttachments","ATTACHMENT_NAME","attachmentParsed","att","isTestRelicDataPayload","networkReqAnnotation","apiCallsAnnotation","apiAssertionsAnnotation","failure","firstError","r","redact","errorLine","codeSnippet","specFile","relative","testId","testType","isFlaky","retryStatus","expectedStatus","actualStatus","artifacts","lastTitle","startedAtTime","totalDuration","timeline","testData","json","TEXT_CONTENT_TYPES","isTextContentType","contentType","lower","prefix","truncateBody","maxSize","bytes","NavigationTracker","page","id","pending","record","testInfo","annotation","requests","event","handler","self","url","onDomContentLoaded","onFrameNavigated","frame","frameObj","lastRecord","onConsoleMessage","msg","msgObj","data","onRequest","request","req","reqId","postData","postDataTruncated","onResponse","resp","contentLength","resourceType","typeKey","responseTimeMs","respHeaders","responseSize","binary","bodyPromise","responseBody","responseBodyTruncated","captured","onRequestFailed","origPush","origReplace","args","CALL_ID_SYMBOL","ASSERTION_ANNOTATION_TYPE","valueCallIdMap","AssertionTracker","REDACTED","redactHeaders","headers","redactList","lowerList","h","key","redactBodyFields","fieldSet","redacted","walkAndRedact","fields","item","val","shouldTrackApiUrl","include","exclude","matchesPattern","globToRegex","glob","char","HTTP_METHODS","ANNOTATION_TYPE","extractRequestBody","multipart","descriptor","_ApiRequestTracker","context","assertionTracker","apiConfig","original","origDispose","origHeaders","origHeadersArray","origJson","origStatus","origStatusText","origOk","origText","origBody","timestamp","httpMethod","requestBody","startTime","performance","errorRequestBody","errorRecord","rawResponseHeaders","capturedRequestHeaders","capturedResponseHeaders","capturedRequestBody","capturedResponseBody","ApiRequestTracker","LEGACY_TRACK_API_CALLS_ANNOTATION","readApiConfig","configAnnotation","deserializeRegExp","legacyAnnotation","source","testRelicFixture","use","tracker","payload","PAYLOAD_VERSION","ATTACHMENT_CONTENT_TYPE","apiTracker","base","testRelicApiFixture","warned","recordNavigation","navigationType"],"mappings":"kRAQO,IAAMA,EAAAA,CAAkD,CAC7D,mBAAA,CACA,iCAAA,CACA,0DAAA,CACA,mBACF,CAAA,CAuBaC,EAAAA,CAA4C,CACvD,eAAA,CACA,QAAA,CACA,aACA,WACF,CAAA,CAEaC,EAAAA,CAAgD,CAC3D,UAAA,CACA,QAAA,CACA,OAAA,CACA,QAAA,CACA,SACF,CAAA,CAeO,SAASC,CAAAA,CAAiBC,CAAAA,CAAsD,CACrF,IAAMC,CAAAA,CAAS,OAAO,MAAA,CAAO,IAAI,CAAA,CACjC,OAAAA,CAAAA,CAAO,aAAA,CAAgBD,CAAAA,EAAS,aAAA,EAAiB,IAAA,CACjDC,CAAAA,CAAO,qBAAA,CAAwBD,CAAAA,EAAS,qBAAA,EAAyB,IAAA,CACjEC,CAAAA,CAAO,sBAAA,CAAyBD,CAAAA,EAAS,sBAAA,EAA0B,IAAA,CACnEC,CAAAA,CAAO,kBAAA,CAAqBD,CAAAA,EAAS,kBAAA,EAAsB,IAAA,CAC3DC,CAAAA,CAAO,mBAAA,CAAsBD,CAAAA,EAAS,mBAAA,EAAuB,IAAA,CAC7DC,CAAAA,CAAO,iBAAA,CAAoBD,CAAAA,EAAS,mBAAqB,IAAA,CACzDC,CAAAA,CAAO,aAAA,CAAgBD,CAAAA,EAAS,aAAA,EAAiB,CAAC,GAAGH,EAAsB,CAAA,CAC3EI,CAAAA,CAAO,gBAAA,CAAmBD,CAAAA,EAAS,gBAAA,EAAoB,CAAC,GAAGF,EAA0B,EACrFG,CAAAA,CAAO,cAAA,CAAiBD,CAAAA,EAAS,cAAA,EAAkB,EAAC,CACpDC,CAAAA,CAAO,cAAA,CAAiBD,CAAAA,EAAS,cAAA,EAAkB,EAAC,CAC7C,MAAA,CAAO,MAAA,CAAOC,CAAM,CAC7B,CAEO,SAASC,CAAAA,CAAcF,CAAAA,CAAmD,CAC/E,GAAIA,CAAAA,GAAY,MAAA,EAAa,CAACG,kBAAAA,CAAcH,CAAO,CAAA,CACjD,MAAMI,gBAAAA,CAAYC,cAAAA,CAAU,cAAA,CAAgB,gCAAgC,CAAA,CAI9E,IAAMJ,CAAAA,CAAS,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA,CACjCA,CAAAA,CAAO,UAAA,CAAaD,CAAAA,EAAS,UAAA,EAAc,wCAAA,CAC3CC,CAAAA,CAAO,iBAAA,CAAoBD,CAAAA,EAAS,iBAAA,EAAqB,MACzDC,CAAAA,CAAO,mBAAA,CAAsBD,CAAAA,EAAS,mBAAA,EAAuB,IAAA,CAC7DC,CAAAA,CAAO,gBAAA,CAAmBD,CAAAA,EAAS,gBAAA,EAAoB,CAAA,CACvDC,CAAAA,CAAO,mBAAA,CAAsBD,CAAAA,EAAS,mBAAA,EAAuB,IAAA,CAC7DC,CAAAA,CAAO,gBAAkBD,CAAAA,EAAS,eAAA,EAAmB,IAAA,CACrDC,CAAAA,CAAO,cAAA,CAAiB,CACtB,GAAGL,EAAAA,CACH,GAAII,CAAAA,EAAS,cAAA,EAAkB,EACjC,CAAA,CACAC,CAAAA,CAAO,SAAA,CAAYD,GAAS,SAAA,EAAa,IAAA,CACzCC,CAAAA,CAAO,QAAA,CAAWD,CAAAA,EAAS,QAAA,EAAY,IAAA,CACvC,IAAMM,CAAAA,CAAaL,CAAAA,CAAO,UAAA,CAC1B,OAAAA,CAAAA,CAAO,UAAA,CAAaD,CAAAA,EAAS,UAAA,EAAc,IAAA,CAC3CC,CAAAA,CAAO,cAAA,CAAiBD,CAAAA,EAAS,cAAA,EAAkBM,CAAAA,CAAW,OAAA,CAAQ,SAAA,CAAW,OAAO,CAAA,CACxFL,CAAAA,CAAO,gBAAA,CAAmBD,CAAAA,EAAS,gBAAA,EAAoB,IAAA,CACvDC,CAAAA,CAAO,cAAgBD,CAAAA,EAAS,aAAA,EAAiB,IAAA,CACjDC,CAAAA,CAAO,KAAA,CAAQD,CAAAA,EAAS,KAAA,EAAS,KAAA,CAE1B,MAAA,CAAO,MAAA,CAAOC,CAAM,CAC7B,CCvGO,IAAMM,CAAAA,CAAiB,QCIvB,SAASC,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACe,CACf,GAAI,CAEF,IAAMC,CAAAA,CADUC,eAAAA,CAAaJ,CAAAA,CAAU,OAAO,EACxB,KAAA,CAAM;AAAA,CAAI,CAAA,CAEhC,GAAIC,CAAAA,CAAO,CAAA,EAAKA,EAAOE,CAAAA,CAAM,MAAA,CAAQ,OAAO,IAAA,CAE5C,IAAME,CAAAA,CAAY,IAAA,CAAK,GAAA,CAAI,EAAGJ,CAAAA,CAAOC,CAAY,CAAA,CAC3CI,CAAAA,CAAU,IAAA,CAAK,GAAA,CAAIH,CAAAA,CAAM,MAAA,CAAQF,EAAOC,CAAY,CAAA,CAEpDK,CAAAA,CAAyB,EAAC,CAChC,IAAA,IAASC,CAAAA,CAAIH,CAAAA,CAAWG,GAAKF,CAAAA,CAASE,CAAAA,EAAAA,CAAK,CACzC,IAAMC,CAAAA,CAASD,CAAAA,GAAMP,CAAAA,CAAO,GAAA,CAAM,IAC5BS,CAAAA,CAAU,MAAA,CAAOF,CAAC,CAAA,CAAE,QAAA,CAAS,MAAA,CAAOF,CAAO,CAAA,CAAE,OAAQ,GAAG,CAAA,CAC9DC,CAAAA,CAAa,IAAA,CAAK,CAAA,EAAGE,CAAM,CAAA,CAAA,EAAIC,CAAO,MAAMP,CAAAA,CAAMK,CAAAA,CAAI,CAAC,CAAC,CAAA,CAAE,EAC5D,CAEA,OAAOD,EAAa,IAAA,CAAK;AAAA,CAAI,CAC/B,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CCpBO,SAASI,CAAAA,CACdC,CAAAA,CAC0B,CAC1B,OAAQC,CAAAA,EAAyB,CAC/B,IAAIC,CAAAA,CAASD,CAAAA,CACb,IAAA,IAAWE,CAAAA,IAAWH,CAAAA,CACpB,GAAI,OAAOG,CAAAA,EAAY,QAAA,CAAU,CAE/B,IAAMC,CAAAA,CAAUD,CAAAA,CAAQ,QAAQ,qBAAA,CAAuB,MAAM,CAAA,CAC7DD,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQ,IAAI,MAAA,CAAOE,CAAAA,CAAS,GAAG,CAAA,CAAG,YAAY,EAChE,CAAA,KAAO,CAEL,IAAMC,CAAAA,CAAQF,CAAAA,CAAQ,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,CAAIA,CAAAA,CAAQ,KAAA,CAAQA,CAAAA,CAAQ,KAAA,CAAQ,GAAA,CACtEG,CAAAA,CAAS,IAAI,MAAA,CAAOH,EAAQ,MAAA,CAAQE,CAAK,CAAA,CAC/CH,CAAAA,CAASA,CAAAA,CAAO,OAAA,CAAQI,CAAAA,CAAQ,YAAY,EAC9C,CAEF,OAAOJ,CACT,CACF,CCtBA,SAASK,EAAAA,CAAoBC,CAAAA,CAA4D,CACvF,OAAIA,CAAAA,CAAI,cAAA,GAAmB,MAAA,CAAe,IAAA,CACnC,CACL,QAAA,CAAU,gBAAA,CACV,OAAA,CAASA,CAAAA,CAAI,aAAA,EAAiB,IAAA,CAC9B,UAAWA,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC7B,MAAA,CAAQA,CAAAA,CAAI,eAAA,EAAmB,IACjC,CACF,CAEA,SAASC,EAAAA,CAAeD,CAAAA,CAA4D,CAClF,OAAIA,CAAAA,CAAI,SAAA,GAAc,MAAA,CAAe,IAAA,CAC9B,CACL,QAAA,CAAU,WAAA,CACV,OAAA,CAASA,CAAAA,CAAI,cAAA,EAAkB,IAAA,CAC/B,SAAA,CAAWA,CAAAA,CAAI,aAAA,EAAiB,IAAA,CAChC,MAAA,CAAQA,EAAI,gBAAA,EAAoBA,CAAAA,CAAI,kBAAA,EAAsB,IAC5D,CACF,CAEA,SAASE,EAAAA,CAAcF,CAAAA,CAA4D,CACjF,GAAI,CAACA,CAAAA,CAAI,WAAA,CAAa,OAAO,IAAA,CAC7B,IAAIG,CAAAA,CAASH,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC/B,OAAIG,CAAAA,EAAQ,UAAA,CAAW,SAAS,CAAA,GAC9BA,CAAAA,CAASA,CAAAA,CAAO,KAAA,CAAM,CAAgB,GAEjC,CACL,QAAA,CAAU,SAAA,CACV,OAAA,CAASH,CAAAA,CAAI,QAAA,EAAY,IAAA,CACzB,SAAA,CAAWA,CAAAA,CAAI,UAAA,EAAc,IAAA,CAC7B,MAAA,CAAAG,CACF,CACF,CAEA,SAASC,EAAAA,CAAeJ,CAAAA,CAA4D,CAClF,OAAIA,CAAAA,CAAI,QAAA,GAAa,MAAA,CAAe,IAAA,CAC7B,CACL,QAAA,CAAU,UAAA,CACV,OAAA,CAASA,CAAAA,CAAI,gBAAA,EAAoB,KACjC,SAAA,CAAWA,CAAAA,CAAI,WAAA,EAAe,IAAA,CAC9B,MAAA,CAAQA,CAAAA,CAAI,aAAA,EAAiB,IAC/B,CACF,CAEA,IAAMK,EAAAA,CAAwB,CAC5BN,EAAAA,CACAE,EAAAA,CACAC,EAAAA,CACAE,EACF,CAAA,CAEO,SAASE,CAAAA,CAASN,CAAAA,CAA6D,CACpF,IAAMO,CAAAA,CAAiB,OAAA,CAAQ,GAAA,CAC/B,IAAA,IAAWC,CAAAA,IAAUH,EAAAA,CAAW,CAC9B,IAAMX,CAAAA,CAASc,CAAAA,CAAOD,CAAO,CAAA,CAC7B,GAAIb,CAAAA,CAAQ,OAAOA,CACrB,CACA,OAAO,IACT,CC9DO,IAAMe,CAAAA,CAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACEZ,CAAA,CAAA,IAAMC,CAAAA,CAAe,CAAA;AAAA;AAAA;AAAA;AAAA,8BAAA,CAAA,CAMfC,CAAAA,CAAW,CAAA;AAAA;AAAA;AAAA;ACRjB,8BAAA,CAAA,CAAA,IAAMC,CAAAA,CAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;ACDlB,CAAA,CAAA,IAAMC,EAAAA,CAAa;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACKnB,CAAA,CAAA,IAAMC,EAAAA,CAAa;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;ACDnB,CAAA,CAAA,IAAMC,EAAAA,CAAkB;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;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","file":"index.cjs","sourcesContent":["/**\n * Config resolution with prototype pollution prevention.\n * Merges user options into defaults, freezes the result.\n */\n\nimport type { ReporterConfig, NavigationType } from '@testrelic/core';\nimport { isValidConfig, createError, ErrorCode } from '@testrelic/core';\n\nexport const DEFAULT_REDACTION_PATTERNS: (string | RegExp)[] = [\n /AKIA[A-Z0-9]{16}/g,\n /Bearer\\s+[A-Za-z0-9\\-._~+/]+=*/g,\n /-----BEGIN\\s+(RSA\\s+)?PRIVATE\\sKEY-----[\\s\\S]*?-----END/g,\n /\\/\\/[^:]+:[^@]+@/g,\n];\n\nexport interface ResolvedConfig {\n readonly outputPath: string;\n readonly includeStackTrace: boolean;\n readonly includeCodeSnippets: boolean;\n readonly codeContextLines: number;\n readonly includeNetworkStats: boolean;\n readonly navigationTypes: NavigationType[] | null;\n readonly redactPatterns: (string | RegExp)[];\n readonly testRunId: string | null;\n readonly metadata: Record<string, unknown> | null;\n readonly openReport: boolean;\n readonly htmlReportPath: string;\n readonly includeArtifacts: boolean;\n readonly trackApiCalls: boolean;\n readonly quiet: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// API-specific configuration defaults and resolution\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_REDACT_HEADERS: readonly string[] = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n];\n\nexport const DEFAULT_REDACT_BODY_FIELDS: readonly string[] = [\n 'password',\n 'secret',\n 'token',\n 'apiKey',\n 'api_key',\n];\n\nexport interface ResolvedApiConfig {\n readonly trackApiCalls: boolean;\n readonly captureRequestHeaders: boolean;\n readonly captureResponseHeaders: boolean;\n readonly captureRequestBody: boolean;\n readonly captureResponseBody: boolean;\n readonly captureAssertions: boolean;\n readonly redactHeaders: readonly string[];\n readonly redactBodyFields: readonly string[];\n readonly apiIncludeUrls: readonly (string | RegExp)[];\n readonly apiExcludeUrls: readonly (string | RegExp)[];\n}\n\nexport function resolveApiConfig(options?: Partial<ReporterConfig>): ResolvedApiConfig {\n const target = Object.create(null) as Record<string, unknown>;\n target.trackApiCalls = options?.trackApiCalls ?? true;\n target.captureRequestHeaders = options?.captureRequestHeaders ?? true;\n target.captureResponseHeaders = options?.captureResponseHeaders ?? true;\n target.captureRequestBody = options?.captureRequestBody ?? true;\n target.captureResponseBody = options?.captureResponseBody ?? true;\n target.captureAssertions = options?.captureAssertions ?? true;\n target.redactHeaders = options?.redactHeaders ?? [...DEFAULT_REDACT_HEADERS];\n target.redactBodyFields = options?.redactBodyFields ?? [...DEFAULT_REDACT_BODY_FIELDS];\n target.apiIncludeUrls = options?.apiIncludeUrls ?? [];\n target.apiExcludeUrls = options?.apiExcludeUrls ?? [];\n return Object.freeze(target) as unknown as ResolvedApiConfig;\n}\n\nexport function resolveConfig(options?: Partial<ReporterConfig>): ResolvedConfig {\n if (options !== undefined && !isValidConfig(options)) {\n throw createError(ErrorCode.CONFIG_INVALID, 'Invalid reporter configuration');\n }\n\n // Prototype pollution prevention: merge onto Object.create(null)\n const target = Object.create(null) as Record<string, unknown>;\n target.outputPath = options?.outputPath ?? './test-results/analytics-timeline.json';\n target.includeStackTrace = options?.includeStackTrace ?? false;\n target.includeCodeSnippets = options?.includeCodeSnippets ?? true;\n target.codeContextLines = options?.codeContextLines ?? 3;\n target.includeNetworkStats = options?.includeNetworkStats ?? true;\n target.navigationTypes = options?.navigationTypes ?? null;\n target.redactPatterns = [\n ...DEFAULT_REDACTION_PATTERNS,\n ...(options?.redactPatterns ?? []),\n ];\n target.testRunId = options?.testRunId ?? null;\n target.metadata = options?.metadata ?? null;\n const outputPath = target.outputPath as string;\n target.openReport = options?.openReport ?? true;\n target.htmlReportPath = options?.htmlReportPath ?? outputPath.replace(/\\.json$/, '.html');\n target.includeArtifacts = options?.includeArtifacts ?? true;\n target.trackApiCalls = options?.trackApiCalls ?? true;\n target.quiet = options?.quiet ?? false;\n\n return Object.freeze(target) as unknown as ResolvedConfig;\n}\n","/**\n * JSON schema version for the analytics timeline output.\n */\nexport const SCHEMA_VERSION = '1.3.0';\n","/**\n * Source file reading and code snippet extraction for failure diagnostics.\n * Never throws — returns null on any error.\n */\n\nimport { readFileSync } from 'node:fs';\n\nexport function extractCodeSnippet(\n filePath: string,\n line: number,\n contextLines: number,\n): string | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n\n if (line < 1 || line > lines.length) return null;\n\n const startLine = Math.max(1, line - contextLines);\n const endLine = Math.min(lines.length, line + contextLines);\n\n const snippetLines: string[] = [];\n for (let i = startLine; i <= endLine; i++) {\n const marker = i === line ? '>' : ' ';\n const lineNum = String(i).padStart(String(endLine).length, ' ');\n snippetLines.push(`${marker} ${lineNum} | ${lines[i - 1]}`);\n }\n\n return snippetLines.join('\\n');\n } catch {\n return null;\n }\n}\n","/**\n * Pattern-based redaction engine.\n * Replaces sensitive data with [REDACTED] before writing reports.\n */\n\nexport const DEFAULT_REDACTION_PATTERNS: (string | RegExp)[] = [\n /AKIA[A-Z0-9]{16}/g,\n /Bearer\\s+[A-Za-z0-9\\-._~+/]+=*/g,\n /-----BEGIN\\s+(RSA\\s+)?PRIVATE\\sKEY-----[\\s\\S]*?-----END/g,\n /\\/\\/[^:]+:[^@]+@/g,\n];\n\nexport function createRedactor(\n patterns: (string | RegExp)[],\n): (text: string) => string {\n return (text: string): string => {\n let result = text;\n for (const pattern of patterns) {\n if (typeof pattern === 'string') {\n // Escape special regex characters and create a global regex\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n result = result.replace(new RegExp(escaped, 'g'), '[REDACTED]');\n } else {\n // Clone the regex to ensure global flag and reset lastIndex\n const flags = pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g';\n const cloned = new RegExp(pattern.source, flags);\n result = result.replace(cloned, '[REDACTED]');\n }\n }\n return result;\n };\n}\n","/**\n * CI provider auto-detection via environment variables.\n * Strategy pattern: check providers in priority order, first match wins.\n */\n\nimport type { CIMetadata, CIProvider } from '@testrelic/core';\n\ntype DetectFn = (env: Record<string, string | undefined>) => CIMetadata | null;\n\nfunction detectGitHubActions(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.GITHUB_ACTIONS !== 'true') return null;\n return {\n provider: 'github-actions',\n buildId: env.GITHUB_RUN_ID ?? null,\n commitSha: env.GITHUB_SHA ?? null,\n branch: env.GITHUB_REF_NAME ?? null,\n };\n}\n\nfunction detectGitLabCI(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.GITLAB_CI !== 'true') return null;\n return {\n provider: 'gitlab-ci',\n buildId: env.CI_PIPELINE_ID ?? null,\n commitSha: env.CI_COMMIT_SHA ?? null,\n branch: env.CI_COMMIT_BRANCH ?? env.CI_COMMIT_REF_NAME ?? null,\n };\n}\n\nfunction detectJenkins(env: Record<string, string | undefined>): CIMetadata | null {\n if (!env.JENKINS_URL) return null;\n let branch = env.GIT_BRANCH ?? null;\n if (branch?.startsWith('origin/')) {\n branch = branch.slice('origin/'.length);\n }\n return {\n provider: 'jenkins',\n buildId: env.BUILD_ID ?? null,\n commitSha: env.GIT_COMMIT ?? null,\n branch,\n };\n}\n\nfunction detectCircleCI(env: Record<string, string | undefined>): CIMetadata | null {\n if (env.CIRCLECI !== 'true') return null;\n return {\n provider: 'circleci',\n buildId: env.CIRCLE_BUILD_NUM ?? null,\n commitSha: env.CIRCLE_SHA1 ?? null,\n branch: env.CIRCLE_BRANCH ?? null,\n };\n}\n\nconst detectors: DetectFn[] = [\n detectGitHubActions,\n detectGitLabCI,\n detectJenkins,\n detectCircleCI,\n];\n\nexport function detectCI(env?: Record<string, string | undefined>): CIMetadata | null {\n const envVars = env ?? process.env;\n for (const detect of detectors) {\n const result = detect(envVars);\n if (result) return result;\n }\n return null;\n}\n","/**\n * Client-side CSS for TestRelic Report.\n * Covers layout, theming, filter bar, test grid, drawer, network DevTools,\n * and all component styles.\n */\nexport const CSS = `\n/* Theme Variables */\n:root,[data-theme=\"dark\"]{\n --bg:#0f1117;--bg-1:#161b22;--bg-2:#1c2128;--bg-3:#21262d;--bg-code:#13111c;\n --fg:#e6edf3;--fg-1:#8b949e;--fg-2:#484f58;--fg-code:#d4d4d4;--fg-err:#f8d7da;\n --bd:rgba(255,255,255,0.06);--bd-s:rgba(255,255,255,0.04);--bd-m:rgba(255,255,255,0.08);--bd-l:rgba(255,255,255,0.1);--bd-xs:rgba(255,255,255,0.03);\n --hvr:rgba(255,255,255,0.025);--hvr-s:rgba(255,255,255,0.02);\n --overlay-bg:rgba(0,0,0,.55);--shadow-c:rgba(0,0,0,.35);\n --lb-bg:rgba(0,0,0,.92);--lb-shadow:rgba(0,0,0,.6);--lb-btn:rgba(255,255,255,.1);--lb-btn-h:rgba(255,255,255,.2);\n --scroll-thumb:#21262d;\n}\n[data-theme=\"light\"]{\n --bg:#ffffff;--bg-1:#f6f8fa;--bg-2:#eaeef2;--bg-3:#d0d7de;--bg-code:#f6f8fa;\n --fg:#1f2328;--fg-1:#656d76;--fg-2:#8b949e;--fg-code:#24292e;--fg-err:#82071e;\n --bd:rgba(0,0,0,0.08);--bd-s:rgba(0,0,0,0.04);--bd-m:rgba(0,0,0,0.12);--bd-l:rgba(0,0,0,0.15);--bd-xs:rgba(0,0,0,0.03);\n --hvr:rgba(0,0,0,0.04);--hvr-s:rgba(0,0,0,0.03);\n --overlay-bg:rgba(0,0,0,.3);--shadow-c:rgba(0,0,0,.1);\n --lb-bg:rgba(0,0,0,.85);--lb-shadow:rgba(0,0,0,.3);--lb-btn:rgba(0,0,0,.12);--lb-btn-h:rgba(0,0,0,.2);\n --scroll-thumb:#d0d7de;\n}\n\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nbody{\n font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;\n background:var(--bg);color:var(--fg);line-height:1.5;\n -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;\n}\n\n/* ── Centered Container ── */\n.wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n/* ── Header ── */\n.top-bar{display:flex;align-items:center;gap:14px;margin-bottom:20px;flex-wrap:wrap}\n.brand{display:flex;align-items:center;gap:10px}\n.brand svg{flex-shrink:0}\n.brand-text{display:flex;flex-direction:column}\n.brand-title{font-size:17px;font-weight:700;color:var(--fg);line-height:1.2}\n.brand-sub{font-size:10px;font-weight:600;color:#03b79c;letter-spacing:.06em;text-transform:uppercase;margin-top:1px}\n.run-meta{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:11px;color:var(--fg-1);margin-left:auto}\n.run-meta-item{display:flex;align-items:center;gap:4px}\n.run-meta-label{font-weight:600}\n.run-id-tag{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;background:var(--bg-2);padding:1px 6px;border-radius:4px;color:var(--fg-1)}\n.ci-tag{display:inline-flex;align-items:center;gap:4px;background:rgba(59,130,246,0.12);color:#60a5fa;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600}\n.merged-tag{background:rgba(245,158,11,0.12);color:#fbbf24;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600}\n\n/* ── Theme Toggle ── */\n.theme-toggle{display:inline-flex;border:1px solid var(--bd-m);border-radius:8px;overflow:hidden;background:var(--bg);flex-shrink:0}\n.theme-btn{padding:6px 10px;border:none;background:transparent;color:var(--fg-2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;font-family:inherit}\n.theme-btn:not(:last-child){border-right:1px solid var(--bd-m)}\n.theme-btn.active{background:var(--bg-3);color:var(--fg)}\n.theme-btn:hover:not(.active){color:var(--fg-1);background:var(--hvr)}\n.theme-btn svg{width:14px;height:14px}\n\n/* ── CTA Button ── */\n.cta-btn{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:500;color:#e6edf3;background:linear-gradient(135deg,rgba(3,183,156,0.15),rgba(14,165,233,0.15));border:1px solid rgba(3,183,156,0.3);padding:6px 14px;border-radius:8px;text-decoration:none;white-space:nowrap;flex-shrink:0;transition:all .2s ease;letter-spacing:.01em}\n.cta-btn:hover{background:linear-gradient(135deg,rgba(3,183,156,0.25),rgba(14,165,233,0.25));border-color:rgba(3,183,156,0.5);box-shadow:0 0 16px rgba(3,183,156,0.15);color:#fff}\n.cta-btn strong{font-weight:700;color:#2dd4a8}\n.cta-btn:hover strong{color:#5eead4}\n.cta-icon{width:13px;height:13px;color:#2dd4a8;flex-shrink:0}\n.cta-sep{color:var(--fg-2);margin:0 1px}\n.cta-arrow{width:11px;height:11px;color:#2dd4a8;flex-shrink:0;transition:transform .2s}\n.cta-btn:hover .cta-arrow{transform:translateX(2px)}\n[data-theme=\"light\"] .cta-btn{color:#1f2328;background:linear-gradient(135deg,rgba(3,183,156,0.08),rgba(14,165,233,0.08));border-color:rgba(3,183,156,0.25)}\n[data-theme=\"light\"] .cta-btn:hover{background:linear-gradient(135deg,rgba(3,183,156,0.15),rgba(14,165,233,0.15));border-color:rgba(3,183,156,0.4);color:#0f172a}\n[data-theme=\"light\"] .cta-btn strong{color:#059669}\n[data-theme=\"light\"] .cta-icon,[data-theme=\"light\"] .cta-arrow{color:#059669}\n\n/* ── Summary Strip ── */\n.summary-strip{display:flex;gap:8px;margin-bottom:20px}\n.summary-chip{flex:1;text-align:center;padding:14px 4px;border-radius:10px;cursor:default;border:1px solid var(--bd-s)}\n.summary-chip .s-count{font-size:22px;font-weight:800;line-height:1}\n.summary-chip .s-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;margin-top:4px;color:var(--fg-2)}\n.s-total{background:rgba(99,102,241,0.06)}.s-total .s-count{color:#818cf8}\n.s-passed{background:rgba(34,197,94,0.06)}.s-passed .s-count{color:#22c55e}\n.s-failed{background:rgba(239,68,68,0.06)}.s-failed .s-count{color:#ef4444}\n.s-flaky{background:rgba(245,158,11,0.06)}.s-flaky .s-count{color:#f59e0b}\n.s-skipped{background:rgba(107,114,128,0.06)}.s-skipped .s-count{color:#6b7280}\n.s-timedout{background:rgba(249,115,22,0.06)}.s-timedout .s-count{color:#f97316}\n\n/* ── Filter Bar ── */\n.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:24px;flex-wrap:wrap}\n.filter-chips{display:flex;gap:5px;flex-wrap:wrap}\n.filter-chip{\n font-size:12px;font-weight:500;padding:5px 14px;border-radius:9999px;\n border:1px solid var(--bd-m);background:transparent;color:var(--fg-1);\n cursor:pointer;transition:all .15s;font-family:inherit;white-space:nowrap;\n}\n.filter-chip:hover{border-color:#03b79c;color:var(--fg)}\n.filter-chip.active{background:rgba(3,183,156,0.12);border-color:#03b79c;color:#2dd4a8}\n.chip-count{font-weight:700;margin-left:3px}\n.filter-chip--zero{opacity:.35;pointer-events:none}\n.filter-chip--dimmed{opacity:.45}\n.filter-clear{font-size:11px;font-weight:600;color:#03b79c;background:none;border:none;cursor:pointer;padding:4px 8px;border-radius:6px;font-family:inherit;transition:background .15s}\n.filter-clear:hover{background:rgba(3,183,156,0.1)}\n.filter-indicator{font-size:11px;color:var(--fg-2)}\n\n/* ── Filter Icon Button ── */\n.filter-icon-btn{position:relative;display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;border:1px solid var(--bd-m);background:transparent;color:var(--fg-2);cursor:pointer;transition:all .15s;flex-shrink:0;margin-left:auto}\n.filter-icon-btn:hover{background:var(--hvr);color:var(--fg);border-color:var(--bd-l)}\n.filter-icon-badge{position:absolute;top:5px;right:5px;width:7px;height:7px;border-radius:50%;background:#03b79c}\n\n/* ── Filter Drawer ── */\n.filter-drawer-backdrop{position:fixed;inset:0;background:var(--overlay-bg);z-index:100;opacity:0;pointer-events:none;transition:opacity .25s ease}\n.filter-drawer-backdrop.open{opacity:1;pointer-events:auto}\n.filter-drawer{position:fixed;top:0;right:0;bottom:0;width:340px;max-width:90vw;z-index:110;background:var(--bg-1);border-left:1px solid var(--bd);transform:translateX(100%);transition:transform .3s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;overflow:hidden;box-shadow:-4px 0 24px var(--shadow-c)}\n.filter-drawer.open{transform:translateX(0)}\n.filter-drawer-bar{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--bd);background:linear-gradient(180deg,rgba(3,183,156,0.04) 0%,transparent 100%);flex-shrink:0}\n.filter-drawer-title{font-size:12px;font-weight:600;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em}\n.filter-drawer-close{width:32px;height:32px;border-radius:8px;border:1px solid var(--bd-m);background:transparent;color:var(--fg-1);font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}\n.filter-drawer-close:hover{background:var(--bd);color:var(--fg)}\n.filter-drawer-body{flex:1;overflow-y:auto;padding:20px;scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}\n.filter-drawer-body::-webkit-scrollbar{width:5px}\n.filter-drawer-body::-webkit-scrollbar-thumb{background:var(--bg-3);border-radius:3px}\n.filter-drawer-section{margin-bottom:20px}\n.filter-drawer-section-label{display:block;font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n.filter-drawer-actions{display:flex;align-items:center;gap:12px;padding-top:16px;border-top:1px solid var(--bd)}\n.search-box{position:relative;width:280px;flex-shrink:1;min-width:180px;margin-left:0}\n.search-input{\n width:100%;padding:6px 10px 6px 32px;\n border:1px solid var(--bd-m);border-radius:8px;\n background:var(--bg-1);color:var(--fg);font-size:12px;font-family:inherit;outline:none;transition:border-color .15s;\n}\n.search-input::placeholder{color:var(--fg-2)}\n.search-input:focus{border-color:#03b79c}\n.search-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--fg-2);pointer-events:none}\n\n/* ── File Groups & Test Rows ── */\n.file-group{margin-bottom:16px}\n.file-group-header{\n font-size:11px;font-weight:600;\n font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;\n color:var(--fg-2);padding:0 4px 6px;\n}\n.file-group-card{\n border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-1);\n}\n.test-row{\n display:flex;align-items:center;gap:12px;padding:12px 18px;cursor:pointer;\n transition:background .1s;border-bottom:1px solid var(--bd-s);\n}\n.test-row:last-child{border-bottom:none}\n.test-row:hover{background:var(--hvr)}\n.test-row.active{background:rgba(3,183,156,0.07)}\n\n.status-indicator{\n width:10px;height:10px;border-radius:50%;flex-shrink:0;\n}\n.si-passed{background:#22c55e}.si-failed{background:#ef4444}\n.si-flaky{background:#f59e0b}.si-skipped{background:#6b7280}.si-timedout{background:#f97316}\n\n.test-row-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}\n.test-row-title{font-size:14px;font-weight:500;color:var(--fg);line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.test-row-badges{display:flex;gap:5px;align-items:center;flex-wrap:wrap}\n.trb{font-size:10px;font-weight:600;padding:1px 7px;border-radius:4px}\n.trb-type{background:rgba(139,92,246,0.12);color:#a78bfa}\n.trb-tag{background:rgba(59,130,246,0.12);color:#60a5fa}\n.trb-retry{background:rgba(245,158,11,0.12);color:#fbbf24}\n.trb-flaky{background:rgba(245,158,11,0.12);color:#fbbf24}\n.test-row-dur{font-size:13px;font-weight:700;color:var(--fg-1);flex-shrink:0;white-space:nowrap}\n.test-row-arrow{color:var(--fg-2);flex-shrink:0;transition:color .15s}\n.test-row:hover .test-row-arrow{color:var(--fg-1)}\n\n.no-tests{text-align:center;padding:48px 20px;color:var(--fg-2);font-size:14px}\n\n/* ── Drawer Overlay ── */\n.drawer-backdrop{\n position:fixed;inset:0;background:var(--overlay-bg);z-index:100;\n opacity:0;pointer-events:none;transition:opacity .25s ease;\n}\n.drawer-backdrop.open{opacity:1;pointer-events:auto}\n\n/* ── Drawer Panel ── */\n.drawer{\n position:fixed;top:0;right:0;bottom:0;width:50vw;max-width:50vw;z-index:110;\n background:var(--bg-1);border-left:1px solid var(--bd);\n transform:translateX(100%);transition:transform .3s cubic-bezier(.4,0,.2,1);\n display:flex;flex-direction:column;overflow:hidden;\n box-shadow:-8px 0 40px var(--shadow-c);\n}\n.drawer.open{transform:translateX(0)}\n.drawer-bar{\n display:flex;align-items:center;justify-content:space-between;\n padding:14px 20px;border-bottom:1px solid var(--bd);\n background:linear-gradient(180deg,rgba(3,183,156,0.04) 0%,transparent 100%);flex-shrink:0;\n}\n.drawer-bar-title{font-size:12px;font-weight:600;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em}\n.drawer-close{\n width:32px;height:32px;border-radius:8px;border:1px solid var(--bd-m);\n background:transparent;color:var(--fg-1);font-size:18px;cursor:pointer;\n display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s;\n}\n.drawer-close:hover{background:var(--bd);color:var(--fg)}\n.drawer-body{flex:1;overflow-y:auto;padding:24px 28px;scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}\n.drawer-body::-webkit-scrollbar{width:5px}\n.drawer-body::-webkit-scrollbar-track{background:transparent}\n.drawer-body::-webkit-scrollbar-thumb{background:var(--bg-3);border-radius:3px}\n\n/* ── Drawer Sections ── */\n.drawer-sections{display:flex;flex-direction:column;gap:24px}\n.drawer-section{min-width:0}\n\n/* ── Segmented Control ── */\n.seg-ctrl{display:inline-flex;border:1px solid var(--bd-l);border-radius:8px;overflow:hidden;background:var(--bg);margin-bottom:12px}\n.seg-btn{padding:7px 16px;font-size:11px;font-weight:600;color:var(--fg-2);background:transparent;border:none;cursor:pointer;font-family:inherit;transition:all .15s;white-space:nowrap;display:flex;align-items:center;gap:5px}\n.seg-btn:not(:last-child){border-right:1px solid var(--bd-l)}\n.seg-btn.active{background:rgba(3,183,156,0.12);color:#2dd4a8}\n.seg-btn:hover:not(.active){color:var(--fg-1);background:var(--bd-xs)}\n.seg-pane{display:none}\n.seg-pane.active{display:block;animation:trFadeIn .15s ease-out}\n\n/* ── Artifact Column ── */\n.artifact-col-img{max-width:100%;border-radius:8px;cursor:pointer;border:1px solid var(--bd);transition:border-color .15s,box-shadow .15s;object-fit:contain;display:block}\n.artifact-col-img:hover{border-color:#03b79c;box-shadow:0 0 0 3px rgba(3,183,156,0.15)}\n.artifact-col-caption{font-size:10px;color:var(--fg-2);margin-top:6px;display:flex;align-items:center;gap:4px}\n.artifact-col-caption svg{opacity:.5}\n.artifact-col-video{border-radius:8px;overflow:hidden;border:1px solid var(--bd);background:#000}\n.artifact-col-video video{width:100%;display:block}\n.artifact-empty{padding:24px 14px;text-align:center;font-size:12px;color:var(--fg-2);font-style:italic;border:1px dashed var(--bd-m);border-radius:10px}\n\n/* ── Network Overview ── */\n.net-overview{border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.net-section-hd{padding:8px 12px;font-size:10px;font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.06em;border-top:1px solid var(--bd-s);display:flex;align-items:center;gap:6px}\n.net-section-hd svg{opacity:.5}\n\n/* ── Resource Bar Chart ── */\n.net-bar-wrap{padding:10px 12px 4px}\n.net-bar{display:flex;height:20px;border-radius:5px;overflow:hidden;background:var(--bg-3)}\n.net-bar-seg{min-width:2px;position:relative;transition:opacity .15s}\n.net-bar-seg:hover{opacity:.8}\n.net-bar--mini{height:6px;border-radius:3px;margin-bottom:4px}\n.net-bar--mini .net-bar-seg{min-width:1px}\n.net-bar-legend{display:flex;flex-wrap:wrap;gap:4px 12px;padding:4px 12px 8px;font-size:10px}\n.net-legend-item{display:flex;align-items:center;gap:4px;color:var(--fg-1)}\n.net-legend-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}\n.net-legend-count{font-weight:700;color:var(--fg)}\n\n/* ── Network Step Cards ── */\n.net-steps-list{border-top:1px solid var(--bd-s)}\n.net-step-card{padding:10px 12px;border-bottom:1px solid var(--bd-xs)}\n.net-step-card:last-child{border-bottom:none}\n.net-step-card-head{display:flex;align-items:center;gap:8px;margin-bottom:6px}\n.net-step-num{width:20px;height:20px;border-radius:50%;background:var(--bg-3);color:var(--fg-1);font-size:9px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0}\n.net-step-url{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.net-step-stats{flex-shrink:0;color:var(--fg-2);font-size:10px;white-space:nowrap}\n.net-step-timing{display:flex;gap:10px;font-size:9px;color:var(--fg-2);flex-wrap:wrap}\n.net-step-timing span{display:flex;align-items:center;gap:3px}\n.timing-label{font-weight:700;text-transform:uppercase;letter-spacing:.04em;font-size:8px}\n.timing-val{font-weight:600;color:var(--fg-1)}\n\n/* ── Failed URL badges ── */\n.fail-url-badge{display:inline-flex;align-items:center;font-size:10px;font-weight:700;padding:1px 5px;border-radius:3px;margin-right:5px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.fail-url-badge--4xx{background:rgba(245,158,11,0.12);color:#f59e0b}\n.fail-url-badge--5xx{background:rgba(239,68,68,0.12);color:#ef4444}\n.fail-url-badge--other{background:rgba(107,114,128,0.1);color:#6b7280}\n\n/* ── Drawer Content — Test Detail ── */\n.test-detail{animation:trFadeIn .2s ease-out}\n@keyframes trFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}\n\n.test-detail-header{margin-bottom:22px;padding-bottom:18px;border-bottom:1px solid var(--bd)}\n.test-detail-top{display:flex;align-items:flex-start;gap:12px;margin-bottom:10px}\n.detail-status-badge{\n font-size:11px;font-weight:700;padding:4px 14px;border-radius:9999px;\n text-transform:uppercase;letter-spacing:.04em;flex-shrink:0;margin-top:2px;\n}\n.dbg-passed{background:rgba(34,197,94,0.1);color:#22c55e;border:1px solid rgba(34,197,94,0.2)}\n.dbg-failed{background:rgba(239,68,68,0.1);color:#ef4444;border:1px solid rgba(239,68,68,0.2)}\n.dbg-flaky{background:rgba(245,158,11,0.1);color:#f59e0b;border:1px solid rgba(245,158,11,0.2)}\n.dbg-skipped{background:rgba(107,114,128,0.1);color:#6b7280;border:1px solid rgba(107,114,128,0.2)}\n.dbg-timedout{background:rgba(249,115,22,0.1);color:#f97316;border:1px solid rgba(249,115,22,0.2)}\n\n.detail-title-section{flex:1;min-width:0}\n.detail-title{font-size:16px;font-weight:700;color:var(--fg);line-height:1.35;margin-bottom:6px}\n.detail-meta{display:flex;flex-wrap:wrap;gap:5px;align-items:center}\n.dm-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:4px}\n.dm-type{background:rgba(139,92,246,0.12);color:#a78bfa}\n.dm-tag{background:rgba(59,130,246,0.12);color:#60a5fa}\n.dm-retry{background:rgba(245,158,11,0.12);color:#fbbf24}\n.dm-flaky{background:rgba(245,158,11,0.12);color:#fbbf24}\n.detail-sub-meta{display:flex;gap:14px;font-size:11px;color:var(--fg-1);flex-wrap:wrap;margin-top:6px}\n.meta-k{font-weight:600;color:var(--fg-2);text-transform:uppercase;letter-spacing:.04em;font-size:10px;margin-right:3px}\n.detail-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;color:var(--fg-1);background:var(--bg-2);padding:1px 6px;border-radius:4px}\n.detail-file{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--fg-1)}\n.detail-suite{font-size:11px;color:var(--fg-1)}\n.detail-duration{font-size:24px;font-weight:800;color:var(--fg);flex-shrink:0;white-space:nowrap}\n\n/* ── Failure Panel ── */\n.failure-panel{margin-bottom:20px;border:1px solid rgba(239,68,68,0.2);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.failure-panel-header{display:flex;align-items:center;gap:8px;padding:10px 14px;background:rgba(239,68,68,0.08);font-size:12px;font-weight:600;color:#ef4444}\n.failure-message{padding:12px 14px;background:var(--bg-code);color:var(--fg-err);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;line-height:1.6;overflow-x:auto}\n.code-snippet{background:var(--bg-code);font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow-x:auto;border-top:1px solid var(--bd-s)}\n.code-line{display:flex;padding:0 14px;line-height:1.8}\n.code-line.highlight{background:rgba(239,68,68,0.12)}\n.line-num{color:var(--fg-2);min-width:36px;text-align:right;padding-right:14px;user-select:none;flex-shrink:0}\n.line-code{color:var(--fg-code);white-space:pre;overflow-x:auto}\n.line-marker{padding:4px 14px;font-size:10px;color:var(--fg-2);background:var(--bg-2);border-top:1px solid var(--bd-s)}\n.stack-toggle-btn{font-size:11px;color:var(--fg-1);cursor:pointer;padding:8px 14px;border:none;background:var(--bg-3);width:100%;text-align:left;font-family:inherit;border-top:1px solid var(--bd-s);transition:background .15s}\n.stack-toggle-btn:hover{background:var(--bg-2);color:var(--fg)}\n.stack-trace{display:none;padding:12px 14px;background:var(--bg-2);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word;color:var(--fg-2);border-top:1px solid var(--bd-s);max-height:260px;overflow-y:auto}\n.stack-trace.show{display:block}\n\n/* ── Section Label ── */\n.section-label{font-size:11px;font-weight:700;color:var(--fg-1);text-transform:uppercase;letter-spacing:.06em;margin-bottom:14px;display:flex;align-items:center;gap:8px}\n.section-label::before{content:'';width:3px;height:14px;background:#03b79c;border-radius:2px}\n\n/* ── Artifacts Panel ── */\n.artifacts-panel{margin-bottom:20px;border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-2)}\n.artifacts-toolbar{display:flex;align-items:center;gap:0;padding:0;background:var(--bg-3);border-bottom:1px solid var(--bd-s)}\n.artifacts-label{display:flex;align-items:center;gap:6px;padding:9px 14px;font-size:11px;font-weight:600;color:var(--fg-1);text-transform:uppercase;letter-spacing:.04em;border-right:1px solid var(--bd-s);flex-shrink:0}\n.artifact-tab{padding:9px 16px;font-size:12px;font-weight:500;color:var(--fg-2);cursor:pointer;border:none;background:transparent;font-family:inherit;transition:color .15s,background .15s;position:relative;display:flex;align-items:center;gap:6px}\n.artifact-tab:hover{color:var(--fg-1);background:var(--hvr-s)}\n.artifact-tab.active{color:var(--fg);background:var(--bd-xs)}\n.artifact-tab.active::after{content:'';position:absolute;bottom:0;left:8px;right:8px;height:2px;background:#03b79c;border-radius:1px}\n.artifact-tab svg{flex-shrink:0;opacity:.6}\n.artifact-tab.active svg{opacity:1}\n.artifact-pane{display:none;padding:14px}\n.artifact-pane.active{display:block;animation:trFadeIn .15s ease-out}\n.screenshot-pane{display:flex;flex-direction:column;align-items:center;gap:8px}\n.artifact-thumb{max-height:260px;max-width:100%;width:auto;border-radius:8px;cursor:pointer;border:1px solid var(--bd);transition:border-color .15s,box-shadow .15s,transform .15s;object-fit:contain;display:block}\n.artifact-thumb:hover{border-color:#03b79c;box-shadow:0 0 0 3px rgba(3,183,156,0.15);transform:scale(1.01)}\n.artifact-caption{font-size:11px;color:var(--fg-2);display:flex;align-items:center;gap:4px}\n.artifact-caption svg{opacity:.5}\n.video-pane{display:flex;flex-direction:column;gap:0}\n.video-player{border-radius:8px;overflow:hidden;border:1px solid var(--bd);background:#000}\n.video-player video{width:100%;max-height:340px;display:block}\n\n/* ── Navigation Timeline ── */\n.nav-timeline{position:relative;margin-bottom:20px}\n.nav-step{display:flex;gap:12px;position:relative}\n.step-connector{display:flex;flex-direction:column;align-items:center;flex-shrink:0;width:28px}\n.step-node{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0;z-index:1;border:2px solid;transition:transform .15s}\n.step-node:hover{transform:scale(1.1)}\n.node-ok{background:rgba(3,183,156,0.1);border-color:rgba(3,183,156,0.35);color:#2dd4a8}\n.node-warn{background:rgba(245,158,11,0.1);border-color:rgba(245,158,11,0.35);color:#f59e0b}\n.step-line{width:2px;flex:1;min-height:10px;background:linear-gradient(to bottom,rgba(3,183,156,0.35),rgba(3,183,156,0.08))}\n.nav-step:last-child .step-line{display:none}\n.step-content{flex:1;min-width:0;padding-bottom:14px}\n.nav-step:last-child .step-content{padding-bottom:0}\n.step-header{display:flex;align-items:center;gap:7px;cursor:pointer;padding:4px 8px;border-radius:6px;margin:-4px -8px;transition:background .1s;flex-wrap:wrap}\n.step-header:hover{background:var(--hvr-s)}\n.step-url{font-size:12px;font-weight:500;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;min-width:0}\n.nav-badge{font-size:9px;font-weight:600;padding:2px 7px;border-radius:9999px;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0;white-space:nowrap}\n.nb-goto{background:rgba(59,130,246,0.12);color:#60a5fa}\n.nb-navigation{background:rgba(99,102,241,0.12);color:#818cf8}\n.nb-page_load{background:rgba(129,140,248,0.12);color:#a5b4fc}\n.nb-back{background:rgba(34,211,238,0.12);color:#22d3ee}\n.nb-forward{background:rgba(34,211,238,0.12);color:#67e8f9}\n.nb-spa_route{background:rgba(139,92,246,0.12);color:#a78bfa}\n.nb-spa_replace{background:rgba(167,139,250,0.12);color:#c4b5fd}\n.nb-hash_change{background:rgba(236,72,153,0.12);color:#f472b6}\n.nb-popstate{background:rgba(244,114,182,0.12);color:#f9a8d4}\n.nb-link_click{background:rgba(251,113,133,0.12);color:#fb7185}\n.nb-form_submit{background:rgba(244,63,94,0.12);color:#fb7185}\n.nb-redirect{background:rgba(249,115,22,0.12);color:#fb923c}\n.nb-refresh{background:rgba(56,189,248,0.12);color:#38bdf8}\n.nb-dummy{background:rgba(107,114,128,0.06);color:var(--fg-2)}\n.nb-fallback{background:rgba(107,114,128,0.06);color:var(--fg-2)}\n.nb-manual_record{background:rgba(234,179,8,0.12);color:#facc15}\n.step-meta{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--fg-2);margin-left:auto;flex-shrink:0}\n\n/* Step expandable detail */\n.step-detail{display:none;margin-top:8px;border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--bg-2)}\n.step-detail.show{display:block;animation:trSlideDown .2s ease-out}\n@keyframes trSlideDown{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}\n.net-header{display:flex;align-items:center;gap:6px;padding:7px 12px;font-size:11px;font-weight:600;color:var(--fg-1);background:var(--bg-3);border-bottom:1px solid var(--bd-s)}\n.net-header.has-fails{color:#ef4444;background:rgba(239,68,68,0.08)}\n.net-stats-row{display:flex;gap:8px;padding:8px 12px;flex-wrap:wrap}\n.net-stat{display:flex;flex-direction:column;align-items:center;gap:1px;padding:5px 10px;border-radius:6px;background:var(--bg-3);min-width:50px}\n.net-stat .nv{font-weight:700;font-size:14px;color:var(--fg);line-height:1}\n.net-stat .nl{color:var(--fg-2);font-size:8px;font-weight:600;text-transform:uppercase;letter-spacing:.06em}\n.net-stat.net-fail{background:rgba(239,68,68,0.08)}.net-stat.net-fail .nv{color:#ef4444}\n.resource-row{display:flex;gap:4px;padding:7px 12px;flex-wrap:wrap;border-top:1px solid var(--bd-xs)}\n.res-type{display:flex;align-items:center;gap:3px;font-size:10px;padding:2px 7px;border-radius:4px;background:var(--bg-3);color:var(--fg-1);font-weight:500}\n.res-type .rc{font-weight:700}\n.res-type--zero{opacity:.25}\n.fail-urls{padding:7px 12px;border-top:1px solid rgba(239,68,68,0.12);background:rgba(239,68,68,0.06);max-height:160px;overflow-y:auto}\n.fail-url{font-size:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#ef4444;padding:2px 0;word-break:break-all}\n.fail-url-status{font-weight:700;margin-right:4px}\n\n/* ── Network DevTools Panel ── */\n.ndt{border:1px solid var(--bd);border-radius:10px;overflow:hidden;background:var(--bg-1)}\n.ndt-toolbar{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--bg-2);border-bottom:1px solid var(--bd);flex-wrap:wrap}\n.ndt-filter{display:flex;gap:2px;flex-wrap:wrap}\n.ndt-filter-btn{font-size:10px;padding:2px 8px;border-radius:4px;border:1px solid transparent;background:none;color:var(--fg-2);cursor:pointer;font-weight:500;transition:all .15s}\n.ndt-filter-btn:hover{background:var(--hvr);color:var(--fg-1)}\n.ndt-filter-btn.active{background:var(--bg-3);color:var(--fg);border-color:var(--bd-m);font-weight:600}\n.ndt-search{flex:1;min-width:100px;max-width:220px;font-size:10px;padding:3px 8px;border-radius:4px;border:1px solid var(--bd);background:var(--bg);color:var(--fg);outline:none;font-family:inherit}\n.ndt-search:focus{border-color:rgba(3,183,156,0.4)}\n.ndt-count{font-size:10px;color:var(--fg-2);margin-left:auto;white-space:nowrap}\n.ndt-list{max-height:600px;overflow-y:auto}\n.ndt-list::-webkit-scrollbar{width:5px}.ndt-list::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-row{display:grid;grid-template-columns:42px 50px 1fr 52px 56px 56px;align-items:center;gap:4px;padding:5px 10px;border-bottom:1px solid var(--bd-xs);cursor:pointer;font-size:11px;transition:background .1s}\n.ndt-row:hover{background:var(--hvr)}\n.ndt-row.ndt-row--open{background:var(--bg-2)}\n.ndt-row.ndt-row--fail{background:rgba(239,68,68,0.04)}\n.ndt-row.ndt-row--fail:hover{background:rgba(239,68,68,0.08)}\n.ndt-status{font-weight:700;font-size:10px;padding:1px 5px;border-radius:3px;text-align:center;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-status--2xx{background:rgba(34,197,94,0.12);color:#22c55e}\n.ndt-status--3xx{background:rgba(59,130,246,0.12);color:#60a5fa}\n.ndt-status--4xx{background:rgba(249,115,22,0.12);color:#fb923c}\n.ndt-status--5xx{background:rgba(239,68,68,0.12);color:#ef4444}\n.ndt-status--0{background:rgba(239,68,68,0.12);color:#ef4444}\n.ndt-method{font-weight:600;font-size:10px;color:var(--fg-1);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-transform:uppercase}\n.ndt-url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.ndt-type{font-size:9px;padding:1px 5px;border-radius:3px;background:var(--bg-3);color:var(--fg-2);text-align:center;font-weight:500}\n.ndt-size{font-size:10px;color:var(--fg-2);text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-time{font-size:10px;color:var(--fg-2);text-align:right;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-detail{display:none;border-bottom:1px solid var(--bd);background:var(--bg);animation:trSlideDown .15s ease-out}\n.ndt-detail.show{display:block}\n.ndt-detail-tabs{display:flex;gap:0;border-bottom:1px solid var(--bd);background:var(--bg-2)}\n.ndt-detail-tab{font-size:10px;padding:6px 14px;border:none;background:none;color:var(--fg-2);cursor:pointer;font-weight:500;border-bottom:2px solid transparent;transition:all .15s}\n.ndt-detail-tab:hover{color:var(--fg-1);background:var(--hvr)}\n.ndt-detail-tab.active{color:#03b79c;border-bottom-color:#03b79c;font-weight:600}\n.ndt-detail-pane{display:none;padding:10px 14px;max-height:360px;overflow-y:auto}\n.ndt-detail-pane::-webkit-scrollbar{width:5px}.ndt-detail-pane::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-detail-pane.active{display:block}\n.ndt-general{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:11px}\n.ndt-general-k{color:var(--fg-2);font-weight:600;white-space:nowrap}\n.ndt-general-v{color:var(--fg);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px}\n.ndt-general-v.ndt-err{color:#ef4444}\n.ndt-headers-tbl{width:100%;border-collapse:collapse;font-size:10px}\n.ndt-headers-tbl th{text-align:left;padding:4px 8px;background:var(--bg-2);color:var(--fg-2);font-weight:600;border-bottom:1px solid var(--bd);font-size:9px;text-transform:uppercase;letter-spacing:.04em}\n.ndt-headers-tbl td{padding:4px 8px;border-bottom:1px solid var(--bd-xs);vertical-align:top}\n.ndt-headers-tbl td:first-child{color:var(--fg-1);font-weight:600;white-space:nowrap;width:180px}\n.ndt-headers-tbl td:last-child{color:var(--fg);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n.ndt-body-wrap{position:relative}\n.ndt-body-pre{margin:0;padding:10px;background:var(--bg-code);border-radius:6px;border:1px solid var(--bd);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;color:var(--fg-code);white-space:pre-wrap;word-break:break-all;max-height:320px;overflow-y:auto;line-height:1.5;tab-size:2}\n.ndt-body-pre::-webkit-scrollbar{width:5px}.ndt-body-pre::-webkit-scrollbar-thumb{background:var(--scroll-thumb);border-radius:4px}\n.ndt-body-empty{padding:16px;text-align:center;font-size:11px;color:var(--fg-2);font-style:italic}\n.ndt-body-truncated{display:inline-block;margin-top:6px;font-size:9px;padding:2px 8px;border-radius:4px;background:rgba(249,115,22,0.1);color:#fb923c;font-weight:500}\n.ndt-hdr-section{margin-bottom:10px}\n.ndt-hdr-label{font-size:10px;font-weight:600;color:var(--fg-1);margin-bottom:4px;display:flex;align-items:center;gap:5px}\n.ndt-hdr-label svg{opacity:.5}\n\n/* ── Lightbox ── */\n.lightbox-overlay{position:fixed;inset:0;background:var(--lb-bg);z-index:1000;display:flex;align-items:center;justify-content:center;cursor:zoom-out;animation:trFadeIn .15s}\n.lightbox-overlay img{max-width:92vw;max-height:92vh;border-radius:8px;box-shadow:0 8px 40px var(--lb-shadow)}\n.lightbox-close{position:fixed;top:16px;right:16px;z-index:1001;width:40px;height:40px;border-radius:50%;border:none;background:var(--lb-btn);color:#fff;font-size:22px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;backdrop-filter:blur(8px)}\n.lightbox-close:hover{background:var(--lb-btn-h)}\n\n/* ── Responsive ── */\n@media(max-width:1100px){\n .drawer{width:60vw;max-width:60vw}\n}\n@media(max-width:800px){\n .drawer{width:85vw;max-width:85vw}\n}\n@media(max-width:640px){\n .wrap{padding:16px 12px 48px}\n .top-bar{flex-direction:column;align-items:flex-start;gap:8px}\n .run-meta{margin-left:0}\n .summary-strip{flex-wrap:wrap}\n .summary-chip{min-width:70px}\n .filter-bar{flex-direction:column;align-items:stretch}\n .search-box{width:100%;margin-left:0}\n .filter-icon-btn{margin-left:auto}\n .filter-drawer{width:100%;max-width:100%}\n .drawer{width:100%;max-width:100%}\n .cta-btn{font-size:10px;padding:5px 10px;order:10}\n .detail-title{font-size:14px}\n .detail-duration{font-size:20px}\n}\n\n/* ── Print ── */\n@media print{\n body{background:#fff;color:#000}\n .drawer-backdrop,.drawer,.filter-drawer-backdrop,.filter-drawer{display:none}\n .lightbox-overlay,.lightbox-close{display:none}\n .test-row-arrow{display:none}\n .summary-chip,.filter-chip,.status-indicator,.detail-status-badge,.dm-badge,.nav-badge,.trb,.step-node{\n print-color-adjust:exact;-webkit-print-color-adjust:exact;\n }\n}\n`;\n","/**\n * TestRelic Logo SVG\n *\n * Inline SVG logo used in the HTML report header and empty states.\n */\n\n/** Raw SVG string for favicon data URI (no presentational size overrides). */\nexport const LOGO_SVG_RAW = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 196 247\" fill=\"none\">\n<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)\"/>\n<defs><linearGradient id=\"paint0_linear_55_11\" x1=\"98\" y1=\"0.000244141\" x2=\"98\" y2=\"247\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#03B79C\"/><stop offset=\"0.504808\" stop-color=\"#4EDAA4\"/><stop offset=\"0.865285\" stop-color=\"#84F3AA\"/>\n</linearGradient></defs></svg>`;\n\nexport const LOGO_SVG = `<svg width=\"32\" height=\"40\" viewBox=\"0 0 196 247\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<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)\"/>\n<defs><linearGradient id=\"paint0_linear_55_11\" x1=\"98\" y1=\"0.000244141\" x2=\"98\" y2=\"247\" gradientUnits=\"userSpaceOnUse\">\n<stop stop-color=\"#03B79C\"/><stop offset=\"0.504808\" stop-color=\"#4EDAA4\"/><stop offset=\"0.865285\" stop-color=\"#84F3AA\"/>\n</linearGradient></defs></svg>`;\n","/**\n * Client-side JavaScript: Core Rendering.\n * Utilities, data transformation, summary, test grid, drawer, and artifacts.\n */\n\nexport const JS_RENDER = `\n /* ── Utilities ── */\n function esc(s){if(!s)return '';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"').replace(/'/g,''')}\n function stripAnsi(s){return s?s.replace(/\\\\u001b\\\\[[0-9;]*m/g,'').replace(/\\\\x1b\\\\[[0-9;]*m/g,''):''}\n function fmtDur(ms){if(ms<1000)return Math.round(ms)+'ms';if(ms<60000)return (ms/1000).toFixed(1)+'s';var m=Math.floor(ms/60000),s=Math.round((ms%60000)/1000);return s>0?m+'m '+s+'s':m+'m'}\n function fmtBytes(b){if(b===0)return '0 B';if(b<1024)return b+' B';if(b<1048576)return (b/1024).toFixed(1)+' KB';return (b/1048576).toFixed(1)+' MB'}\n function fmtDate(iso){try{return new Date(iso).toLocaleString()}catch(e){return iso}}\n function shortTitle(t){var p=t.split(' > ');return p.length>1?p[p.length-1]:t}\n\n /* ── Data Transformation ── */\n var testMap={};\n for(var i=0;i<data.timeline.length;i++){\n var entry=data.timeline[i];\n for(var j=0;j<entry.tests.length;j++){\n var t=entry.tests[j];\n if(!testMap[t.testId]){\n testMap[t.testId]={\n testId:t.testId,title:t.title,status:t.status,duration:t.duration,\n startedAt:t.startedAt,completedAt:t.completedAt,retryCount:t.retryCount,\n tags:t.tags,failure:t.failure,filePath:t.filePath,suiteName:t.suiteName,\n testType:t.testType,isFlaky:t.isFlaky,retryStatus:t.retryStatus,\n expectedStatus:t.expectedStatus,actualStatus:t.actualStatus,\n artifacts:t.artifacts,networkRequests:t.networkRequests||[],steps:[],apiCalls:[]\n };\n }\n if(entry.type==='api_call'){\n testMap[t.testId].apiCalls.push({\n callId:entry.callId,method:entry.method,url:entry.url,\n responseTime:entry.responseTime,timestamp:entry.timestamp,\n request:entry.request,response:entry.response,assertions:entry.assertions\n });\n }else{\n testMap[t.testId].steps.push({\n url:entry.url,navigationType:entry.navigationType,\n visitedAt:entry.visitedAt,duration:entry.duration,specFile:entry.specFile,\n domContentLoadedAt:entry.domContentLoadedAt,networkIdleAt:entry.networkIdleAt,\n networkStats:entry.networkStats\n });\n }\n }\n }\n var tests=[];\n for(var id in testMap){if(testMap.hasOwnProperty(id))tests.push(testMap[id])}\n tests.sort(function(a,b){\n if(a.filePath!==b.filePath)return a.filePath<b.filePath?-1:1;\n return a.startedAt<b.startedAt?-1:1;\n });\n\n /* ── State ── */\n var searchQuery='';\n\n /* ── DOM Refs ── */\n var runMetaEl=document.getElementById('run-meta');\n var summaryEl=document.getElementById('summary-strip');\n var filterEl=document.getElementById('filter-bar');\n var testGridEl=document.getElementById('test-grid');\n var drawerEl=document.getElementById('drawer');\n var drawerBodyEl=document.getElementById('drawer-body');\n var drawerBackdropEl=document.getElementById('drawer-backdrop');\n\n /* ── Render: Run Meta ── */\n function renderRunMeta(){\n var h='';\n h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Run</span><span class=\"run-id-tag\">'+esc(data.testRunId.substring(0,8))+'</span></div>';\n h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Duration</span>'+fmtDur(data.totalDuration)+'</div>';\n h+='<div class=\"run-meta-item\">'+fmtDate(data.startedAt)+'</div>';\n if(data.ci){\n h+='<div class=\"run-meta-item\"><span class=\"ci-tag\">'+esc(data.ci.provider)+'</span></div>';\n if(data.ci.branch)h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Branch</span>'+esc(data.ci.branch)+'</div>';\n if(data.ci.commitSha)h+='<div class=\"run-meta-item\"><span class=\"run-meta-label\">Commit</span><span class=\"run-id-tag\">'+esc(data.ci.commitSha.substring(0,8))+'</span></div>';\n }\n if(data.shardRunIds&&data.shardRunIds.length>0){\n h+='<div class=\"run-meta-item\"><span class=\"merged-tag\">Merged ('+data.shardRunIds.length+' shards)</span></div>';\n }\n runMetaEl.innerHTML=h;\n }\n\n /* ── Render: Summary ── */\n function renderSummary(){\n var s=data.summary;var h='';\n h+='<div class=\"summary-chip s-total\"><div class=\"s-count\">'+s.total+'</div><div class=\"s-label\">Total</div></div>';\n h+='<div class=\"summary-chip s-passed\"><div class=\"s-count\">'+s.passed+'</div><div class=\"s-label\">Passed</div></div>';\n h+='<div class=\"summary-chip s-failed\"><div class=\"s-count\">'+s.failed+'</div><div class=\"s-label\">Failed</div></div>';\n h+='<div class=\"summary-chip s-flaky\"><div class=\"s-count\">'+s.flaky+'</div><div class=\"s-label\">Flaky</div></div>';\n h+='<div class=\"summary-chip s-skipped\"><div class=\"s-count\">'+s.skipped+'</div><div class=\"s-label\">Skipped</div></div>';\n 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>';\n summaryEl.innerHTML=h;\n }\n\n /* ── Render: Test Grid ── */\n function renderTestGrid(){\n var filtered=getFilteredTests();\n if(filtered.length===0){testGridEl.innerHTML='<div class=\"no-tests\">No tests match your filters</div>';return;}\n var h='';var lastFile='';\n for(var i=0;i<filtered.length;i++){\n var t=filtered[i];\n if(t.filePath!==lastFile){\n if(lastFile)h+='</div></div>';\n h+='<div class=\"file-group\"><div class=\"file-group-header\">'+esc(t.filePath)+'</div><div class=\"file-group-card\">';\n lastFile=t.filePath;\n }\n h+='<div class=\"test-row\" data-testid=\"'+esc(t.testId)+'\">';\n h+='<div class=\"status-indicator si-'+t.status+'\"></div>';\n h+='<div class=\"test-row-info\">';\n h+='<div class=\"test-row-title\" title=\"'+esc(t.title)+'\">'+esc(shortTitle(t.title))+'</div>';\n var hasBadges=(t.testType&&t.testType!=='unknown')||t.isFlaky||(t.tags&&t.tags.length>0);\n if(hasBadges){\n h+='<div class=\"test-row-badges\">';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"trb trb-type\">'+esc(t.testType)+'</span>';\n if(t.isFlaky)h+='<span class=\"trb trb-flaky\">flaky</span>';\n if(t.retryCount>0)h+='<span class=\"trb trb-retry\">'+t.retryCount+' retries</span>';\n if(t.tags){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti])h+='<span class=\"trb trb-tag\">'+esc(t.tags[ti])+'</span>';}}\n h+='</div>';\n }\n h+='</div>';\n h+='<span class=\"test-row-dur\">'+fmtDur(t.duration)+'</span>';\n 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>';\n h+='</div>';\n }\n if(lastFile)h+='</div></div>';\n testGridEl.innerHTML=h;\n }\n\n /* ── Render helpers (code, artifacts) ── */\n\n function renderCodeSnippet(code){\n var lines=code.split('\\\\n');var out='<div class=\"code-snippet\">';\n for(var k=0;k<lines.length;k++){var raw=lines[k];var numMatch=raw.match(/^\\\\s*(>?\\\\s*)(\\\\d+)\\\\s*\\\\|/);var lineNum=numMatch?numMatch[2]:'';var isHighlight=raw.trimStart().startsWith('>');var codePart=raw.replace(/^\\\\s*>?\\\\s*\\\\d+\\\\s*\\\\|/,'');out+='<div class=\"code-line'+(isHighlight?' highlight':'')+'\"><span class=\"line-num\">'+esc(lineNum)+'</span><span class=\"line-code\">'+esc(codePart)+'</span></div>';}\n out+='</div>';return out;\n }\n\n function renderArtifactColumn(artifacts){\n if(!artifacts)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var hasS=!!artifacts.screenshot,hasV=!!artifacts.video;\n if(!hasS&&!hasV)return '<div class=\"artifact-empty\">No artifacts captured</div>';\n var h='';\n if(hasS&&hasV){\n h+='<div class=\"seg-ctrl\" data-seg-group=\"artifacts\">';\n 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>';\n 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>';\n h+='</div>';\n }\n 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>';}\n 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>';}\n return h;\n }\n\n /* ── Render: Drawer Content ── */\n function renderDrawer(testId){\n var t=testMap[testId];\n if(!t){drawerBodyEl.innerHTML='';return;}\n var h='<div class=\"test-detail\">';\n\n /* ── Top: Metadata ── */\n h+='<div class=\"test-detail-header\"><div class=\"test-detail-top\">';\n h+='<span class=\"detail-status-badge dbg-'+t.status+'\">'+t.status+'</span>';\n h+='<div class=\"detail-title-section\"><div class=\"detail-title\">'+esc(t.title)+'</div><div class=\"detail-meta\">';\n if(t.testType&&t.testType!=='unknown')h+='<span class=\"dm-badge dm-type\">'+esc(t.testType)+'</span>';\n if(t.isFlaky)h+='<span class=\"dm-badge dm-flaky\">Flaky</span>';\n if(t.retryCount>0)h+='<span class=\"dm-badge dm-retry\">'+t.retryCount+' retries</span>';\n if(t.retryStatus)h+='<span class=\"dm-badge dm-retry\">'+esc(t.retryStatus)+'</span>';\n if(t.tags&&t.tags.length>0){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti])h+='<span class=\"dm-badge dm-tag\">'+esc(t.tags[ti])+'</span>';}}\n if(t.expectedStatus&&t.actualStatus&&t.expectedStatus!==t.actualStatus)h+='<span class=\"dm-badge\" style=\"background:rgba(239,68,68,0.12);color:#ef4444\">expected: '+esc(t.expectedStatus)+' actual: '+esc(t.actualStatus)+'</span>';\n h+='</div>';\n if(t.testId||t.filePath||t.suiteName){h+='<div class=\"detail-sub-meta\">';if(t.testId)h+='<span><span class=\"meta-k\">ID</span><span class=\"detail-id\" title=\"'+esc(t.testId)+'\">'+esc(t.testId.substring(0,12))+'</span></span>';if(t.filePath)h+='<span><span class=\"meta-k\">File</span><span class=\"detail-file\">'+esc(t.filePath)+'</span></span>';if(t.suiteName)h+='<span><span class=\"meta-k\">Suite</span><span class=\"detail-suite\">'+esc(t.suiteName)+'</span></span>';h+='</div>';}\n h+='</div><div class=\"detail-duration\">'+fmtDur(t.duration)+'</div></div></div>';\n\n /* ── Failure panel (full width) ── */\n if((t.status==='failed'||t.status==='flaky')&&t.failure)h+=renderFailure(t.failure);\n 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>';\n\n /* ── Stacked sections: 1) Artifacts 2) Timeline / Network 3) API Calls ── */\n var hasArt=t.artifacts&&(t.artifacts.screenshot||t.artifacts.video);\n var hasSteps=t.steps&&t.steps.length>0;\n var hasApiCalls=t.apiCalls&&t.apiCalls.length>0;\n if(hasArt||hasSteps||hasApiCalls){\n h+='<div class=\"drawer-sections\">';\n if(hasArt)h+='<div class=\"drawer-section\">'+renderArtifactColumn(t.artifacts)+'</div>';\n if(hasSteps)h+='<div class=\"drawer-section\">'+renderTimelineColumn(t.steps,t.networkRequests)+'</div>';\n if(hasApiCalls)h+='<div class=\"drawer-section\">'+renderApiCallsColumn(t.apiCalls)+'</div>';\n h+='</div>';\n }\n\n h+='</div>';\n drawerBodyEl.innerHTML=h;\n drawerBodyEl.scrollTop=0;\n }\n\n /* ── Drawer open / close ── */\n function openDrawer(testId){\n renderDrawer(testId);\n drawerEl.classList.add('open');\n drawerBackdropEl.classList.add('open');\n document.body.style.overflow='hidden';\n }\n function closeDrawer(){\n drawerEl.classList.remove('open');\n drawerBackdropEl.classList.remove('open');\n document.body.style.overflow='';\n /* Pause any playing video */\n var v=drawerBodyEl.querySelector('video');\n if(v)v.pause();\n }\n\n /* ── Search ── */\n function doSearch(q){searchQuery=q;applyFilters();}\n`;\n","/**\n * Client-side JavaScript: Network.\n * Resource visualization, navigation timeline, Network DevTools panel, and failure rendering.\n */\nexport const JS_NETWORK = `\n /* ── Network Visualization Helpers ── */\n var RES_COLORS={xhr:'#60a5fa',document:'#818cf8',script:'#fbbf24',stylesheet:'#a78bfa',image:'#34d399',font:'#f472b6',other:'#6b7280'};\n var RES_LABELS={xhr:'XHR',document:'Doc',script:'JS',stylesheet:'CSS',image:'Img',font:'Font',other:'Other'};\n var RES_ORDER=['xhr','document','script','stylesheet','image','font','other'];\n\n function renderResBar(byType,total,mini){\n if(!byType||total===0)return '';\n var h='<div class=\"net-bar'+(mini?' net-bar--mini':'')+'\">';\n for(var i=0;i<RES_ORDER.length;i++){var k=RES_ORDER[i];var cnt=byType[k]||0;if(cnt===0)continue;var pct=(cnt/total*100).toFixed(1);h+='<div class=\"net-bar-seg\" style=\"width:'+pct+'%;background:'+RES_COLORS[k]+'\" title=\"'+RES_LABELS[k]+': '+cnt+' ('+(cnt/total*100).toFixed(0)+'%)\"></div>';}\n h+='</div>';return h;\n }\n\n function renderResLegend(byType){\n if(!byType)return '';\n var h='<div class=\"net-bar-legend\">';\n for(var i=0;i<RES_ORDER.length;i++){var k=RES_ORDER[i];var cnt=byType[k]||0;if(cnt===0)continue;h+='<span class=\"net-legend-item\"><span class=\"net-legend-dot\" style=\"background:'+RES_COLORS[k]+'\"></span>'+RES_LABELS[k]+' <span class=\"net-legend-count\">'+cnt+'</span></span>';}\n h+='</div>';return h;\n }\n\n function calcStepTiming(step){\n var t={};\n try{\n if(step.visitedAt&&step.domContentLoadedAt){var dcl=new Date(step.domContentLoadedAt).getTime()-new Date(step.visitedAt).getTime();if(dcl>=0)t.dcl=dcl;}\n if(step.visitedAt&&step.networkIdleAt){var idle=new Date(step.networkIdleAt).getTime()-new Date(step.visitedAt).getTime();if(idle>=0)t.idle=idle;}\n }catch(e){}\n return t;\n }\n\n function failStatusClass(code){var n=parseInt(code,10);if(n>=400&&n<500)return 'fail-url-badge--4xx';if(n>=500)return 'fail-url-badge--5xx';return 'fail-url-badge--other';}\n\n function renderFailedUrls(failedUrls){\n if(!failedUrls||failedUrls.length===0)return '';\n var h='<div class=\"fail-urls\">';\n for(var fi=0;fi<failedUrls.length;fi++){\n var furl=failedUrls[fi];var spIdx=furl.indexOf(' ');\n var fStatus=spIdx>0?furl.substring(0,spIdx):'';var fPath=spIdx>0?furl.substring(spIdx+1):furl;\n h+='<div class=\"fail-url\"><span class=\"fail-url-badge '+failStatusClass(fStatus)+'\">'+esc(fStatus)+'</span>'+esc(fPath)+'</div>';\n }\n h+='</div>';return h;\n }\n\n function renderNetworkPanel(stats){\n if(!stats)return '<div style=\"padding:7px 12px;font-size:11px;color:var(--fg-2);font-style:italic\">No network data</div>';\n var hasFail=stats.failedRequests>0;\n var h='<div class=\"net-header'+(hasFail?' has-fails':'')+'\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>Network</div>';\n h+='<div class=\"net-stats-row\"><div class=\"net-stat\"><span class=\"nv\">'+stats.totalRequests+'</span><span class=\"nl\">Requests</span></div><div class=\"net-stat'+(hasFail?' net-fail':'')+'\"><span class=\"nv\">'+stats.failedRequests+'</span><span class=\"nl\">Failed</span></div><div class=\"net-stat\"><span class=\"nv\">'+fmtBytes(stats.totalBytes)+'</span><span class=\"nl\">Transfer</span></div></div>';\n if(stats.byType&&stats.totalRequests>0){h+='<div class=\"net-bar-wrap\">'+renderResBar(stats.byType,stats.totalRequests,false)+'</div>';h+=renderResLegend(stats.byType);}\n if(hasFail&&stats.failedRequestUrls&&stats.failedRequestUrls.length>0)h+=renderFailedUrls(stats.failedRequestUrls);\n return h;\n }\n\n function renderNavTimeline(steps){\n if(!steps||steps.length===0)return '';\n var h='<div class=\"nav-timeline\">';\n for(var i=0;i<steps.length;i++){\n var s=steps[i];var hasNetFail=s.networkStats&&s.networkStats.failedRequests>0;\n h+='<div class=\"nav-step\"><div class=\"step-connector\"><div class=\"step-node '+(hasNetFail?'node-warn':'node-ok')+'\">'+(i+1)+'</div>';\n if(i<steps.length-1)h+='<div class=\"step-line\"></div>';\n h+='</div><div class=\"step-content\"><div class=\"step-header\" data-step=\"'+i+'\"><span class=\"step-url\" title=\"'+esc(s.url)+'\">'+esc(s.url)+'</span><span class=\"nav-badge nb-'+s.navigationType+'\">'+esc(s.navigationType.replace(/_/g,' '))+'</span><span class=\"step-meta\"><span>'+fmtDur(s.duration)+'</span>';\n if(s.networkStats)h+=' <span style=\"color:var(--fg-2)\">'+s.networkStats.totalRequests+' req</span>';\n h+='</span></div><div class=\"step-detail\" id=\"step-detail-'+i+'\">'+renderNetworkPanel(s.networkStats)+'</div></div></div>';\n }\n h+='</div>';return h;\n }\n\n /* ── Network DevTools Panel ── */\n function ndtStatusClass(code){var n=parseInt(code,10);if(n===0)return 'ndt-status--0';if(n<300)return 'ndt-status--2xx';if(n<400)return 'ndt-status--3xx';if(n<500)return 'ndt-status--4xx';return 'ndt-status--5xx';}\n function urlPath(u){try{var p=new URL(u);return p.pathname+p.search;}catch(e){return u;}}\n function prettyBody(body,ct){\n if(!body)return null;\n if(ct&&(ct.indexOf('json')>=0||ct.indexOf('javascript')>=0)){try{return JSON.stringify(JSON.parse(body),null,2);}catch(e){}}\n return body;\n }\n\n function renderNdtHeaders(headers,label){\n if(!headers)return '<div class=\"ndt-body-empty\">No '+label+' headers captured</div>';\n var keys=[];for(var k in headers){if(headers.hasOwnProperty(k))keys.push(k);}\n if(keys.length===0)return '<div class=\"ndt-body-empty\">No '+label+' headers captured</div>';\n keys.sort();\n var h='<table class=\"ndt-headers-tbl\"><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody>';\n for(var i=0;i<keys.length;i++){h+='<tr><td>'+esc(keys[i])+'</td><td>'+esc(headers[keys[i]])+'</td></tr>';}\n h+='</tbody></table>';return h;\n }\n\n function renderNdtBody(body,truncated,isBinary,ct,emptyLabel){\n if(isBinary)return '<div class=\"ndt-body-empty\">Binary content — not displayed</div>';\n if(!body)return '<div class=\"ndt-body-empty\">'+emptyLabel+'</div>';\n var pretty=prettyBody(body,ct);\n var h='<div class=\"ndt-body-wrap\"><pre class=\"ndt-body-pre\">'+esc(pretty||body)+'</pre>';\n if(truncated)h+='<span class=\"ndt-body-truncated\">Response truncated (body exceeded capture limit)</span>';\n h+='</div>';return h;\n }\n\n function renderNdtDetail(req,idx){\n var did='ndt-d-'+idx;\n var h='<div class=\"ndt-detail\" id=\"'+did+'\">';\n h+='<div class=\"ndt-detail-tabs\">';\n h+='<button class=\"ndt-detail-tab active\" data-ndt-tab=\"general\" data-ndt-target=\"'+did+'\">General</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-headers\" data-ndt-target=\"'+did+'\">Request Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-body\" data-ndt-target=\"'+did+'\">Payload</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-headers\" data-ndt-target=\"'+did+'\">Response Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-body\" data-ndt-target=\"'+did+'\">Response</button>';\n h+='</div>';\n\n /* General tab */\n h+='<div class=\"ndt-detail-pane active\" data-ndt-pane=\"general\">';\n h+='<div class=\"ndt-general\">';\n h+='<span class=\"ndt-general-k\">Request URL</span><span class=\"ndt-general-v\">'+esc(req.url)+'</span>';\n h+='<span class=\"ndt-general-k\">Method</span><span class=\"ndt-general-v\">'+esc(req.method)+'</span>';\n h+='<span class=\"ndt-general-k\">Status Code</span><span class=\"ndt-general-v'+(req.statusCode>=400||req.statusCode===0?' ndt-err':'')+'\">'+req.statusCode+(req.error?' ('+esc(req.error)+')':'')+'</span>';\n h+='<span class=\"ndt-general-k\">Resource Type</span><span class=\"ndt-general-v\">'+esc(req.resourceType)+'</span>';\n if(req.contentType)h+='<span class=\"ndt-general-k\">Content-Type</span><span class=\"ndt-general-v\">'+esc(req.contentType)+'</span>';\n h+='<span class=\"ndt-general-k\">Response Size</span><span class=\"ndt-general-v\">'+fmtBytes(req.responseSize||0)+'</span>';\n h+='<span class=\"ndt-general-k\">Response Time</span><span class=\"ndt-general-v\">'+fmtDur(req.responseTimeMs)+'</span>';\n if(req.startedAt)h+='<span class=\"ndt-general-k\">Started At</span><span class=\"ndt-general-v\">'+fmtDate(req.startedAt)+'</span>';\n if(req.error)h+='<span class=\"ndt-general-k\">Error</span><span class=\"ndt-general-v ndt-err\">'+esc(req.error)+'</span>';\n h+='</div></div>';\n\n /* Request Headers tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-headers\">'+renderNdtHeaders(req.requestHeaders,'request')+'</div>';\n\n /* Payload tab (request body) */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-body\">'+renderNdtBody(req.requestBody,req.requestBodyTruncated,false,req.contentType,'No request body')+'</div>';\n\n /* Response Headers tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-headers\">'+renderNdtHeaders(req.responseHeaders,'response')+'</div>';\n\n /* Response tab */\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-body\">'+renderNdtBody(req.responseBody,req.responseBodyTruncated,req.isBinary,req.contentType,'No response body captured')+'</div>';\n\n h+='</div>';return h;\n }\n\n function renderNetworkDevTools(reqs){\n if(!reqs||reqs.length===0)return '<div class=\"artifact-empty\">No network requests captured</div>';\n var uid='ndt-'+Math.random().toString(36).substr(2,6);\n var h='<div class=\"ndt\" id=\"'+uid+'\">';\n\n /* Toolbar with type filters and search */\n h+='<div class=\"ndt-toolbar\">';\n h+='<div class=\"ndt-filter\">';\n h+='<button class=\"ndt-filter-btn active\" data-ndt-filter=\"all\">All</button>';\n var types=['xhr','document','script','stylesheet','image','font','other'];\n var typeLabels={xhr:'XHR',document:'Doc',script:'JS',stylesheet:'CSS',image:'Img',font:'Font',other:'Other'};\n for(var ti=0;ti<types.length;ti++){\n var cnt=0;for(var ci=0;ci<reqs.length;ci++){if(reqs[ci].resourceType===types[ti])cnt++;}\n if(cnt>0)h+='<button class=\"ndt-filter-btn\" data-ndt-filter=\"'+types[ti]+'\">'+typeLabels[types[ti]]+' <span style=\"opacity:.6\">'+cnt+'</span></button>';\n }\n h+='</div>';\n h+='<input class=\"ndt-search\" placeholder=\"Filter URLs...\" data-ndt-search=\"'+uid+'\">';\n h+='<span class=\"ndt-count\" data-ndt-count=\"'+uid+'\">'+reqs.length+' requests</span>';\n h+='</div>';\n\n /* Request list */\n h+='<div class=\"ndt-list\" data-ndt-list=\"'+uid+'\">';\n for(var i=0;i<reqs.length;i++){\n var r=reqs[i];\n var isFail=r.statusCode>=400||r.statusCode===0;\n h+='<div class=\"ndt-row'+(isFail?' ndt-row--fail':'')+'\" data-ndt-idx=\"'+i+'\" data-ndt-type=\"'+esc(r.resourceType)+'\" data-ndt-url=\"'+esc(r.url.toLowerCase())+'\">';\n h+='<span class=\"ndt-status '+ndtStatusClass(r.statusCode)+'\">'+r.statusCode+'</span>';\n h+='<span class=\"ndt-method\">'+esc(r.method)+'</span>';\n h+='<span class=\"ndt-url\" title=\"'+esc(r.url)+'\">'+esc(urlPath(r.url))+'</span>';\n h+='<span class=\"ndt-type\">'+esc(RES_LABELS[r.resourceType]||r.resourceType)+'</span>';\n h+='<span class=\"ndt-size\">'+fmtBytes(r.responseSize||0)+'</span>';\n h+='<span class=\"ndt-time\">'+fmtDur(r.responseTimeMs)+'</span>';\n h+='</div>';\n h+=renderNdtDetail(r,i);\n }\n h+='</div></div>';return h;\n }\n\n function renderTimelineColumn(steps,networkRequests){\n if(!steps||steps.length===0)return '<div class=\"artifact-empty\">No navigation data</div>';\n var hasReqs=networkRequests&&networkRequests.length>0;\n var h='<div class=\"seg-ctrl\" data-seg-group=\"right-panel\">';\n h+='<button class=\"seg-btn active\" data-seg=\"timeline\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></svg>Timeline ('+steps.length+')</button>';\n h+='<button class=\"seg-btn\" data-seg=\"network\"><svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>Network'+(hasReqs?' ('+networkRequests.length+')':'')+'</button>';\n h+='</div>';\n h+='<div class=\"seg-pane active\" data-seg-pane=\"timeline\">'+renderNavTimeline(steps)+'</div>';\n h+='<div class=\"seg-pane\" data-seg-pane=\"network\">'+renderNetworkDevTools(networkRequests)+'</div>';\n return h;\n }\n\n function renderFailure(failure){\n if(!failure)return '';\n var sid='s-'+Math.random().toString(36).substr(2,8);\n 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</div>';\n h+='<div class=\"failure-message\">'+esc(stripAnsi(failure.message))+'</div>';\n if(failure.code)h+=renderCodeSnippet(failure.code);\n if(failure.line!==null&&failure.line!==undefined)h+='<div class=\"line-marker\">Line '+failure.line+'</div>';\n 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>';}\n h+='</div>';return h;\n }\n\n /* ── Network DevTools Filter Logic ── */\n /* ── API Calls Column (for API-only tests) ── */\n var API_METHOD_COLORS={GET:'#60a5fa',POST:'#34d399',PUT:'#fbbf24',PATCH:'#a78bfa',DELETE:'#f87171',HEAD:'#6b7280',OPTIONS:'#6b7280'};\n\n function renderApiCallDetail(ac,idx){\n var did='ndt-d-api-'+idx;\n var reqHeaders=ac.request?ac.request.headers:null;\n var reqBody=ac.request?ac.request.body:null;\n var resHeaders=ac.response?ac.response.headers:null;\n var resBody=ac.response?ac.response.body:null;\n var ct=(resHeaders&&resHeaders['content-type'])?resHeaders['content-type']:'';\n var reqBodyStr=(reqBody!==null&&reqBody!==undefined)?((typeof reqBody==='string')?reqBody:JSON.stringify(reqBody,null,2)):null;\n var resBodyStr=(resBody!==null&&resBody!==undefined)?((typeof resBody==='string')?resBody:JSON.stringify(resBody,null,2)):null;\n\n var h='<div class=\"ndt-detail\" id=\"'+did+'\">';\n h+='<div class=\"ndt-detail-tabs\">';\n h+='<button class=\"ndt-detail-tab active\" data-ndt-tab=\"general\" data-ndt-target=\"'+did+'\">General</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-headers\" data-ndt-target=\"'+did+'\">Request Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"req-body\" data-ndt-target=\"'+did+'\">Payload</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-headers\" data-ndt-target=\"'+did+'\">Response Headers</button>';\n h+='<button class=\"ndt-detail-tab\" data-ndt-tab=\"res-body\" data-ndt-target=\"'+did+'\">Response</button>';\n h+='</div>';\n\n /* General tab */\n h+='<div class=\"ndt-detail-pane active\" data-ndt-pane=\"general\"><div class=\"ndt-general\">';\n h+='<span class=\"ndt-general-k\">Request URL</span><span class=\"ndt-general-v\">'+esc(ac.url)+'</span>';\n h+='<span class=\"ndt-general-k\">Method</span><span class=\"ndt-general-v\">'+esc(ac.method)+'</span>';\n var sc=ac.response?ac.response.statusCode:0;\n var st=ac.response?ac.response.statusText:'';\n h+='<span class=\"ndt-general-k\">Status Code</span><span class=\"ndt-general-v'+(sc>=400?' ndt-err':'')+'\">'+sc+(st?' '+esc(st):'')+'</span>';\n h+='<span class=\"ndt-general-k\">Response Time</span><span class=\"ndt-general-v\">'+(ac.responseTime!==null?fmtDur(ac.responseTime):'N/A')+'</span>';\n if(ac.timestamp)h+='<span class=\"ndt-general-k\">Timestamp</span><span class=\"ndt-general-v\">'+fmtDate(ac.timestamp)+'</span>';\n h+='</div></div>';\n\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-headers\">'+renderNdtHeaders(reqHeaders,'request')+'</div>';\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"req-body\">'+renderNdtBody(reqBodyStr,false,false,ct,'No request body')+'</div>';\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-headers\">'+renderNdtHeaders(resHeaders,'response')+'</div>';\n h+='<div class=\"ndt-detail-pane\" data-ndt-pane=\"res-body\">'+renderNdtBody(resBodyStr,false,false,ct,'No response body captured')+'</div>';\n h+='</div>';\n return h;\n }\n\n function renderApiCallsColumn(apiCalls){\n if(!apiCalls||apiCalls.length===0)return '<div class=\"artifact-empty\">No API calls captured</div>';\n var uid='api-'+Math.random().toString(36).substr(2,6);\n var h='<div class=\"ndt\" id=\"'+uid+'\">';\n\n /* Toolbar */\n h+='<div class=\"ndt-toolbar\"><div class=\"ndt-filter\">';\n h+='<button class=\"ndt-filter-btn active\" data-ndt-filter=\"all\">All</button>';\n var methodCounts={};\n for(var mi=0;mi<apiCalls.length;mi++){var m=apiCalls[mi].method;methodCounts[m]=(methodCounts[m]||0)+1;}\n var methodKeys=[];for(var mk in methodCounts){if(methodCounts.hasOwnProperty(mk))methodKeys.push(mk);}\n methodKeys.sort();\n for(var ki=0;ki<methodKeys.length;ki++){var mk=methodKeys[ki];h+='<button class=\"ndt-filter-btn\" data-ndt-filter=\"'+mk+'\">'+mk+' <span style=\"opacity:.6\">'+methodCounts[mk]+'</span></button>';}\n h+='</div>';\n h+='<input class=\"ndt-search\" placeholder=\"Filter URLs...\" data-ndt-search=\"'+uid+'\">';\n h+='<span class=\"ndt-count\" data-ndt-count=\"'+uid+'\">'+apiCalls.length+' API calls</span>';\n h+='</div>';\n\n /* Request list */\n h+='<div class=\"ndt-list\" data-ndt-list=\"'+uid+'\">';\n for(var i=0;i<apiCalls.length;i++){\n var ac=apiCalls[i];\n var sc=ac.response?ac.response.statusCode:0;\n var isFail=sc>=400||sc===0;\n 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())+'\">';\n h+='<span class=\"ndt-status '+ndtStatusClass(sc)+'\">'+sc+'</span>';\n h+='<span class=\"ndt-method\" style=\"color:'+(API_METHOD_COLORS[ac.method]||'#6b7280')+'\">'+esc(ac.method)+'</span>';\n h+='<span class=\"ndt-url\" title=\"'+esc(ac.url)+'\">'+esc(urlPath(ac.url))+'</span>';\n h+='<span class=\"ndt-type\"></span>';\n h+='<span class=\"ndt-size\"></span>';\n h+='<span class=\"ndt-time\">'+(ac.responseTime!==null?fmtDur(ac.responseTime):'')+'</span>';\n h+='</div>';\n h+=renderApiCallDetail(ac,i);\n }\n h+='</div></div>';\n return h;\n }\n\n function ndtApplyFilters(ndt,filterType,searchVal){\n var list=ndt.querySelector('.ndt-list');if(!list)return;\n var searchInput=ndt.querySelector('.ndt-search');\n var q=(searchVal!==null&&searchVal!==undefined)?searchVal:(searchInput?searchInput.value:'');\n q=q.toLowerCase();\n var rows=list.querySelectorAll('.ndt-row');\n var visible=0;\n for(var i=0;i<rows.length;i++){\n var row=rows[i];\n var type=row.getAttribute('data-ndt-type');\n var url=row.getAttribute('data-ndt-url')||'';\n var show=true;\n if(filterType&&filterType!=='all'&&type!==filterType)show=false;\n if(show&&q&&url.indexOf(q)<0)show=false;\n row.style.display=show?'':'none';\n /* Also hide detail if row is hidden */\n var det=row.nextElementSibling;\n if(det&&det.classList.contains('ndt-detail')){if(!show){det.classList.remove('show');row.classList.remove('ndt-row--open');}}\n if(show)visible++;\n }\n var countEl=ndt.querySelector('.ndt-count');\n if(countEl)countEl.textContent=visible+' / '+rows.length+' requests';\n }\n`;\n","/**\n * Client-side JavaScript: Filter Bar & Multi-Dimensional Filter Logic\n *\n * Renders a filter bar with search + status chips + filter icon on the main bar.\n * Type and File filters render inside a separate filter drawer.\n * Manages filter state and applies filtering via re-rendering.\n * OR logic within dimensions, AND logic across dimensions, plus text search.\n */\n\nexport const JS_FILTERS = `\nvar _filterState={status:{},type:{},specFile:{}};\nvar _totalTests=0;\nvar _originalSummary=null;\n\nfunction renderFilterBar(){\n var filterEl=document.getElementById('filter-bar');\n var drawerBodyEl=document.getElementById('filter-drawer-body');\n var statusCounts={passed:0,failed:0,flaky:0,skipped:0,timedout:0};\n var typeCounts={},specCounts={};\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(statusCounts[t.status]!==undefined)statusCounts[t.status]++;\n var tp=t.testType||'unknown';\n typeCounts[tp]=(typeCounts[tp]||0)+1;\n if(t.filePath)specCounts[t.filePath]=(specCounts[t.filePath]||0)+1;\n }\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var hasAny=hasStatus||hasType||hasSpec||!!searchQuery;\n var hasAdvanced=hasType||hasSpec;\n\n /* ── Filter Bar (search + status chips + filter icon) ── */\n var h='';\n\n /* Search box (left-aligned, wider) */\n h+='<div class=\"search-box\"><svg class=\"search-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"M21 21l-4.35-4.35\"/></svg><input class=\"search-input\" id=\"search-input\" type=\"text\" placeholder=\"Search tests...\" value=\"'+esc(searchQuery)+'\"></div>';\n\n /* Status chips (inline) */\n h+='<div class=\"filter-chips\">';\n var statuses=['passed','failed','flaky','skipped','timedout'];\n var statusLabels={passed:'Passed',failed:'Failed',flaky:'Flaky',skipped:'Skipped',timedout:'Timed Out'};\n for(var i=0;i<statuses.length;i++){\n var s=statuses[i];var cnt=statusCounts[s]||0;\n var cls='filter-chip'+(!!_filterState.status[s]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n h+='<button class=\"'+cls+'\" data-dimension=\"status\" data-value=\"'+s+'\">'+statusLabels[s]+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n h+='</div>';\n\n /* Filter icon button */\n h+='<button class=\"filter-icon-btn\" title=\"More filters\">';\n h+='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 3H2l8 9.46V19l4 2v-8.54L22 3z\"/></svg>';\n if(hasAdvanced)h+='<span class=\"filter-icon-badge\"></span>';\n h+='</button>';\n\n if(filterEl)filterEl.innerHTML=h;\n\n /* ── Filter Drawer Body (type + file + actions) ── */\n var d='';\n\n /* Type section */\n var types=Object.keys(typeCounts).sort();\n if(types.length>0){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">Type</span>';\n d+='<div class=\"filter-chips\">';\n for(var i=0;i<types.length;i++){\n var tp=types[i];var cnt=typeCounts[tp]||0;\n var cls='filter-chip'+(!!_filterState.type[tp]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n d+='<button class=\"'+cls+'\" data-dimension=\"type\" data-value=\"'+esc(tp)+'\">'+esc(tp)+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n d+='</div></div>';\n }\n\n /* Spec file section */\n var specs=Object.keys(specCounts).sort();\n if(specs.length>1){\n d+='<div class=\"filter-drawer-section\"><span class=\"filter-drawer-section-label\">File</span>';\n d+='<div class=\"filter-chips\">';\n for(var i=0;i<specs.length;i++){\n var sp=specs[i];var cnt=specCounts[sp]||0;\n var short=sp.split('/').pop()||sp;\n var cls='filter-chip'+(!!_filterState.specFile[sp]?' active':'')+(cnt===0?' filter-chip--dimmed':'');\n d+='<button class=\"'+cls+'\" data-dimension=\"specFile\" data-value=\"'+esc(sp)+'\" title=\"'+esc(sp)+'\">'+esc(short)+' <span class=\"chip-count\">'+cnt+'</span></button>';\n }\n d+='</div></div>';\n }\n\n /* Actions (Clear all + indicator) */\n d+='<div class=\"filter-drawer-actions\">';\n d+='<button class=\"filter-clear\" id=\"filter-clear\" style=\"display:'+(hasAny?'inline-block':'none')+'\">Clear all</button>';\n d+='<span class=\"filter-indicator\" id=\"filter-indicator\" style=\"display:'+(hasAny?'inline':'none')+'\">';\n d+='Showing <span id=\"filter-shown\">0</span> of <span id=\"filter-total\">'+_totalTests+'</span> tests</span>';\n d+='</div>';\n\n if(drawerBodyEl)drawerBodyEl.innerHTML=d;\n}\n\nfunction initFilters(){\n _filterState={status:{},type:{},specFile:{}};\n _totalTests=tests.length;\n _originalSummary={total:data.summary.total,passed:data.summary.passed,\n failed:data.summary.failed,flaky:data.summary.flaky,\n skipped:data.summary.skipped,timedout:data.summary.timedout||0};\n}\n\nfunction getFilteredTests(){\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var out=[];\n for(var i=0;i<tests.length;i++){\n var t=tests[i];\n if(hasStatus&&!_filterState.status[t.status])continue;\n if(hasType&&!_filterState.type[t.testType||'unknown'])continue;\n if(hasSpec&&!_filterState.specFile[t.filePath])continue;\n if(searchQuery){\n var q=searchQuery.toLowerCase();\n var match=t.title.toLowerCase().indexOf(q)>=0||t.filePath.toLowerCase().indexOf(q)>=0||(t.suiteName&&t.suiteName.toLowerCase().indexOf(q)>=0);\n if(!match&&t.tags){for(var ti=0;ti<t.tags.length;ti++){if(t.tags[ti]&&t.tags[ti].toLowerCase().indexOf(q)>=0){match=true;break}}}\n if(!match)continue;\n }\n out.push(t);\n }\n return out;\n}\n\nfunction toggleFilter(dim,val){\n if(!_filterState[dim])_filterState[dim]={};\n if(_filterState[dim][val]){\n delete _filterState[dim][val];\n }else{\n _filterState[dim][val]=true;\n }\n applyFilters();\n}\n\nfunction clearAllFilters(){\n _filterState={status:{},type:{},specFile:{}};\n searchQuery='';\n var si=document.getElementById('search-input');\n if(si)si.value='';\n applyFilters();\n}\n\nfunction applyFilters(){\n renderFilterBar();\n renderTestGrid();\n var hasStatus=Object.keys(_filterState.status).length>0;\n var hasType=Object.keys(_filterState.type).length>0;\n var hasSpec=Object.keys(_filterState.specFile).length>0;\n var hasAny=hasStatus||hasType||hasSpec||!!searchQuery;\n if(hasAny){\n var filtered=getFilteredTests();\n var byStatus={passed:0,failed:0,flaky:0,skipped:0,timedout:0};\n for(var i=0;i<filtered.length;i++){\n var st=filtered[i].status;\n if(byStatus[st]!==undefined)byStatus[st]++;\n }\n _updateCard('s-total',filtered.length);\n _updateCard('s-passed',byStatus.passed);\n _updateCard('s-failed',byStatus.failed);\n _updateCard('s-flaky',byStatus.flaky);\n _updateCard('s-skipped',byStatus.skipped);\n _updateCard('s-timedout',byStatus.timedout);\n var shown=document.getElementById('filter-shown');\n if(shown)shown.textContent=''+filtered.length;\n }else if(_originalSummary){\n _updateCard('s-total',_originalSummary.total);\n _updateCard('s-passed',_originalSummary.passed);\n _updateCard('s-failed',_originalSummary.failed);\n _updateCard('s-flaky',_originalSummary.flaky);\n _updateCard('s-skipped',_originalSummary.skipped);\n _updateCard('s-timedout',_originalSummary.timedout);\n }\n}\n\nfunction _updateCard(cls,val){\n var el=document.querySelector('.'+cls+' .s-count');\n if(el)el.textContent=''+val;\n}\n`;\n","/**\n * Client-side JavaScript: Interactions\n *\n * Event delegation, theme management, lightbox, and filter drawer\n * for the HTML report.\n * Uses event delegation (document-level listeners) instead of inline handlers.\n */\n\nexport const JS_INTERACTIONS = `\n /* ── Event Delegation ── */\n document.addEventListener('click',function(e){\n var target=e.target;\n\n /* Test row click → open drawer */\n var row=target.closest('.test-row');\n if(row){openDrawer(row.getAttribute('data-testid'));return;}\n\n /* Filter chip (multi-dimensional) */\n var chip=target.closest('.filter-chip');\n if(chip){\n var dim=chip.getAttribute('data-dimension');\n var val=chip.getAttribute('data-value');\n if(dim&&val)toggleFilter(dim,val);\n return;\n }\n\n /* Clear all filters */\n if(target.closest('.filter-clear')){clearAllFilters();return;}\n\n /* Filter icon → open filter drawer */\n if(target.closest('.filter-icon-btn')){openFilterDrawer();return;}\n\n /* Filter drawer close */\n if(target.closest('#filter-drawer-close')){closeFilterDrawer();return;}\n if(target.closest('#filter-drawer-backdrop')){closeFilterDrawer();return;}\n\n /* Step header → expand/collapse */\n var stepH=target.closest('[data-step]');\n if(stepH){var sd=document.getElementById('step-detail-'+stepH.getAttribute('data-step'));if(sd)sd.classList.toggle('show');return;}\n\n /* Screenshot → lightbox */\n if(target.classList.contains('artifact-thumb')||target.classList.contains('artifact-col-img')){openLightbox(target.src);return;}\n\n /* Segmented control switch */\n var segBtn=target.closest('.seg-btn');\n if(segBtn){\n var group=segBtn.parentElement;\n if(group&&group.classList.contains('seg-ctrl')){\n var col=group.parentElement;\n var btns=group.querySelectorAll('.seg-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n segBtn.classList.add('active');\n var panes=col.querySelectorAll('.seg-pane');\n for(var pi=0;pi<panes.length;pi++){panes[pi].classList.remove('active');var vid=panes[pi].querySelector('video');if(vid)vid.pause();}\n var tp=col.querySelector('[data-seg-pane=\"'+segBtn.getAttribute('data-seg')+'\"]');\n if(tp)tp.classList.add('active');\n }\n return;\n }\n\n /* Network DevTools: request row → expand/collapse detail */\n var ndtRow=target.closest('.ndt-row');\n if(ndtRow){\n var idx=ndtRow.getAttribute('data-ndt-idx');\n var det=document.getElementById('ndt-d-'+idx);\n if(det){\n var isOpen=det.classList.toggle('show');\n ndtRow.classList.toggle('ndt-row--open',isOpen);\n /* Close other open details */\n var list=ndtRow.parentElement;\n if(list){var others=list.querySelectorAll('.ndt-detail.show');for(var oi=0;oi<others.length;oi++){if(others[oi]!==det){others[oi].classList.remove('show');var pr=others[oi].previousElementSibling;if(pr)pr.classList.remove('ndt-row--open');}}}\n }\n return;\n }\n\n /* Network DevTools: detail tab switch */\n var ndtTab=target.closest('.ndt-detail-tab');\n if(ndtTab){\n var tgt=ndtTab.getAttribute('data-ndt-target');\n var pane=ndtTab.getAttribute('data-ndt-tab');\n var det=document.getElementById(tgt);\n if(det){\n var tabs=det.querySelectorAll('.ndt-detail-tab');\n for(var ti=0;ti<tabs.length;ti++)tabs[ti].classList.remove('active');\n ndtTab.classList.add('active');\n var panes=det.querySelectorAll('.ndt-detail-pane');\n for(var pi=0;pi<panes.length;pi++)panes[pi].classList.remove('active');\n var tp=det.querySelector('[data-ndt-pane=\"'+pane+'\"]');\n if(tp)tp.classList.add('active');\n }\n return;\n }\n\n /* Network DevTools: type filter */\n var ndtFilterBtn=target.closest('.ndt-filter-btn');\n if(ndtFilterBtn){\n var filterType=ndtFilterBtn.getAttribute('data-ndt-filter');\n var toolbar=ndtFilterBtn.closest('.ndt-toolbar');\n if(toolbar){\n var btns=toolbar.querySelectorAll('.ndt-filter-btn');\n for(var bi=0;bi<btns.length;bi++)btns[bi].classList.remove('active');\n ndtFilterBtn.classList.add('active');\n var ndt=toolbar.closest('.ndt');\n if(ndt){ndtApplyFilters(ndt,filterType,null);}\n }\n return;\n }\n\n /* Stack trace toggle */\n if(target.classList.contains('stack-toggle-btn')){var se=document.getElementById(target.getAttribute('data-stack'));if(se){var sv=se.classList.toggle('show');target.textContent=sv?'Hide stack trace':'Show stack trace';}return;}\n\n /* Lightbox close */\n if(target.classList.contains('lightbox-overlay')||target.classList.contains('lightbox-close')){closeLightbox();return;}\n\n /* Theme toggle */\n var themeBtn=target.closest('.theme-btn');\n if(themeBtn){setTheme(themeBtn.getAttribute('data-theme-val'));return;}\n\n /* Drawer close */\n if(target.closest('#drawer-close')){closeDrawer();return;}\n if(target.closest('#drawer-backdrop')){closeDrawer();return;}\n });\n\n /* Search input — main test search + network filter search */\n document.addEventListener('input',function(e){\n if(e.target.id==='search-input')doSearch(e.target.value);\n if(e.target.hasAttribute('data-ndt-search')){\n var ndt=e.target.closest('.ndt');\n if(ndt){\n var activeBtn=ndt.querySelector('.ndt-filter-btn.active');\n var filterType=activeBtn?activeBtn.getAttribute('data-ndt-filter'):'all';\n ndtApplyFilters(ndt,filterType,e.target.value);\n }\n }\n });\n\n /* Keyboard */\n document.addEventListener('keydown',function(e){\n if(e.key==='Escape'){closeLightbox();closeDrawer();closeFilterDrawer();}\n });\n\n /* ── Theme Management ── */\n function getThemePref(){return localStorage.getItem('tr-theme')||'system'}\n function resolveTheme(pref){return pref==='system'?(window.matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):pref}\n function applyTheme(){\n var pref=getThemePref();\n document.documentElement.setAttribute('data-theme',resolveTheme(pref));\n var btns=document.querySelectorAll('.theme-btn');\n for(var i=0;i<btns.length;i++){btns[i].classList.toggle('active',btns[i].getAttribute('data-theme-val')===pref);}\n }\n function setTheme(pref){localStorage.setItem('tr-theme',pref);applyTheme();}\n try{window.matchMedia('(prefers-color-scheme:light)').addEventListener('change',function(){if(getThemePref()==='system')applyTheme();});}catch(e){}\n\n /* ── Lightbox ── */\n function openLightbox(src){\n var o=document.createElement('div');o.className='lightbox-overlay';\n o.innerHTML='<img src=\"'+src.replace(/\"/g,'"')+'\" alt=\"Screenshot\">';\n var b=document.createElement('button');b.className='lightbox-close';b.innerHTML='×';\n document.body.appendChild(o);document.body.appendChild(b);\n }\n function closeLightbox(){\n var o=document.querySelector('.lightbox-overlay');if(o)o.remove();\n var b=document.querySelector('.lightbox-close');if(b)b.remove();\n }\n\n /* ── Filter Drawer ── */\n function openFilterDrawer(){\n var d=document.getElementById('filter-drawer');if(d)d.classList.add('open');\n var b=document.getElementById('filter-drawer-backdrop');if(b)b.classList.add('open');\n }\n function closeFilterDrawer(){\n var d=document.getElementById('filter-drawer');if(d)d.classList.remove('open');\n var b=document.getElementById('filter-drawer-backdrop');if(b)b.classList.remove('open');\n }\n`;\n","/**\n * HTML Template for TestRelic Analytics Report\n *\n * Orchestrator: imports CSS, logo, and JS modules; assembles them\n * into a single self-contained HTML document.\n */\n\nimport { CSS } from './html-css.js';\nimport { LOGO_SVG, LOGO_SVG_RAW } from './html-logo.js';\nimport { JS_RENDER } from './html-js-render.js';\nimport { JS_NETWORK } from './html-js-network.js';\nimport { JS_FILTERS } from './html-js-filters.js';\nimport { JS_INTERACTIONS } from './html-js-interactions.js';\n\nconst JS = `\n(function(){\n var data=JSON.parse(document.getElementById('report-data').textContent);\n ${JS_RENDER}\n ${JS_NETWORK}\n ${JS_FILTERS}\n ${JS_INTERACTIONS}\n applyTheme();\n renderRunMeta();\n renderSummary();\n initFilters();\n renderFilterBar();\n renderTestGrid();\n})();`;\n\nexport function renderHtmlDocument(reportJson: string): string {\n // Escape </ sequences to prevent HTML parser from closing <script> tags prematurely.\n // JSON spec allows \\/ as an escape for /, so <\\/ is transparent to JSON.parse.\n const safeJson = reportJson.replace(/<\\//g, '<\\\\/');\n return `<!-- TestRelic AI Analytics Report — self-contained, no external dependencies -->\n<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<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';\">\n<title>TestRelic AI Analytics Report</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml;base64,${Buffer.from(LOGO_SVG_RAW).toString('base64')}\">\n<style>${CSS}</style>\n<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>\n</head>\n<body>\n<div class=\"wrap\">\n <div class=\"top-bar\">\n <div class=\"brand\">\n ${LOGO_SVG}\n <div class=\"brand-text\">\n <span class=\"brand-title\">TestRelic AI</span>\n <span class=\"brand-sub\">Analytics Report</span>\n </div>\n </div>\n <div class=\"run-meta\" id=\"run-meta\"></div>\n <div class=\"theme-toggle\" id=\"theme-toggle\">\n <button class=\"theme-btn\" data-theme-val=\"system\" title=\"System\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M8 21h8M12 17v4\"/></svg></button>\n <button class=\"theme-btn\" data-theme-val=\"light\" title=\"Light\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/></svg></button>\n <button class=\"theme-btn\" data-theme-val=\"dark\" title=\"Dark\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg></button>\n </div>\n <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\">–</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>\n </div>\n <div id=\"summary-strip\" class=\"summary-strip\"></div>\n <div id=\"filter-bar\" class=\"filter-bar\"></div>\n <div id=\"test-grid\"></div>\n</div>\n<div class=\"drawer-backdrop\" id=\"drawer-backdrop\"></div>\n<aside class=\"drawer\" id=\"drawer\">\n <div class=\"drawer-bar\">\n <span class=\"drawer-bar-title\">Test Details</span>\n <button class=\"drawer-close\" id=\"drawer-close\">×</button>\n </div>\n <div class=\"drawer-body\" id=\"drawer-body\"></div>\n</aside>\n<div class=\"filter-drawer-backdrop\" id=\"filter-drawer-backdrop\"></div>\n<aside class=\"filter-drawer\" id=\"filter-drawer\">\n <div class=\"filter-drawer-bar\">\n <span class=\"filter-drawer-title\">Filters</span>\n <button class=\"filter-drawer-close\" id=\"filter-drawer-close\">×</button>\n </div>\n <div class=\"filter-drawer-body\" id=\"filter-drawer-body\"></div>\n</aside>\n<script id=\"report-data\" type=\"application/json\">${safeJson}</script>\n<script>${JS}</script>\n</body>\n</html>`;\n}\n","/**\n * Cross-platform browser auto-open utility.\n *\n * Opens a file in the user's default browser.\n * Fire-and-forget — never throws, errors logged to stderr.\n */\n\nimport { exec } from 'node:child_process';\n\nexport function openInBrowser(filePath: string): void {\n try {\n const platform = process.platform;\n let command: string;\n\n if (platform === 'darwin') {\n command = `open \"${filePath}\"`;\n } else if (platform === 'win32') {\n command = `start \"\" \"${filePath}\"`;\n } else {\n command = `xdg-open \"${filePath}\"`;\n }\n\n exec(command, (err) => {\n if (err) {\n process.stderr.write(\n `[testrelic] Failed to open browser: ${err.message}\\n`,\n );\n }\n });\n } catch {\n // Never crash the reporter\n }\n}\n","/**\n * HTML Report Generator\n *\n * Generates a self-contained HTML report from a TestRunReport.\n * Utility functions for HTML escaping, ANSI stripping, and formatting.\n */\n\nimport { writeFileSync, renameSync, mkdirSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { TestRunReport } from '@testrelic/core';\nimport type { ResolvedConfig } from './config.js';\nimport { renderHtmlDocument } from './html-template.js';\nimport { openInBrowser } from './browser-open.js';\n\n// ---------------------------------------------------------------------------\n// Utility Functions\n// ---------------------------------------------------------------------------\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\nexport function escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (ch) => HTML_ESCAPE_MAP[ch] ?? ch);\n}\n\n// SAFETY: Regex targets all ANSI SGR escape sequences (color/style codes)\nconst ANSI_REGEX = /\\u001b\\[[0-9;]*m/g;\n\nexport function stripAnsi(str: string): string {\n return str.replace(ANSI_REGEX, '');\n}\n\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${Math.round(ms)}ms`;\n if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;\n const minutes = Math.floor(ms / 60_000);\n const seconds = Math.round((ms % 60_000) / 1000);\n return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (bytes === 0) return '0 B';\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ---------------------------------------------------------------------------\n// HTML Report Generation\n// ---------------------------------------------------------------------------\n\nexport function generateHtmlReport(report: TestRunReport): string {\n const reportJson = JSON.stringify(report);\n return renderHtmlDocument(reportJson);\n}\n\nexport function writeHtmlReport(report: TestRunReport, config: ResolvedConfig): void {\n try {\n const html = generateHtmlReport(report);\n const outputPath = config.htmlReportPath;\n const dir = dirname(outputPath);\n\n mkdirSync(dir, { recursive: true });\n\n // Atomic write: write to temp, then rename\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, html, 'utf-8');\n renameSync(tmpPath, outputPath);\n\n // Auto-open in browser if configured and not in CI\n if (config.openReport && report.ci === null) {\n const absolutePath = resolve(outputPath);\n openInBrowser(absolutePath);\n }\n } catch (err) {\n // FR-026: log to stderr, never crash\n process.stderr.write(\n `[testrelic] Failed to write HTML report: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n}\n","/**\n * ArtifactManager — copies test artifacts to structured output folders.\n *\n * Handles screenshot/video files from Playwright's result.attachments,\n * organizing them into per-test folders with sanitized names.\n */\n\nimport { mkdirSync, copyFileSync, existsSync } from 'node:fs';\nimport { join, extname } from 'node:path';\nimport type { TestArtifacts } from '@testrelic/core';\n\ninterface Attachment {\n name: string;\n contentType: string;\n path?: string;\n body?: Buffer;\n}\n\n/**\n * Sanitize a test title into a filesystem-safe folder name.\n *\n * Rules:\n * 1. Replace characters not in [a-zA-Z0-9-_ ] with hyphens\n * 2. Replace spaces with hyphens\n * 3. Collapse consecutive hyphens\n * 4. Trim leading/trailing hyphens\n * 5. Truncate to 100 characters\n */\nexport function sanitizeFolderName(title: string): string {\n let name = title\n .replace(/[^a-zA-Z0-9\\-_ ]/g, '-')\n .replace(/\\s+/g, '-')\n .replace(/-{2,}/g, '-')\n .replace(/^-+|-+$/g, '');\n\n if (name.length > 100) {\n name = name.substring(0, 100).replace(/-+$/, '');\n }\n\n return name || 'unnamed-test';\n}\n\n/**\n * Copy test artifacts from Playwright's temp locations to structured output folders.\n *\n * @returns TestArtifacts with relative paths, or null if no artifacts found.\n */\nexport function copyArtifacts(\n attachments: readonly Attachment[],\n testTitle: string,\n retryCount: number,\n outputDir: string,\n): TestArtifacts | null {\n const screenshot = attachments.find(\n (a) => a.name === 'screenshot' && a.path,\n );\n const video = attachments.find(\n (a) => a.name === 'video' && a.path,\n );\n\n if (!screenshot && !video) {\n return null;\n }\n\n let folderName = sanitizeFolderName(testTitle);\n if (retryCount > 0) {\n folderName += `--retry-${retryCount}`;\n }\n\n const artifactDir = join(outputDir, 'artifacts', folderName);\n const result: { screenshot?: string; video?: string } = {};\n\n try {\n mkdirSync(artifactDir, { recursive: true });\n } catch {\n // Cannot create directory — skip artifact capture\n return null;\n }\n\n if (screenshot?.path) {\n try {\n if (existsSync(screenshot.path)) {\n const ext = extname(screenshot.path) || '.png';\n const destName = `screenshot${ext}`;\n copyFileSync(screenshot.path, join(artifactDir, destName));\n result.screenshot = `artifacts/${folderName}/${destName}`;\n }\n } catch {\n // FR-026: never crash — skip this artifact\n }\n }\n\n if (video?.path) {\n try {\n if (existsSync(video.path)) {\n const ext = extname(video.path) || '.webm';\n const destName = `video${ext}`;\n copyFileSync(video.path, join(artifactDir, destName));\n result.video = `artifacts/${folderName}/${destName}`;\n }\n } catch {\n // FR-026: never crash — skip this artifact\n }\n }\n\n if (!result.screenshot && !result.video) {\n return null;\n }\n\n return result;\n}\n","/**\n * Timeline Builder — Constructs unified chronological timeline\n *\n * Merges navigation steps and API call steps into a single sorted array.\n * Each entry carries a type discriminator, sequential index, and test identity.\n */\n\nimport type {\n ApiCallRecord,\n ApiAssertion,\n NavigationAnnotation,\n NavigationType,\n FailureDiagnostic,\n NetworkStats,\n TestStatus,\n TestType,\n TestResult as TRTestResult,\n TestArtifacts,\n CapturedNetworkRequest,\n TimelineStep,\n NavigationStep,\n ApiCallStep,\n StepTestIdentity,\n StepAssertion,\n ApiCallStepResponse,\n} from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// CollectedTest fields needed for timeline building\n// ---------------------------------------------------------------------------\n\n/** Test data passed to the timeline builder. */\nexport interface TimelineTestData {\n readonly titlePath: string[];\n readonly title: string;\n readonly status: TestStatus;\n readonly duration: number;\n readonly startedAt: string;\n readonly completedAt: string;\n readonly retryCount: number;\n readonly retry: number;\n readonly tags: string[];\n readonly failure: FailureDiagnostic | null;\n readonly specFile: string;\n readonly navigations: NavigationAnnotation[];\n readonly apiCalls: ApiCallRecord[] | null;\n readonly apiAssertions: ApiAssertion[] | null;\n // Full test result fields for backward-compatible `tests[]` array\n readonly testId: string;\n readonly filePath: string;\n readonly suiteName: string;\n readonly testType: TestType;\n readonly isFlaky: boolean;\n readonly retryStatus: string | null;\n readonly expectedStatus: TestStatus;\n readonly actualStatus: TestStatus;\n readonly artifacts: TestArtifacts | null;\n readonly networkRequests: CapturedNetworkRequest[] | null;\n}\n\n// ---------------------------------------------------------------------------\n// Configuration for timeline building\n// ---------------------------------------------------------------------------\n\nexport interface TimelineBuildOptions {\n readonly navigationTypes: NavigationType[] | null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal unindexed step type (before index assignment)\n// ---------------------------------------------------------------------------\n\ninterface UnindexedStep {\n readonly type: 'navigation' | 'api_call';\n readonly url: string;\n readonly timestamp: string;\n readonly specFile: string;\n readonly test: StepTestIdentity;\n readonly tests: TRTestResult[];\n // Navigation-specific\n durationOnUrl?: number;\n readonly navigationType?: NavigationType;\n readonly domContentLoadedAt?: string | null;\n readonly networkIdleAt?: string | null;\n readonly networkStats?: NetworkStats | null;\n // API call-specific\n readonly callId?: string;\n readonly method?: string;\n readonly responseTime?: number | null;\n readonly request?: { readonly headers: Record<string, string> | null; readonly body: unknown };\n readonly response?: ApiCallStepResponse | null;\n readonly error?: string | null;\n readonly assertions?: readonly StepAssertion[];\n // Track source test for duration recalculation\n readonly _testTitle: string;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Build a unified timeline from collected test data.\n * Merges navigation and API call steps, sorts chronologically,\n * recalculates navigation durations, and assigns sequential indices.\n */\nexport function buildUnifiedTimeline(\n tests: readonly TimelineTestData[],\n options: TimelineBuildOptions,\n): TimelineStep[] {\n const allSteps: UnindexedStep[] = [];\n\n for (const test of tests) {\n const identity = buildTestIdentity(test);\n const testResult = toTestResult(test);\n const navSteps = buildNavigationSteps(test, identity, testResult, options);\n const apiSteps = buildApiCallSteps(test, identity, testResult);\n\n // If test has no navigations and no API calls, produce a dummy nav entry\n if (navSteps.length === 0 && apiSteps.length === 0) {\n allSteps.push({\n type: 'navigation',\n url: 'about:blank',\n timestamp: test.startedAt,\n durationOnUrl: test.duration,\n navigationType: 'dummy',\n domContentLoadedAt: null,\n networkIdleAt: null,\n networkStats: null,\n specFile: test.specFile,\n test: identity,\n tests: [testResult],\n _testTitle: test.title,\n });\n continue;\n }\n\n allSteps.push(...navSteps, ...apiSteps);\n }\n\n // Sort by timestamp, then navigation before api_call, then by callId\n allSteps.sort(compareSteps);\n\n // Recalculate navigation durations based on unified timeline\n recalculateNavigationDurations(allSteps, tests);\n\n // Assign sequential indices and produce final typed steps\n return allSteps.map((step, i) => {\n if (step.type === 'navigation') {\n const navStep: NavigationStep = {\n index: i,\n type: 'navigation',\n url: step.url,\n timestamp: step.timestamp,\n durationOnUrl: step.durationOnUrl ?? 0,\n navigationType: step.navigationType!,\n domContentLoadedAt: step.domContentLoadedAt ?? null,\n networkIdleAt: step.networkIdleAt ?? null,\n networkStats: step.networkStats ?? null,\n specFile: step.specFile,\n test: step.test,\n };\n // Add backward-compatible fields via spread\n return { ...navStep, tests: step.tests } as NavigationStep & { tests: TRTestResult[] };\n }\n\n const apiStep: ApiCallStep = {\n index: i,\n type: 'api_call',\n callId: step.callId!,\n method: step.method!,\n url: step.url,\n timestamp: step.timestamp,\n responseTime: step.responseTime ?? null,\n request: step.request!,\n response: step.response ?? null,\n ...(step.error ? { error: step.error } : {}),\n assertions: step.assertions ?? [],\n specFile: step.specFile,\n test: step.test,\n };\n return { ...apiStep, tests: step.tests } as ApiCallStep & { tests: TRTestResult[] };\n }) as TimelineStep[];\n}\n\n// ---------------------------------------------------------------------------\n// Step builders\n// ---------------------------------------------------------------------------\n\nfunction buildTestIdentity(test: TimelineTestData): StepTestIdentity {\n return {\n title: test.title,\n fullTitle: test.titlePath,\n status: test.status,\n duration: test.duration,\n retries: test.retryCount,\n retry: test.retry,\n tags: test.tags,\n failure: test.failure,\n };\n}\n\nfunction toTestResult(test: TimelineTestData): TRTestResult {\n return {\n title: test.title,\n status: test.status,\n duration: test.duration,\n startedAt: test.startedAt,\n completedAt: test.completedAt,\n retryCount: test.retryCount,\n tags: test.tags,\n failure: test.failure,\n testId: test.testId,\n filePath: test.filePath,\n suiteName: test.suiteName,\n testType: test.testType,\n isFlaky: test.isFlaky,\n retryStatus: test.retryStatus,\n expectedStatus: test.expectedStatus,\n actualStatus: test.actualStatus,\n artifacts: test.artifacts,\n networkRequests: test.networkRequests,\n apiCalls: test.apiCalls,\n apiAssertions: test.apiAssertions,\n };\n}\n\nfunction buildNavigationSteps(\n test: TimelineTestData,\n identity: StepTestIdentity,\n testResult: TRTestResult,\n options: TimelineBuildOptions,\n): UnindexedStep[] {\n const steps: UnindexedStep[] = [];\n\n for (const nav of test.navigations) {\n // Apply navigation type filter\n if (options.navigationTypes !== null && !options.navigationTypes.includes(nav.navigationType)) {\n continue;\n }\n\n steps.push({\n type: 'navigation',\n url: nav.url,\n timestamp: nav.timestamp,\n durationOnUrl: 0, // placeholder — recalculated after sorting\n navigationType: nav.navigationType,\n domContentLoadedAt: nav.domContentLoadedAt ?? null,\n networkIdleAt: nav.networkIdleAt ?? null,\n networkStats: nav.networkStats ?? null,\n specFile: test.specFile,\n test: identity,\n tests: [testResult],\n _testTitle: test.title,\n });\n }\n\n return steps;\n}\n\nfunction buildApiCallSteps(\n test: TimelineTestData,\n identity: StepTestIdentity,\n testResult: TRTestResult,\n): UnindexedStep[] {\n if (!test.apiCalls || test.apiCalls.length === 0) {\n return [];\n }\n\n const assertions = test.apiAssertions ?? [];\n\n return test.apiCalls.map((call): UnindexedStep => {\n // Filter assertions for this call\n const linkedAssertions: StepAssertion[] = assertions\n .filter((a) => a.callId === call.id)\n .map((a) => ({\n type: a.type,\n expected: a.expected,\n actual: a.actual,\n status: a.status,\n location: a.location,\n ...(a.expression !== undefined ? { expression: a.expression } : {}),\n }));\n\n // Build response, null on error\n let response: ApiCallStepResponse | null = null;\n if (call.responseStatusCode !== null && call.responseStatusText !== null) {\n response = {\n statusCode: call.responseStatusCode,\n statusText: call.responseStatusText,\n headers: call.responseHeaders,\n body: parseBody(call.responseBody),\n };\n }\n\n return {\n type: 'api_call',\n callId: call.id,\n method: call.method,\n url: call.url,\n timestamp: call.timestamp,\n responseTime: call.error ? null : call.responseTimeMs,\n request: {\n headers: call.requestHeaders,\n body: parseBody(call.requestBody),\n },\n response,\n ...(call.error ? { error: call.error } : {}),\n assertions: linkedAssertions,\n specFile: test.specFile,\n test: identity,\n tests: [testResult],\n _testTitle: test.title,\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Sorting\n// ---------------------------------------------------------------------------\n\nfunction compareSteps(a: UnindexedStep, b: UnindexedStep): number {\n const timeA = new Date(a.timestamp).getTime();\n const timeB = new Date(b.timestamp).getTime();\n\n if (timeA !== timeB) return timeA - timeB;\n\n // Same timestamp: navigation before api_call\n const typeOrder = { navigation: 0, api_call: 1 } as const;\n const typeA = typeOrder[a.type];\n const typeB = typeOrder[b.type];\n if (typeA !== typeB) return typeA - typeB;\n\n // Same type and timestamp: sort API calls by callId numeric suffix\n if (a.type === 'api_call' && b.type === 'api_call' && a.callId && b.callId) {\n return extractCallNumber(a.callId) - extractCallNumber(b.callId);\n }\n\n return 0;\n}\n\nfunction extractCallNumber(callId: string): number {\n const match = callId.match(/(\\d+)$/);\n return match ? parseInt(match[1], 10) : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Duration recalculation\n// ---------------------------------------------------------------------------\n\nfunction recalculateNavigationDurations(\n steps: UnindexedStep[],\n tests: readonly TimelineTestData[],\n): void {\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n if (step.type !== 'navigation') continue;\n\n const navTime = new Date(step.timestamp).getTime();\n\n // Find the next step from the same test\n let nextStepTime: number | null = null;\n for (let j = i + 1; j < steps.length; j++) {\n if (steps[j]._testTitle === step._testTitle) {\n nextStepTime = new Date(steps[j].timestamp).getTime();\n break;\n }\n }\n\n if (nextStepTime !== null) {\n step.durationOnUrl = Math.max(0, nextStepTime - navTime);\n } else {\n // Last step for this test — use test completedAt\n const test = tests.find((t) => t.title === step._testTitle);\n if (test) {\n const endTime = new Date(test.completedAt).getTime();\n step.durationOnUrl = Math.max(0, endTime - navTime);\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Body parsing helper\n// ---------------------------------------------------------------------------\n\nfunction parseBody(body: string | null): unknown {\n if (body === null || body === undefined) return null;\n try {\n return JSON.parse(body) as unknown;\n } catch {\n return body;\n }\n}\n","/**\n * Enriched summary statistics builder.\n *\n * Calculates comprehensive summary statistics from collected test data,\n * including API call metrics, assertion stats, and navigation stats.\n */\n\nimport type {\n Summary,\n ApiCallsByStatusRange,\n ApiResponseTimeStats,\n ApiCallRecord,\n ApiAssertion,\n NavigationAnnotation,\n TestStatus,\n} from '@testrelic/core';\n\n/** Minimal test data shape needed for summary calculation. */\nexport interface SummaryTestInput {\n readonly status: TestStatus;\n readonly navigations: readonly NavigationAnnotation[];\n readonly apiCalls: readonly ApiCallRecord[] | null;\n readonly apiAssertions: readonly ApiAssertion[] | null;\n}\n\n/**\n * Calculate the nearest-rank percentile from a sorted array.\n * Index = ceil(P/100 * N) - 1, clamped to [0, N-1].\n */\nfunction nearestRankPercentile(sorted: number[], percentile: number): number {\n const n = sorted.length;\n const index = Math.min(Math.ceil((percentile / 100) * n) - 1, n - 1);\n return sorted[Math.max(0, index)];\n}\n\n/** Strip query parameters and fragment from a URL for uniqueness comparison. */\nfunction stripUrlQuery(rawUrl: string): string {\n try {\n const parsed = new URL(rawUrl);\n return parsed.origin + parsed.pathname;\n } catch {\n return rawUrl;\n }\n}\n\n/** Categorize a response status code into a status range key. */\nfunction getStatusRange(statusCode: number | null): keyof ApiCallsByStatusRange {\n if (statusCode === null || statusCode === undefined) return 'error';\n if (statusCode >= 200 && statusCode < 300) return '2xx';\n if (statusCode >= 300 && statusCode < 400) return '3xx';\n if (statusCode >= 400 && statusCode < 500) return '4xx';\n if (statusCode >= 500 && statusCode < 600) return '5xx';\n return 'error';\n}\n\n/**\n * Build enriched summary statistics from collected test data.\n *\n * @param tests - Array of collected test data\n * @param timelineLength - Number of steps in the unified timeline\n * @returns Enriched Summary object with all 17 fields\n */\nexport function buildEnrichedSummary(\n tests: readonly SummaryTestInput[],\n timelineLength: number,\n): Summary {\n // Test status counts\n let passed = 0;\n let failed = 0;\n let flaky = 0;\n let skipped = 0;\n let timedout = 0;\n\n for (const test of tests) {\n switch (test.status) {\n case 'passed': passed++; break;\n case 'failed': failed++; break;\n case 'flaky': flaky++; break;\n case 'skipped': skipped++; break;\n case 'timedout': timedout++; break;\n }\n }\n\n // Flatten all API calls\n const allApiCalls: ApiCallRecord[] = [];\n for (const test of tests) {\n if (test.apiCalls) {\n allApiCalls.push(...test.apiCalls);\n }\n }\n\n // API call statistics\n const totalApiCalls = allApiCalls.length;\n const apiUrlSet = new Set<string>();\n const methodCounts: Record<string, number> = {};\n const statusRanges: ApiCallsByStatusRange = { '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0, error: 0 };\n const responseTimes: number[] = [];\n\n for (const call of allApiCalls) {\n apiUrlSet.add(stripUrlQuery(call.url));\n methodCounts[call.method] = (methodCounts[call.method] ?? 0) + 1;\n statusRanges[getStatusRange(call.responseStatusCode)] += 1;\n responseTimes.push(call.responseTimeMs);\n }\n\n // Response time percentiles\n let apiResponseTime: ApiResponseTimeStats | null = null;\n if (responseTimes.length > 0) {\n const sorted = [...responseTimes].sort((a, b) => a - b);\n const sum = sorted.reduce((acc, v) => acc + v, 0);\n apiResponseTime = {\n avg: Math.round(sum / sorted.length),\n min: sorted[0],\n max: sorted[sorted.length - 1],\n p50: nearestRankPercentile(sorted, 50),\n p95: nearestRankPercentile(sorted, 95),\n p99: nearestRankPercentile(sorted, 99),\n };\n }\n\n // Assertion statistics\n let totalAssertions = 0;\n let passedAssertions = 0;\n let failedAssertions = 0;\n for (const test of tests) {\n if (test.apiAssertions) {\n for (const assertion of test.apiAssertions) {\n totalAssertions++;\n if (assertion.status === 'passed') passedAssertions++;\n else failedAssertions++;\n }\n }\n }\n\n // Navigation statistics\n let totalNavigations = 0;\n const navUrlSet = new Set<string>();\n for (const test of tests) {\n totalNavigations += test.navigations.length;\n for (const nav of test.navigations) {\n navUrlSet.add(nav.url);\n }\n }\n\n return {\n total: tests.length,\n passed,\n failed,\n flaky,\n skipped,\n timedout,\n totalApiCalls,\n uniqueApiUrls: apiUrlSet.size,\n apiCallsByMethod: methodCounts,\n apiCallsByStatusRange: statusRanges,\n apiResponseTime,\n totalAssertions,\n passedAssertions,\n failedAssertions,\n totalNavigations,\n uniqueNavigationUrls: navUrlSet.size,\n totalTimelineSteps: timelineLength,\n };\n}\n","/**\n * Formatted console summary output.\n *\n * Prints a box-drawing formatted summary to stderr at test run completion.\n * Adapts to data present: e2e-only, API-only, or mixed.\n */\n\nimport type { Summary } from '@testrelic/core';\n\nconst BOX_WIDTH = 58;\n\nfunction pad(text: string): string {\n const padding = BOX_WIDTH - 2 - text.length;\n return padding > 0 ? text + ' '.repeat(padding) : text;\n}\n\nfunction line(text: string): string {\n return `\\u2502 ${pad(text)} \\u2502\\n`;\n}\n\n/**\n * Print a formatted console summary to stderr.\n *\n * @param summary - Enriched summary statistics\n * @param outputPath - Path to the JSON output file\n * @param quiet - When true, suppresses all output\n */\nexport function printConsoleSummary(\n summary: Summary,\n outputPath: string,\n quiet: boolean,\n): void {\n if (quiet) return;\n if (summary.total === 0) return;\n\n const top = `\\u250C${'─'.repeat(BOX_WIDTH)}\\u2510\\n`;\n const bottom = `\\u2514${'─'.repeat(BOX_WIDTH)}\\u2518\\n`;\n const blank = line('');\n\n let output = top;\n output += line('TestRelic Timeline Generated');\n output += blank;\n\n // Tests line\n const parts: string[] = [];\n if (summary.passed > 0) parts.push(`${summary.passed} \\u2713`);\n if (summary.failed > 0) parts.push(`${summary.failed} \\u2717`);\n if (summary.flaky > 0) parts.push(`${summary.flaky} \\u26A0`);\n if (summary.skipped > 0) parts.push(`${summary.skipped} skipped`);\n if (summary.timedout > 0) parts.push(`${summary.timedout} timedout`);\n output += line(`Tests: ${summary.total} total (${parts.join(' ')})`);\n\n // Navigation section (only if navigations present)\n if (summary.totalNavigations > 0) {\n output += line(\n `Navigations: ${summary.totalNavigations} visits across ${summary.uniqueNavigationUrls} unique URLs`,\n );\n }\n\n // API section (only if API calls present)\n if (summary.totalApiCalls > 0) {\n output += line(\n `API Calls: ${summary.totalApiCalls} calls across ${summary.uniqueApiUrls} unique endpoints`,\n );\n\n // Method breakdown\n const methodParts = Object.entries(summary.apiCallsByMethod)\n .filter(([, count]) => count > 0)\n .map(([method, count]) => `${method}: ${count}`);\n if (methodParts.length > 0) {\n output += line(` ${methodParts.join(' ')}`);\n }\n\n // Status breakdown (only non-zero)\n const statusParts = Object.entries(summary.apiCallsByStatusRange)\n .filter(([, count]) => count > 0)\n .map(([range, count]) => `${range}: ${count}`);\n if (statusParts.length > 0) {\n output += line(` ${statusParts.join(' ')}`);\n }\n\n // Response times\n if (summary.apiResponseTime) {\n output += line(\n ` Avg response: ${summary.apiResponseTime.avg}ms P95: ${summary.apiResponseTime.p95}ms`,\n );\n }\n }\n\n // Assertion section (only if assertions present)\n if (summary.totalAssertions > 0) {\n output += line(\n `Assertions: ${summary.totalAssertions} total (${summary.passedAssertions} \\u2713 ${summary.failedAssertions} \\u2717)`,\n );\n }\n\n // Output path\n output += line(`Output: ${outputPath}`);\n\n output += bottom;\n\n process.stderr.write(output);\n}\n","/**\n * TestRelicReporter — Playwright custom reporter\n *\n * Captures test execution data and produces a structured JSON timeline.\n * All hooks are wrapped in try/catch (FR-026): never crashes the test run.\n */\n\nimport { randomUUID, createHash } from 'node:crypto';\nimport { mkdirSync, writeFileSync, renameSync } from 'node:fs';\nimport { dirname, relative } from 'node:path';\nimport type {\n ApiCallRecord,\n ApiAssertion,\n CapturedNetworkRequest,\n ReporterConfig,\n TestRunReport,\n Summary,\n TimelineStep,\n TestResult as TRTestResult,\n TestArtifacts,\n NavigationAnnotation,\n FailureDiagnostic,\n TestStatus,\n TestType,\n} from '@testrelic/core';\nimport { isTestRelicDataPayload, ATTACHMENT_NAME } from '@testrelic/core';\nimport { resolveConfig, resolveApiConfig } from './config.js';\nimport type { ResolvedConfig, ResolvedApiConfig } from './config.js';\nimport { SCHEMA_VERSION } from './schema.js';\nimport { extractCodeSnippet } from './code-extractor.js';\nimport { createRedactor } from './redaction.js';\nimport { detectCI } from './ci-detector.js';\nimport { writeHtmlReport } from './html-report.js';\nimport { copyArtifacts } from './artifact-manager.js';\nimport { buildUnifiedTimeline } from './timeline-builder.js';\nimport type { TimelineTestData } from './timeline-builder.js';\nimport { buildEnrichedSummary } from './summary-builder.js';\nimport { printConsoleSummary } from './console-summary.js';\n\n// Playwright types — imported for type annotations only\ninterface PwFullConfig {\n rootDir: string;\n [key: string]: unknown;\n}\n\ninterface PwSuite {\n allTests(): PwTestCase[];\n}\n\ninterface PwTestCase {\n title: string;\n titlePath(): string[];\n annotations: Array<{ type: string; description?: string }>;\n tags?: string[];\n location: { file: string; line: number; column: number };\n results: PwTestResult[];\n outcome(): 'expected' | 'unexpected' | 'skipped' | 'flaky';\n expectedStatus: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';\n id: string;\n}\n\ninterface PwTestResult {\n status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';\n duration: number;\n startTime: Date;\n retry: number;\n errors: PwTestError[];\n attachments: Array<{ name: string; contentType: string; path?: string; body?: Buffer }>;\n}\n\ninterface PwTestError {\n message?: string;\n stack?: string;\n location?: { file: string; line: number; column: number };\n snippet?: string;\n}\n\ninterface PwFullResult {\n status: 'passed' | 'failed' | 'timedout' | 'interrupted';\n startTime: Date;\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helper functions (private to this module)\n// ---------------------------------------------------------------------------\n\nfunction mapPlaywrightStatus(status: string): TestStatus {\n switch (status) {\n case 'passed': return 'passed';\n case 'failed': return 'failed';\n case 'timedOut': return 'timedout';\n case 'skipped': return 'skipped';\n case 'interrupted': return 'failed';\n default: return 'failed';\n }\n}\n\nfunction generateTestId(filePath: string, suiteName: string, title: string): string {\n const input = `${filePath}::${suiteName}::${title}`;\n return createHash('sha256').update(input).digest('hex').substring(0, 16);\n}\n\nfunction getSuiteName(titlePath: string[]): string {\n // titlePath: ['', 'project', 'file.spec.ts', ...suites..., 'test title']\n // <= 4 elements means no describe block (root, project, file, test)\n if (titlePath.length <= 4) return '';\n return titlePath[titlePath.length - 2];\n}\n\nfunction getRetryStatus(results: PwTestResult[]): string | null {\n const passedIndex = results.findIndex((r) => r.status === 'passed');\n return passedIndex > 0 ? `passed on retry ${passedIndex}` : null;\n}\n\nfunction getTestType(tags: string[], filePath: string): TestType {\n const typeOrder: TestType[] = ['e2e', 'api', 'unit'];\n for (const type of typeOrder) {\n if (tags.some((tag) => tag === `@${type}` || tag === type)) {\n return type;\n }\n }\n for (const type of typeOrder) {\n if (filePath.includes(`/${type}/`)) {\n return type;\n }\n }\n return 'unknown';\n}\n\n// Internal state for collected test data\ninterface CollectedTest {\n titlePath: string[];\n title: string;\n status: TestStatus;\n duration: number;\n startedAt: string;\n completedAt: string;\n retryCount: number;\n retry: number;\n tags: string[];\n failure: FailureDiagnostic | null;\n specFile: string;\n navigations: NavigationAnnotation[];\n testId: string;\n filePath: string;\n suiteName: string;\n testType: TestType;\n isFlaky: boolean;\n retryStatus: string | null;\n expectedStatus: TestStatus;\n actualStatus: TestStatus;\n artifacts: TestArtifacts | null;\n networkRequests: CapturedNetworkRequest[] | null;\n apiCalls: ApiCallRecord[] | null;\n apiAssertions: ApiAssertion[] | null;\n}\n\nconst API_CONFIG_ANNOTATION = '__testrelic_api_config';\n\n/** Serialize ResolvedApiConfig to JSON, converting RegExp to serializable form. */\nfunction serializeApiConfig(config: ResolvedApiConfig): string {\n return JSON.stringify(config, (_key, value) => {\n if (value instanceof RegExp) {\n return { __regexp: true, source: value.source, flags: value.flags };\n }\n return value;\n });\n}\n\nexport default class TestRelicReporter {\n private config: ResolvedConfig;\n private apiConfig: ResolvedApiConfig;\n private rootDir = '';\n private startedAt = '';\n private testRunId = '';\n private collectedTests: CollectedTest[] = [];\n\n constructor(options?: Partial<ReporterConfig>) {\n this.config = resolveConfig(options);\n this.apiConfig = resolveApiConfig(options);\n }\n\n onBegin(config: PwFullConfig, _suite: PwSuite): void {\n try {\n this.rootDir = config.rootDir;\n this.startedAt = new Date().toISOString();\n this.testRunId = this.config.testRunId ?? randomUUID();\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n onTestBegin(test: PwTestCase, _result: PwTestResult): void {\n try {\n test.annotations.push({\n type: API_CONFIG_ANNOTATION,\n description: serializeApiConfig(this.apiConfig),\n });\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n onTestEnd(test: PwTestCase, result: PwTestResult): void {\n try {\n const lastResult = result;\n const outcome = test.outcome();\n\n // Determine status using expanded mapping\n let status: TestStatus;\n if (outcome === 'flaky') {\n status = 'flaky';\n } else if (outcome === 'skipped') {\n status = 'skipped';\n } else {\n status = mapPlaywrightStatus(lastResult.status);\n }\n\n const startedAt = lastResult.startTime.toISOString();\n const completedAt = new Date(lastResult.startTime.getTime() + lastResult.duration).toISOString();\n\n // Extract tags: prefer test.tags (1.42+), fallback to annotations\n const tags = test.tags\n ? [...test.tags]\n : test.annotations\n .filter((a) => a.type === 'tag')\n .map((a) => a.description ?? '');\n\n // Extract data: prefer consolidated attachments, fall back to legacy annotations\n let navigations: NavigationAnnotation[] = [];\n let networkRequests: CapturedNetworkRequest[] | null = null;\n let apiCalls: ApiCallRecord[] | null = null;\n let apiAssertions: ApiAssertion[] | null = null;\n\n const payloadAttachments = lastResult.attachments.filter(\n (a) => a.name === ATTACHMENT_NAME && a.body,\n );\n\n let attachmentParsed = false;\n if (payloadAttachments.length > 0) {\n // New path: read from consolidated attachment(s)\n for (const att of payloadAttachments) {\n try {\n const parsed = JSON.parse(att.body!.toString());\n if (isTestRelicDataPayload(parsed)) {\n navigations = navigations.concat(parsed.navigations);\n if (parsed.networkRequests.length > 0) {\n networkRequests = (networkRequests ?? [] as CapturedNetworkRequest[]).concat(parsed.networkRequests as CapturedNetworkRequest[]);\n }\n if (parsed.apiCalls.length > 0) {\n apiCalls = (apiCalls ?? [] as ApiCallRecord[]).concat(parsed.apiCalls as ApiCallRecord[]);\n }\n if (parsed.apiAssertions.length > 0) {\n apiAssertions = (apiAssertions ?? [] as ApiAssertion[]).concat(parsed.apiAssertions as ApiAssertion[]);\n }\n attachmentParsed = true;\n }\n } catch {\n process.stderr.write(`[testrelic] Warning: Corrupt attachment for test \"${test.title}\", falling back to annotations\\n`);\n }\n }\n }\n\n if (!attachmentParsed) {\n // Legacy/fallback path: read from individual annotations\n navigations = test.annotations\n .filter((a) => a.type === 'lambdatest-navigation' && a.description)\n .map((a) => {\n try { return JSON.parse(a.description!) as NavigationAnnotation; } catch { return null; }\n })\n .filter((n): n is NavigationAnnotation => n !== null);\n\n const networkReqAnnotation = test.annotations.find(\n (a) => a.type === '__testrelic_network_requests' && a.description,\n );\n if (networkReqAnnotation) {\n try { networkRequests = JSON.parse(networkReqAnnotation.description!) as CapturedNetworkRequest[]; } catch { /* ignore */ }\n }\n\n const apiCallsAnnotation = test.annotations.find(\n (a) => a.type === '__testrelic_api_calls' && a.description,\n );\n if (apiCallsAnnotation) {\n try { apiCalls = JSON.parse(apiCallsAnnotation.description!) as ApiCallRecord[]; } catch { /* ignore */ }\n }\n\n const apiAssertionsAnnotation = test.annotations.find(\n (a) => a.type === '__testrelic_api_assertions' && a.description,\n );\n if (apiAssertionsAnnotation) {\n try { apiAssertions = JSON.parse(apiAssertionsAnnotation.description!) as ApiAssertion[]; } catch { /* ignore */ }\n }\n }\n\n // T012: Warn when no data found (possible worker crash)\n const hasNoData = navigations.length === 0 && !networkRequests && !apiCalls && !apiAssertions;\n if (hasNoData && status !== 'skipped') {\n process.stderr.write(`[testrelic] Warning: No data found for test \"${test.title}\", data may be incomplete due to worker crash\\n`);\n }\n\n // Build failure diagnostic\n let failure: FailureDiagnostic | null = null;\n if (status === 'failed' || status === 'flaky') {\n const errors = status === 'flaky'\n ? (test.results.find((r) => r.status !== 'passed')?.errors ?? [])\n : lastResult.errors;\n const firstError = errors[0];\n if (firstError) {\n const redact = createRedactor(this.config.redactPatterns);\n const errorLine = firstError.location?.line ?? null;\n let codeSnippet: string | null = null;\n if (this.config.includeCodeSnippets && errorLine !== null && firstError.location?.file) {\n codeSnippet = extractCodeSnippet(\n firstError.location.file,\n errorLine,\n this.config.codeContextLines,\n );\n if (codeSnippet) codeSnippet = redact(codeSnippet);\n }\n\n failure = {\n message: redact(firstError.message ?? 'Unknown error'),\n line: errorLine,\n code: codeSnippet,\n stack: this.config.includeStackTrace ? (firstError.stack ? redact(firstError.stack) : null) : null,\n };\n }\n }\n\n const titlePath = test.titlePath().filter(Boolean);\n const specFile = relative(this.rootDir || '.', test.location.file);\n\n // Extract enhanced metadata\n const suiteName = getSuiteName(titlePath);\n const title = titlePath.join(' > ');\n const filePath = specFile;\n const testId = generateTestId(filePath, suiteName, title);\n const testType = getTestType(tags, filePath);\n const isFlaky = outcome === 'flaky';\n const retryStatus = getRetryStatus(test.results);\n const expectedStatus = mapPlaywrightStatus(test.expectedStatus);\n const actualStatus = mapPlaywrightStatus(lastResult.status);\n\n // Extract artifacts (screenshots/videos) from Playwright attachments\n let artifacts: TestArtifacts | null = null;\n if (this.config.includeArtifacts && status !== 'skipped' && lastResult.attachments) {\n const outputDir = dirname(this.config.outputPath);\n const lastTitle = titlePath[titlePath.length - 1] ?? test.title;\n artifacts = copyArtifacts(lastResult.attachments, lastTitle, lastResult.retry, outputDir);\n }\n\n this.collectedTests.push({\n titlePath,\n title,\n status,\n duration: lastResult.duration,\n startedAt,\n completedAt,\n retryCount: test.results.length - 1,\n retry: lastResult.retry,\n tags,\n failure,\n specFile,\n navigations,\n testId,\n filePath,\n suiteName,\n testType,\n isFlaky,\n retryStatus,\n expectedStatus,\n actualStatus,\n artifacts,\n networkRequests,\n apiCalls,\n apiAssertions,\n });\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n onEnd(_result: PwFullResult): void {\n try {\n const completedAt = new Date().toISOString();\n const startedAtTime = new Date(this.startedAt).getTime();\n const completedAtTime = new Date(completedAt).getTime();\n const totalDuration = completedAtTime - startedAtTime;\n\n // Build timeline from navigation data\n const timeline = this.buildTimeline();\n\n // Build enriched summary\n const summary = buildEnrichedSummary(this.collectedTests, timeline.length);\n\n const report: TestRunReport = {\n schemaVersion: SCHEMA_VERSION,\n testRunId: this.testRunId,\n startedAt: this.startedAt,\n completedAt,\n totalDuration,\n summary,\n ci: detectCI(),\n metadata: this.config.metadata,\n timeline,\n shardRunIds: null,\n };\n\n this.writeReport(report);\n writeHtmlReport(report, this.config);\n printConsoleSummary(summary, this.config.outputPath, this.config.quiet);\n } catch {\n // FR-026: never crash the test run\n }\n }\n\n printsToStdio(): boolean {\n return false;\n }\n\n private buildTimeline(): TimelineStep[] {\n const testData: TimelineTestData[] = this.collectedTests.map((t) => ({\n titlePath: t.titlePath,\n title: t.title,\n status: t.status,\n duration: t.duration,\n startedAt: t.startedAt,\n completedAt: t.completedAt,\n retryCount: t.retryCount,\n retry: t.retry,\n tags: t.tags,\n failure: t.failure,\n specFile: t.specFile,\n navigations: t.navigations,\n apiCalls: t.apiCalls,\n apiAssertions: t.apiAssertions,\n testId: t.testId,\n filePath: t.filePath,\n suiteName: t.suiteName,\n testType: t.testType,\n isFlaky: t.isFlaky,\n retryStatus: t.retryStatus,\n expectedStatus: t.expectedStatus,\n actualStatus: t.actualStatus,\n artifacts: t.artifacts,\n networkRequests: t.networkRequests,\n }));\n\n return buildUnifiedTimeline(testData, {\n navigationTypes: this.config.navigationTypes,\n });\n }\n\n\n private toTestResult(test: CollectedTest): TRTestResult {\n return {\n title: test.title,\n status: test.status,\n duration: test.duration,\n startedAt: test.startedAt,\n completedAt: test.completedAt,\n retryCount: test.retryCount,\n tags: test.tags,\n failure: test.failure,\n testId: test.testId,\n filePath: test.filePath,\n suiteName: test.suiteName,\n testType: test.testType,\n isFlaky: test.isFlaky,\n retryStatus: test.retryStatus,\n expectedStatus: test.expectedStatus,\n actualStatus: test.actualStatus,\n artifacts: test.artifacts,\n networkRequests: test.networkRequests,\n apiCalls: test.apiCalls,\n apiAssertions: test.apiAssertions,\n };\n }\n\n private writeReport(report: TestRunReport): void {\n try {\n const json = JSON.stringify(report, null, 2);\n const outputPath = this.config.outputPath;\n const dir = dirname(outputPath);\n\n mkdirSync(dir, { recursive: true });\n\n // Atomic write: write to temp, then rename\n const tmpPath = outputPath + '.tmp';\n writeFileSync(tmpPath, json, 'utf-8');\n renameSync(tmpPath, outputPath);\n } catch (err) {\n // FR-026: log to stderr, never crash\n process.stderr.write(\n `[testrelic] Failed to write report: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n }\n }\n}\n","/**\n * NavigationTracker — wraps a Playwright Page to track navigation events.\n *\n * Intercepts page methods (goto, goBack, goForward, reload) and listens\n * for DOM events (framenavigated, domcontentloaded, load) plus injected\n * SPA detection scripts.\n */\n\nimport type { CapturedNetworkRequest, NavigationAnnotation, NavigationType, NetworkStats, ResourceBreakdown, ResourceType } from '@testrelic/core';\n\n// Minimal Playwright Page type subset for loose coupling\ninterface PageLike {\n goto(url: string, options?: unknown): Promise<unknown>;\n goBack(options?: unknown): Promise<unknown>;\n goForward(options?: unknown): Promise<unknown>;\n reload(options?: unknown): Promise<unknown>;\n url(): string;\n on(event: string, handler: (...args: unknown[]) => void): void;\n off(event: string, handler: (...args: unknown[]) => void): void;\n addInitScript(script: string | { path?: string } | (() => void)): Promise<void>;\n evaluate(fn: (...args: unknown[]) => unknown, ...args: unknown[]): Promise<unknown>;\n mainFrame(): { url(): string };\n}\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\ninterface NavigationRecord {\n url: string;\n navigationType: NavigationType;\n timestamp: string;\n domContentLoadedAt?: string;\n networkIdleAt?: string;\n networkStats?: NetworkStats;\n}\n\ninterface NetworkCounter {\n totalRequests: number;\n failedRequests: number;\n failedRequestUrls: string[];\n totalBytes: number;\n byType: Record<keyof ResourceBreakdown, number>;\n}\n\ninterface PendingRequest {\n url: string;\n method: string;\n resourceType: ResourceType;\n headers: Record<string, string>;\n postData: string | null;\n postDataTruncated: boolean;\n startedAt: string;\n startTimeMs: number;\n}\n\nconst MAX_BODY_SIZE = 10240; // 10 KB\nconst MAX_REQUESTS_PER_TEST = 500;\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some(prefix => lower.includes(prefix));\n}\n\nfunction truncateBody(body: string, maxSize: number): { text: string; truncated: boolean } {\n const bytes = new TextEncoder().encode(body);\n if (bytes.length <= maxSize) {\n return { text: body, truncated: false };\n }\n // Truncate at byte boundary, decode back to valid string\n const truncated = new TextDecoder().decode(bytes.slice(0, maxSize));\n return { text: truncated, truncated: true };\n}\n\nexport class NavigationTracker {\n private records: NavigationRecord[] = [];\n private listeners: Array<{ event: string; handler: (...args: unknown[]) => void }> = [];\n private origGoto: PageLike['goto'];\n private origGoBack: PageLike['goBack'];\n private origGoForward: PageLike['goForward'];\n private origReload: PageLike['reload'];\n private lastDomContentLoaded: string | undefined;\n private includeNetworkStats: boolean;\n private currentNetworkCounter: NetworkCounter | null = null;\n private pendingRequests = new Map<string, PendingRequest>();\n private capturedRequests: CapturedNetworkRequest[] = [];\n private pendingBodyReads: Promise<void>[] = [];\n private requestIdCounter = 0;\n private requestCaptureCount = 0;\n\n constructor(private page: PageLike, options?: { includeNetworkStats?: boolean }) {\n this.includeNetworkStats = options?.includeNetworkStats ?? true;\n // Store originals\n this.origGoto = page.goto.bind(page);\n this.origGoBack = page.goBack.bind(page);\n this.origGoForward = page.goForward.bind(page);\n this.origReload = page.reload.bind(page);\n\n this.interceptMethods();\n this.attachListeners();\n }\n\n async init(): Promise<void> {\n await this.injectSPADetection();\n }\n\n async getCapturedRequests(): Promise<CapturedNetworkRequest[]> {\n // Wait for all pending body reads to complete\n await Promise.allSettled(this.pendingBodyReads);\n this.pendingBodyReads = [];\n // Convert any remaining pending requests (incomplete) to captured entries\n for (const [id, pending] of this.pendingRequests) {\n this.capturedRequests.push({\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: 'incomplete',\n });\n this.pendingRequests.delete(id);\n }\n // Return sorted by startedAt\n return [...this.capturedRequests].sort((a, b) => a.startedAt.localeCompare(b.startedAt));\n }\n\n async getData(): Promise<{ navigations: NavigationAnnotation[]; networkRequests: CapturedNetworkRequest[] }> {\n // Finalize network stats for the last navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n const navigations: NavigationAnnotation[] = this.records.map(record => ({\n url: record.url,\n navigationType: record.navigationType,\n timestamp: record.timestamp,\n domContentLoadedAt: record.domContentLoadedAt,\n networkIdleAt: record.networkIdleAt,\n networkStats: record.networkStats,\n }));\n\n const networkRequests = this.includeNetworkStats\n ? await this.getCapturedRequests()\n : [];\n\n return { navigations, networkRequests };\n }\n\n async flushLegacyAnnotations(testInfo: TestInfoLike): Promise<void> {\n // Finalize network stats for the last navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n for (const record of this.records) {\n const annotation: NavigationAnnotation = {\n url: record.url,\n navigationType: record.navigationType,\n timestamp: record.timestamp,\n domContentLoadedAt: record.domContentLoadedAt,\n networkIdleAt: record.networkIdleAt,\n networkStats: record.networkStats,\n };\n testInfo.annotations.push({\n type: 'lambdatest-navigation',\n description: JSON.stringify(annotation),\n });\n }\n\n // Capture and serialize individual network requests\n if (this.includeNetworkStats) {\n const requests = await this.getCapturedRequests();\n if (requests.length > 0) {\n testInfo.annotations.push({\n type: '__testrelic_network_requests',\n description: JSON.stringify(requests),\n });\n }\n }\n }\n\n dispose(): void {\n // Restore original methods\n (this.page as Record<string, unknown>).goto = this.origGoto;\n (this.page as Record<string, unknown>).goBack = this.origGoBack;\n (this.page as Record<string, unknown>).goForward = this.origGoForward;\n (this.page as Record<string, unknown>).reload = this.origReload;\n\n // Remove event listeners\n for (const { event, handler } of this.listeners) {\n this.page.off(event, handler);\n }\n this.listeners = [];\n this.records = [];\n this.pendingRequests.clear();\n this.capturedRequests = [];\n this.pendingBodyReads = [];\n this.requestCaptureCount = 0;\n }\n\n getRecords(): readonly NavigationRecord[] {\n return this.records;\n }\n\n private interceptMethods(): void {\n const self = this;\n const page = this.page;\n\n (page as Record<string, unknown>).goto = async function (url: string, options?: unknown) {\n self.recordNavigation(url, 'goto');\n return self.origGoto(url, options);\n };\n\n (page as Record<string, unknown>).goBack = async function (options?: unknown) {\n const result = await self.origGoBack(options);\n self.recordNavigation(page.url(), 'back');\n return result;\n };\n\n (page as Record<string, unknown>).goForward = async function (options?: unknown) {\n const result = await self.origGoForward(options);\n self.recordNavigation(page.url(), 'forward');\n return result;\n };\n\n (page as Record<string, unknown>).reload = async function (options?: unknown) {\n self.recordNavigation(page.url(), 'refresh');\n return self.origReload(options);\n };\n }\n\n private attachListeners(): void {\n // Track DOMContentLoaded for lifecycle timestamps\n const onDomContentLoaded = () => {\n this.lastDomContentLoaded = new Date().toISOString();\n if (this.records.length > 0) {\n this.records[this.records.length - 1].domContentLoadedAt = this.lastDomContentLoaded;\n }\n };\n this.page.on('domcontentloaded', onDomContentLoaded as (...args: unknown[]) => void);\n this.listeners.push({ event: 'domcontentloaded', handler: onDomContentLoaded as (...args: unknown[]) => void });\n\n // Track main frame navigation for non-intercepted navigations (link clicks, form submits)\n const onFrameNavigated = (frame: unknown) => {\n // Only track main frame\n try {\n const frameObj = frame as { url(): string; parentFrame(): unknown };\n if (typeof frameObj.parentFrame === 'function' && frameObj.parentFrame() !== null) {\n return; // Skip sub-frames\n }\n const url = frameObj.url();\n // Skip if we already recorded this URL in the last 50ms (from method interception)\n const lastRecord = this.records[this.records.length - 1];\n if (lastRecord) {\n const timeDiff = Date.now() - new Date(lastRecord.timestamp).getTime();\n if (timeDiff < 50 && lastRecord.url === url) {\n return; // Duplicate from method interception\n }\n }\n // This is an untracked navigation (link click, form submit, etc.)\n this.recordNavigation(url, 'navigation');\n } catch {\n // Ignore frame navigation errors\n }\n };\n this.page.on('framenavigated', onFrameNavigated);\n this.listeners.push({ event: 'framenavigated', handler: onFrameNavigated });\n\n // Track console messages from injected SPA detection script\n const onConsoleMessage = (msg: unknown) => {\n try {\n const msgObj = msg as { text(): string; type(): string };\n if (msgObj.type() !== 'debug') return;\n const text = msgObj.text();\n if (!text.startsWith('__testrelic_nav:')) return;\n const data = JSON.parse(text.slice('__testrelic_nav:'.length));\n if (data.type && data.url) {\n this.recordNavigation(data.url, data.type as NavigationType);\n }\n } catch {\n // Ignore parse errors\n }\n };\n this.page.on('console', onConsoleMessage);\n this.listeners.push({ event: 'console', handler: onConsoleMessage });\n\n // Network stats tracking + individual request capture\n if (this.includeNetworkStats) {\n const onRequest = (request: unknown) => {\n if (this.currentNetworkCounter) {\n this.currentNetworkCounter.totalRequests++;\n }\n // Capture individual request details\n if (this.requestCaptureCount >= MAX_REQUESTS_PER_TEST) return;\n this.requestCaptureCount++;\n try {\n const req = request as {\n url(): string;\n method(): string;\n resourceType(): string;\n headers(): Record<string, string>;\n postData(): string | null;\n };\n const reqId = String(this.requestIdCounter++);\n let postData = req.postData() ?? null;\n let postDataTruncated = false;\n if (postData !== null) {\n const result = truncateBody(postData, MAX_BODY_SIZE);\n postData = result.text;\n postDataTruncated = result.truncated;\n }\n const pending: PendingRequest = {\n url: req.url(),\n method: req.method(),\n resourceType: this.mapResourceType(req.resourceType()),\n headers: req.headers(),\n postData,\n postDataTruncated,\n startedAt: new Date().toISOString(),\n startTimeMs: Date.now(),\n };\n this.pendingRequests.set(reqId, pending);\n // Tag the request object with our ID for lookup in response/failed handlers\n (request as Record<string, unknown>).__testrelic_id = reqId;\n } catch {\n // Ignore request capture errors\n }\n };\n this.page.on('request', onRequest);\n this.listeners.push({ event: 'request', handler: onRequest });\n\n const onResponse = (response: unknown) => {\n try {\n const resp = response as {\n url(): string;\n status(): number;\n headers(): Record<string, string>;\n request(): { resourceType(): string; __testrelic_id?: string };\n body(): Promise<Buffer>;\n };\n // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n const status = resp.status();\n if (status >= 400) {\n this.currentNetworkCounter.failedRequests++;\n this.currentNetworkCounter.failedRequestUrls.push(status + ' ' + resp.url());\n }\n const contentLength = resp.headers()['content-length'];\n if (contentLength) {\n this.currentNetworkCounter.totalBytes += parseInt(contentLength, 10) || 0;\n }\n const resourceType = resp.request().resourceType();\n const typeKey = this.mapResourceType(resourceType);\n this.currentNetworkCounter.byType[typeKey]++;\n }\n // Individual request capture\n const reqId = (resp.request() as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!pending) return;\n this.pendingRequests.delete(reqId);\n const responseTimeMs = Date.now() - pending.startTimeMs;\n const respHeaders = resp.headers();\n const contentType = respHeaders['content-type'] ?? null;\n const responseSize = parseInt(respHeaders['content-length'] ?? '0', 10) || 0;\n const binary = contentType ? !isTextContentType(contentType) : false;\n // Read response body asynchronously\n const bodyPromise = (async () => {\n let responseBody: string | null = null;\n let responseBodyTruncated = false;\n if (!binary) {\n try {\n const buf = await resp.body();\n const text = buf.toString('utf-8');\n const result = truncateBody(text, MAX_BODY_SIZE);\n responseBody = result.text;\n responseBodyTruncated = result.truncated;\n } catch {\n // Body unavailable (e.g., redirect)\n }\n }\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: resp.status(),\n responseTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody,\n responseHeaders: respHeaders,\n contentType,\n responseSize,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated,\n isBinary: binary,\n error: null,\n };\n this.capturedRequests.push(captured);\n })();\n this.pendingBodyReads.push(bodyPromise);\n } catch {\n // Ignore response processing errors\n }\n };\n this.page.on('response', onResponse);\n this.listeners.push({ event: 'response', handler: onResponse });\n\n const onRequestFailed = (request: unknown) => {\n // Aggregate stats (existing logic)\n if (this.currentNetworkCounter) {\n this.currentNetworkCounter.failedRequests++;\n try {\n const req = request as { url(): string };\n this.currentNetworkCounter.failedRequestUrls.push('ERR ' + req.url());\n } catch {\n // Ignore\n }\n }\n // Individual request capture\n try {\n const req = request as { failure(): { errorText: string } | null; __testrelic_id?: string };\n const reqId = (req as Record<string, unknown>).__testrelic_id as string | undefined;\n if (!reqId) return;\n const pending = this.pendingRequests.get(reqId);\n if (!pending) return;\n this.pendingRequests.delete(reqId);\n const captured: CapturedNetworkRequest = {\n url: pending.url,\n method: pending.method,\n resourceType: pending.resourceType,\n statusCode: 0,\n responseTimeMs: Date.now() - pending.startTimeMs,\n startedAt: pending.startedAt,\n requestHeaders: pending.headers,\n requestBody: pending.postData,\n responseBody: null,\n responseHeaders: null,\n contentType: null,\n responseSize: 0,\n requestBodyTruncated: pending.postDataTruncated,\n responseBodyTruncated: false,\n isBinary: false,\n error: req.failure()?.errorText ?? 'Unknown error',\n };\n this.capturedRequests.push(captured);\n } catch {\n // Ignore\n }\n };\n this.page.on('requestfailed', onRequestFailed);\n this.listeners.push({ event: 'requestfailed', handler: onRequestFailed });\n }\n }\n\n private async injectSPADetection(): Promise<void> {\n try {\n await this.page.addInitScript(() => {\n const origPush = history.pushState.bind(history);\n const origReplace = history.replaceState.bind(history);\n\n history.pushState = function (...args: Parameters<typeof origPush>) {\n origPush(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_route',\n url: location.href,\n }));\n };\n\n history.replaceState = function (...args: Parameters<typeof origReplace>) {\n origReplace(...args);\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'spa_replace',\n url: location.href,\n }));\n };\n\n window.addEventListener('popstate', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'popstate',\n url: location.href,\n }));\n });\n\n window.addEventListener('hashchange', () => {\n // eslint-disable-next-line no-console\n console.debug('__testrelic_nav:' + JSON.stringify({\n type: 'hash_change',\n url: location.href,\n }));\n });\n });\n } catch {\n // Ignore injection errors (page may be closed)\n }\n }\n\n private recordNavigation(url: string, type: NavigationType): void {\n // Finalize network stats for the previous navigation\n if (this.includeNetworkStats && this.currentNetworkCounter && this.records.length > 0) {\n this.records[this.records.length - 1].networkStats = {\n totalRequests: this.currentNetworkCounter.totalRequests,\n failedRequests: this.currentNetworkCounter.failedRequests,\n failedRequestUrls: [...this.currentNetworkCounter.failedRequestUrls],\n totalBytes: this.currentNetworkCounter.totalBytes,\n byType: { ...this.currentNetworkCounter.byType },\n };\n }\n\n this.records.push({\n url,\n navigationType: type,\n timestamp: new Date().toISOString(),\n });\n\n // Start fresh counter for this navigation\n if (this.includeNetworkStats) {\n this.currentNetworkCounter = this.createNetworkCounter();\n }\n }\n\n private createNetworkCounter(): NetworkCounter {\n return {\n totalRequests: 0,\n failedRequests: 0,\n failedRequestUrls: [],\n totalBytes: 0,\n byType: { xhr: 0, document: 0, script: 0, stylesheet: 0, image: 0, font: 0, other: 0 },\n };\n }\n\n private mapResourceType(type: string): keyof ResourceBreakdown {\n switch (type) {\n case 'xhr':\n case 'fetch':\n return 'xhr';\n case 'document':\n return 'document';\n case 'script':\n return 'script';\n case 'stylesheet':\n return 'stylesheet';\n case 'image':\n return 'image';\n case 'font':\n return 'font';\n default:\n return 'other';\n }\n }\n}\n","/**\n * AssertionTracker — Captures and links assertions to API calls\n *\n * Records every assertion made on API response data (both passing and failing),\n * links each to the originating API call by call ID, and flushes the data to\n * testInfo.annotations for the reporter to consume.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { ApiAssertion, AssertionType, AssertionLocation } from '@testrelic/core';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Symbol used to tag APIResponse objects with their call ID. */\nexport const CALL_ID_SYMBOL: unique symbol = Symbol.for('__testrelic_call_id');\n\n/** Annotation type for assertion data transport (fixture → reporter). */\nexport const ASSERTION_ANNOTATION_TYPE = '__testrelic_api_assertions';\n\n/** WeakMap to track call IDs for object values extracted from responses. */\nexport const valueCallIdMap = new WeakMap<object, string>();\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// AssertionTracker\n// ---------------------------------------------------------------------------\n\nexport class AssertionTracker {\n private assertions: ApiAssertion[] = [];\n private currentCallId: string | null = null;\n\n /** Record a captured assertion. */\n recordAssertion(assertion: ApiAssertion): void {\n this.assertions.push(assertion);\n }\n\n /** Get all recorded assertions. */\n getAssertions(): readonly ApiAssertion[] {\n return this.assertions;\n }\n\n /** Set the most recent API call ID for temporal fallback linking. */\n setCurrentCallId(callId: string): void {\n this.currentCallId = callId;\n }\n\n /** Get the most recent API call ID. */\n getCurrentCallId(): string | null {\n return this.currentCallId;\n }\n\n /** Return assertion data for consolidated payload. */\n getData(): ApiAssertion[] {\n return [...this.assertions];\n }\n\n /** Flush assertion data to testInfo annotations (legacy). */\n flushLegacyAnnotations(testInfo: TestInfoLike): void {\n if (this.assertions.length === 0) return;\n\n testInfo.annotations.push({\n type: ASSERTION_ANNOTATION_TYPE,\n description: JSON.stringify(this.assertions),\n });\n }\n\n /** Clear all state for garbage collection. */\n dispose(): void {\n this.assertions = [];\n this.currentCallId = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Location Capture\n// ---------------------------------------------------------------------------\n\n/** Cache for source file lines to avoid repeated reads. */\nconst sourceCache = new Map<string, string[]>();\n\nfunction getSourceLines(filePath: string): string[] | null {\n const cached = sourceCache.get(filePath);\n if (cached) return cached;\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.split('\\n');\n sourceCache.set(filePath, lines);\n return lines;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the call stack to find the assertion location in user test code.\n * Returns the first stack frame that is NOT inside the SDK's own source files.\n */\nfunction captureAssertionLocation(): AssertionLocation | null {\n const err = new Error();\n // SAFETY: Error.stack is V8-specific but guaranteed on Node.js 18+\n const stack = err.stack;\n if (!stack) return null;\n\n const lines = stack.split('\\n');\n for (const line of lines) {\n // Skip frames from the SDK's own assertion capture internals\n if (line.includes('/assertion-tracker.') ||\n line.includes('/api-request-tracker.') ||\n line.includes('node:internal') ||\n line.includes(' at new Error') ||\n line.includes(' at captureAssertionLocation') ||\n line.includes(' at expectWrapper') ||\n line.includes(' at wrappedMatcher')) {\n continue;\n }\n\n // Match V8 stack frame: \" at functionName (file:line:column)\"\n // or \" at file:line:column\"\n const match = line.match(/at\\s+(?:.*?\\s+\\()?(.+?):(\\d+):(\\d+)\\)?$/);\n if (match) {\n return {\n file: match[1],\n line: parseInt(match[2], 10),\n column: parseInt(match[3], 10),\n };\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Assertion Type Detection\n// ---------------------------------------------------------------------------\n\n/**\n * Infer the assertion type from the source expression and the value being asserted.\n */\nfunction inferAssertionType(\n expression: string | undefined,\n // SAFETY: value is the raw asserted value from user code — may be any type\n _value: unknown,\n): AssertionType {\n if (!expression) return 'custom';\n\n const expr = expression.toLowerCase();\n\n if (expr.includes('.status()') && !expr.includes('.statustext()')) {\n return expr.includes('.ok()') ? 'statusOk' : 'status';\n }\n if (expr.includes('.ok()')) return 'statusOk';\n if (expr.includes('.headers()') || expr.includes('.headersarray()')) return 'header';\n if (expr.includes('tomatchobject') || expr.includes('toequal')) return 'bodyMatch';\n if (expr.includes('tocontain') || expr.includes('tostringcontaining')) return 'bodyContains';\n\n // If expression references json() or text() it's a body field\n if (expr.includes('.json()') || expr.includes('.text()')) return 'bodyField';\n\n // If none of the above match, default to bodyField for parsed data, or custom\n return 'custom';\n}\n\n// ---------------------------------------------------------------------------\n// Assertion-Aware Expect Wrapper\n// ---------------------------------------------------------------------------\n\n/** Minimal interface for ApiRequestTracker to avoid circular imports. */\ninterface ApiTrackerLike {\n readonly lastCallId: string | null;\n getCallIdForValue(value: unknown): string | null;\n}\n\n/**\n * Creates a wrapped `expect` that captures assertion metadata and records it\n * via the AssertionTracker. Preserves all original Playwright expect behavior.\n */\nexport function createAssertionAwareExpect(\n tracker: AssertionTracker,\n apiTracker: ApiTrackerLike,\n // SAFETY: originalExpect is the Playwright expect — typed as Function to avoid\n // importing @playwright/test in this module's runtime\n originalExpect: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => unknown {\n const wrappedExpect = function expectWrapper(received: unknown, ...rest: unknown[]): unknown {\n // Determine call ID from the value being asserted\n let callId: string | null = null;\n\n // Strategy 1: Check if the value itself is tagged (response object)\n if (received !== null && received !== undefined && typeof received === 'object') {\n const tagged = (received as Record<symbol, string>)[CALL_ID_SYMBOL];\n if (typeof tagged === 'string') {\n callId = tagged;\n }\n }\n\n // Strategy 2: Check if value is in the WeakMap (extracted from response)\n if (!callId && received !== null && received !== undefined && typeof received === 'object') {\n callId = valueCallIdMap.get(received as object) ?? null;\n }\n\n // Strategy 3: Check via the API tracker's value-to-callId mapping\n if (!callId) {\n callId = apiTracker.getCallIdForValue(received);\n }\n\n // Strategy 4: Temporal fallback — use the most recent API call\n if (!callId) {\n callId = tracker.getCurrentCallId();\n }\n\n // If no call ID found, this assertion is not related to an API call — skip capture\n if (!callId) {\n return (originalExpect as Function)(received, ...rest);\n }\n\n // Capture location before the assertion runs\n const location = captureAssertionLocation();\n\n // Read the source expression (best-effort)\n let expression: string | undefined;\n if (location) {\n const lines = getSourceLines(location.file);\n if (lines && location.line > 0 && location.line <= lines.length) {\n expression = lines[location.line - 1].trim();\n }\n }\n\n // Get the matchers object from the original expect\n const matchers = (originalExpect as Function)(received, ...rest);\n\n // If no callId at this point, just return the matchers directly\n if (!callId) return matchers;\n\n // Wrap the matchers to intercept assertion calls\n return createMatcherProxy(matchers as object, {\n tracker,\n callId,\n received,\n location: location ?? { file: 'unknown', line: 0 },\n expression,\n });\n };\n\n // Copy static methods from originalExpect (e.g., expect.soft, expect.configure, etc.)\n for (const key of Object.keys(originalExpect as object)) {\n (wrappedExpect as Record<string, unknown>)[key] =\n (originalExpect as Record<string, unknown>)[key];\n }\n\n return wrappedExpect;\n}\n\n// ---------------------------------------------------------------------------\n// Matcher Proxy\n// ---------------------------------------------------------------------------\n\ninterface MatcherContext {\n tracker: AssertionTracker;\n callId: string;\n received: unknown;\n location: AssertionLocation;\n expression: string | undefined;\n}\n\n/**\n * Creates a Proxy around the matchers object returned by expect().\n * Intercepts matcher method calls (toBe, toEqual, etc.) to record assertions.\n */\nfunction createMatcherProxy(matchers: object, ctx: MatcherContext): object {\n return new Proxy(matchers, {\n get(target: object, prop: string | symbol, receiver: unknown): unknown {\n const value = Reflect.get(target, prop, receiver);\n\n // Only intercept function calls (matcher methods)\n if (typeof value !== 'function') return value;\n\n // Skip internal Playwright properties\n if (typeof prop === 'symbol') return value;\n\n // Handle .not — return a new proxy with negation\n if (prop === 'not') {\n const negated = value;\n return createMatcherProxy(\n negated as object,\n ctx,\n );\n }\n\n // Wrap the matcher function to capture the assertion\n return function wrappedMatcher(this: unknown, ...args: unknown[]): unknown {\n const expected = args[0];\n let status: 'passed' | 'failed' = 'passed';\n let actual: unknown = ctx.received;\n let thrownError: unknown = null;\n\n try {\n const result = (value as Function).apply(this ?? target, args);\n\n // If result is a Promise (async matchers), handle it\n if (result && typeof result === 'object' && typeof (result as { then?: unknown }).then === 'function') {\n return (result as Promise<unknown>).then(\n () => {\n recordAssertionResult(ctx, prop, expected, actual, 'passed');\n },\n (err: unknown) => {\n recordAssertionResult(ctx, prop, expected, actual, 'failed');\n throw err;\n },\n );\n }\n\n return result;\n } catch (err: unknown) {\n status = 'failed';\n thrownError = err;\n\n // Try to extract actual value from the error message\n if (err instanceof Error && err.message) {\n const receivedMatch = err.message.match(/Received:\\s*(.+)/);\n if (receivedMatch) {\n actual = receivedMatch[1];\n }\n }\n\n throw err;\n } finally {\n // Only record sync assertions here; async assertions are handled above\n if (thrownError !== null || status === 'passed') {\n recordAssertionResult(ctx, prop, expected, actual, status);\n }\n }\n };\n },\n });\n}\n\nfunction recordAssertionResult(\n ctx: MatcherContext,\n matcherName: string,\n expected: unknown,\n actual: unknown,\n status: 'passed' | 'failed',\n): void {\n const assertionType = inferAssertionType(ctx.expression, ctx.received);\n\n const assertion: ApiAssertion = {\n callId: ctx.callId,\n type: assertionType,\n expected: safeSerializable(expected),\n actual: safeSerializable(actual),\n status,\n location: ctx.location,\n expression: ctx.expression,\n };\n\n ctx.tracker.recordAssertion(assertion);\n}\n\n/**\n * Ensure a value is safely serializable to JSON.\n * Converts non-serializable values to string representations.\n */\nfunction safeSerializable(value: unknown): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;\n try {\n JSON.stringify(value);\n return value;\n } catch {\n return String(value);\n }\n}\n","/**\n * API data redaction — header values and recursive body field redaction.\n *\n * Separate from redaction.ts (pattern-based string redaction).\n * This module operates on structured data: header name→value pairs\n * and JSON object field names at any nesting depth.\n */\n\nconst REDACTED = '[REDACTED]';\n\n/**\n * Redact header values by name (case-insensitive).\n * Returns a new object with matching header values replaced by \"[REDACTED]\".\n */\nexport function redactHeaders(\n headers: Record<string, string> | null,\n redactList: readonly string[],\n): Record<string, string> | null {\n if (headers === null || redactList.length === 0) return headers;\n\n const lowerList = new Set(redactList.map((h) => h.toLowerCase()));\n const result: Record<string, string> = {};\n\n for (const key of Object.keys(headers)) {\n if (Object.hasOwn(headers, key)) {\n result[key] = lowerList.has(key.toLowerCase()) ? REDACTED : headers[key];\n }\n }\n\n return result;\n}\n\n/**\n * Redact body field values by name (recursive, any depth).\n * Parses JSON, walks object/array, replaces matching field values with \"[REDACTED]\".\n * Non-JSON bodies are returned as-is.\n */\nexport function redactBodyFields(\n body: string | null,\n redactList: readonly string[],\n): string | null {\n if (body === null || redactList.length === 0) return body;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // Not JSON — return as-is (FR-019)\n return body;\n }\n\n if (typeof parsed !== 'object' || parsed === null) return body;\n\n const fieldSet = new Set(redactList);\n const redacted = walkAndRedact(parsed, fieldSet);\n return JSON.stringify(redacted);\n}\n\n/**\n * Recursively walk an object/array and redact matching field values.\n */\nfunction walkAndRedact(value: unknown, fields: Set<string>): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => walkAndRedact(item, fields));\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (!Object.hasOwn(value, key)) continue;\n const val = (value as Record<string, unknown>)[key];\n if (fields.has(key)) {\n result[key] = REDACTED;\n } else {\n result[key] = walkAndRedact(val, fields);\n }\n }\n return result;\n }\n\n return value;\n}\n","/**\n * API URL filtering — include/exclude patterns for API call tracking.\n *\n * Supports glob strings and RegExp objects. Exclude takes precedence over include.\n */\n\n/**\n * Determines whether an API call URL should be tracked.\n *\n * - No filters → track all\n * - Include set → URL must match at least one include pattern\n * - Exclude set → URL must not match any exclude pattern\n * - Both set → exclude takes precedence\n */\nexport function shouldTrackApiUrl(\n url: string,\n include: readonly (string | RegExp)[],\n exclude: readonly (string | RegExp)[],\n): boolean {\n // Check exclude first (takes precedence)\n if (exclude.length > 0) {\n for (const pattern of exclude) {\n if (matchesPattern(url, pattern)) return false;\n }\n }\n\n // Check include\n if (include.length > 0) {\n for (const pattern of include) {\n if (matchesPattern(url, pattern)) return true;\n }\n return false; // Include set but no match → don't track\n }\n\n return true; // No filters → track all\n}\n\n/**\n * Test a URL against a single pattern (glob string or RegExp).\n */\nfunction matchesPattern(url: string, pattern: string | RegExp): boolean {\n try {\n if (pattern instanceof RegExp) {\n return pattern.test(url);\n }\n // Convert glob to regex\n const regex = globToRegex(pattern);\n return regex.test(url);\n } catch {\n // Invalid pattern — skip with warning\n console.warn(`[testrelic] Invalid URL filter pattern: ${String(pattern)}`);\n return false;\n }\n}\n\n/**\n * Convert a glob pattern to a RegExp.\n * Handles `**` (match anything including /) and `*` (match anything except /).\n */\nfunction globToRegex(glob: string): RegExp {\n let result = '';\n let i = 0;\n while (i < glob.length) {\n const char = glob[i];\n if (char === '*' && glob[i + 1] === '*') {\n result += '.*';\n i += 2;\n // Skip optional trailing /\n if (glob[i] === '/') i++;\n } else if (char === '*') {\n result += '[^/]*';\n i++;\n } else if (char === '?') {\n result += '[^/]';\n i++;\n } else if ('.+^${}()|[]\\\\'.includes(char)) {\n result += '\\\\' + char;\n i++;\n } else {\n result += char;\n i++;\n }\n }\n return new RegExp(result);\n}\n","/**\n * ApiRequestTracker — Proxy wrapper for Playwright's APIRequestContext\n *\n * Intercepts all HTTP method calls (.get, .post, .put, .patch, .delete,\n * .head, .fetch, .dispose) to record detailed API call analytics.\n * Data is flushed to testInfo.annotations for the reporter to consume.\n */\n\nimport { performance } from 'node:perf_hooks';\nimport type { APIRequestContext, APIResponse } from '@playwright/test';\nimport type { ApiCallRecord } from '@testrelic/core';\nimport { CALL_ID_SYMBOL, valueCallIdMap } from './assertion-tracker.js';\nimport type { AssertionTracker } from './assertion-tracker.js';\nimport type { ResolvedApiConfig } from './config.js';\nimport { redactHeaders, redactBodyFields } from './api-redactor.js';\nimport { shouldTrackApiUrl } from './api-url-filter.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'fetch'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nconst TEXT_CONTENT_TYPES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\nconst ANNOTATION_TYPE = '__testrelic_api_calls';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isTextContentType(contentType: string): boolean {\n const lower = contentType.toLowerCase();\n return TEXT_CONTENT_TYPES.some((prefix) => lower.includes(prefix));\n}\n\n/**\n * Extract request body from Playwright request options.\n * Handles `data` (string/object/Buffer), `form` (key-value), and\n * `multipart` (field descriptors) cases.\n */\nfunction extractRequestBody(options?: Record<string, unknown> | null): string | null {\n if (!options) return null;\n\n if (options.data !== undefined && options.data !== null) {\n const data = options.data;\n if (typeof data === 'string') return data;\n if (Buffer.isBuffer(data)) return data.toString('base64');\n return JSON.stringify(data);\n }\n\n if (options.form !== undefined && options.form !== null) {\n return JSON.stringify(options.form);\n }\n\n if (options.multipart !== undefined && options.multipart !== null) {\n // Capture field names and metadata, not full binary contents\n const multipart = options.multipart as Record<string, unknown>;\n const descriptor: Record<string, string> = {};\n for (const [key, value] of Object.entries(multipart)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n descriptor[key] = String(value);\n } else if (value && typeof value === 'object' && 'name' in value) {\n descriptor[key] = `[file: ${(value as { name: string }).name}]`;\n } else {\n descriptor[key] = '[binary]';\n }\n }\n return JSON.stringify(descriptor);\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// TestInfo-like interface (avoids importing Playwright test internals)\n// ---------------------------------------------------------------------------\n\ninterface TestInfoLike {\n annotations: Array<{ type: string; description?: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// ApiRequestTracker\n// ---------------------------------------------------------------------------\n\nexport class ApiRequestTracker {\n private readonly context: APIRequestContext;\n private readonly originals: Map<string, (...args: never[]) => unknown> = new Map();\n private capturedCalls: ApiCallRecord[] = [];\n private callCounter = 0;\n private disposed = false;\n private readonly assertionTracker: AssertionTracker | null;\n private readonly apiConfig: ResolvedApiConfig;\n private _lastCallId: string | null = null;\n\n /** Map of primitive values to their originating call IDs (for status, ok, etc.). */\n private readonly primitiveCallIds = new Map<unknown, string>();\n\n /** Default API config when none is provided (all capture enabled, default redaction). */\n private static readonly DEFAULT_API_CONFIG: ResolvedApiConfig = Object.freeze({\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n });\n\n constructor(context: APIRequestContext, assertionTracker?: AssertionTracker, apiConfig?: ResolvedApiConfig) {\n this.context = context;\n this.assertionTracker = assertionTracker ?? null;\n this.apiConfig = apiConfig ?? ApiRequestTracker.DEFAULT_API_CONFIG;\n }\n\n /** The most recent API call ID (for temporal fallback linking). */\n get lastCallId(): string | null {\n return this._lastCallId;\n }\n\n /**\n * Get the call ID for a value extracted from a response.\n * Works for objects (via WeakMap) and primitives (via internal Map).\n */\n getCallIdForValue(value: unknown): string | null {\n if (value !== null && value !== undefined && typeof value === 'object') {\n return valueCallIdMap.get(value as object) ?? null;\n }\n return this.primitiveCallIds.get(value) ?? null;\n }\n\n /** Replace HTTP methods and dispose on the context with instrumented wrappers. */\n intercept(): void {\n for (const method of HTTP_METHODS) {\n const original = (this.context[method] as (...args: never[]) => unknown).bind(this.context);\n this.originals.set(method, original);\n (this.context as unknown as Record<string, unknown>)[method] = this.createWrapper(method, original);\n }\n\n // Intercept dispose\n const origDispose = this.context.dispose.bind(this.context);\n this.originals.set('dispose', origDispose as (...args: never[]) => unknown);\n (this.context as unknown as Record<string, unknown>).dispose = async (\n options?: { reason?: string },\n ): Promise<void> => {\n this.disposed = true;\n return origDispose(options);\n };\n }\n\n /** Return captured API call records for consolidated payload. */\n getData(): ApiCallRecord[] {\n return [...this.capturedCalls];\n }\n\n /** Flush captured API call records to testInfo annotations (legacy). */\n flushLegacyAnnotations(testInfo: TestInfoLike): void {\n if (this.capturedCalls.length === 0) return;\n\n testInfo.annotations.push({\n type: ANNOTATION_TYPE,\n description: JSON.stringify(this.capturedCalls),\n });\n }\n\n /** Restore original methods and clear captured data. */\n dispose(): void {\n for (const [method, original] of this.originals) {\n (this.context as unknown as Record<string, unknown>)[method] = original;\n }\n this.originals.clear();\n this.capturedCalls = [];\n this.callCounter = 0;\n this._lastCallId = null;\n this.primitiveCallIds.clear();\n }\n\n /** Whether the underlying context has been disposed. */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /** Access captured calls (for testing). */\n getCapturedCalls(): readonly ApiCallRecord[] {\n return this.capturedCalls;\n }\n\n /**\n * Wraps response methods to tag extracted values with the originating call ID.\n * This enables assertion linking for values extracted from responses.\n */\n private tagResponseMethods(response: APIResponse, callId: string): void {\n const self = this;\n\n // Wrap methods that return objects (headers, json)\n const origHeaders = response.headers.bind(response);\n (response as unknown as Record<string, unknown>).headers = function taggedHeaders(): Record<string, string> {\n const result = origHeaders();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origHeadersArray = response.headersArray.bind(response);\n (response as unknown as Record<string, unknown>).headersArray = function taggedHeadersArray(): Array<{ name: string; value: string }> {\n const result = origHeadersArray();\n valueCallIdMap.set(result, callId);\n return result;\n };\n\n const origJson = response.json.bind(response);\n (response as unknown as Record<string, unknown>).json = async function taggedJson(): Promise<unknown> {\n const result = await origJson();\n if (result !== null && result !== undefined && typeof result === 'object') {\n valueCallIdMap.set(result as object, callId);\n }\n return result;\n };\n\n // Wrap methods that return primitives — track in primitiveCallIds\n const origStatus = response.status.bind(response);\n (response as unknown as Record<string, unknown>).status = function taggedStatus(): number {\n const result = origStatus();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origStatusText = response.statusText.bind(response);\n (response as unknown as Record<string, unknown>).statusText = function taggedStatusText(): string {\n const result = origStatusText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origOk = response.ok.bind(response);\n (response as unknown as Record<string, unknown>).ok = function taggedOk(): boolean {\n const result = origOk();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origText = response.text.bind(response);\n (response as unknown as Record<string, unknown>).text = async function taggedText(): Promise<string> {\n const result = await origText();\n self.primitiveCallIds.set(result, callId);\n return result;\n };\n\n const origBody = response.body.bind(response);\n (response as unknown as Record<string, unknown>).body = async function taggedBody(): Promise<Buffer> {\n const result = await origBody();\n valueCallIdMap.set(result, callId);\n return result;\n };\n }\n\n private createWrapper(\n method: HttpMethod,\n original: (...args: never[]) => unknown,\n ): (url: string, options?: Record<string, unknown>) => Promise<APIResponse> {\n const self = this;\n\n return async function wrappedMethod(\n url: string,\n options?: Record<string, unknown>,\n ): Promise<APIResponse> {\n // URL filtering: skip tracking if URL doesn't match include/exclude patterns\n if (!shouldTrackApiUrl(url, self.apiConfig.apiIncludeUrls, self.apiConfig.apiExcludeUrls)) {\n return (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(url, options);\n }\n\n const id = `api-call-${self.callCounter++}`;\n const timestamp = new Date().toISOString();\n const httpMethod = method === 'fetch'\n ? ((options?.method as string) ?? 'GET').toUpperCase()\n : method.toUpperCase();\n const requestBody = extractRequestBody(options);\n const startTime = performance.now();\n\n // Update temporal tracking for assertion linking\n self._lastCallId = id;\n if (self.assertionTracker) {\n self.assertionTracker.setCurrentCallId(id);\n }\n\n let response: APIResponse;\n try {\n response = await (original as (url: string, options?: Record<string, unknown>) => Promise<APIResponse>)(\n url,\n options,\n );\n } catch (err: unknown) {\n // Error path: capture partial record, re-throw\n const endTime = performance.now();\n try {\n const errorRequestBody = self.apiConfig.captureRequestBody\n ? redactBodyFields(requestBody, self.apiConfig.redactBodyFields)\n : null;\n const errorRecord: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url,\n requestHeaders: null,\n requestBody: errorRequestBody,\n responseStatusCode: null,\n responseStatusText: null,\n responseHeaders: null,\n responseBody: null,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: false,\n error: err instanceof Error ? err.message : String(err),\n };\n self.capturedCalls.push(errorRecord);\n } catch {\n // FR-016: if recording fails, still re-throw the original exception\n }\n throw err;\n }\n\n // Success path: capture full record with config-driven capture control and redaction\n try {\n const endTime = performance.now();\n const rawResponseHeaders = response.headers();\n const contentType = rawResponseHeaders['content-type'] ?? null;\n const binary = contentType ? !isTextContentType(contentType) : false;\n\n // Capture control: headers\n let capturedRequestHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureRequestHeaders && options?.headers) {\n capturedRequestHeaders = options.headers as Record<string, string>;\n }\n let capturedResponseHeaders: Record<string, string> | null = null;\n if (self.apiConfig.captureResponseHeaders) {\n capturedResponseHeaders = rawResponseHeaders;\n }\n\n // Capture control: bodies\n let capturedRequestBody: string | null = self.apiConfig.captureRequestBody ? requestBody : null;\n let capturedResponseBody: string | null = null;\n if (self.apiConfig.captureResponseBody) {\n try {\n if (binary) {\n const buf = await response.body();\n capturedResponseBody = buf.toString('base64');\n } else {\n capturedResponseBody = await response.text();\n }\n } catch {\n // Body read failed — record what we have\n }\n }\n\n // Apply redaction to captured data (FR-006: before storage)\n capturedRequestHeaders = redactHeaders(capturedRequestHeaders, self.apiConfig.redactHeaders);\n capturedResponseHeaders = redactHeaders(capturedResponseHeaders, self.apiConfig.redactHeaders);\n capturedRequestBody = redactBodyFields(capturedRequestBody, self.apiConfig.redactBodyFields);\n capturedResponseBody = redactBodyFields(capturedResponseBody, self.apiConfig.redactBodyFields);\n\n const record: ApiCallRecord = {\n id,\n timestamp,\n method: httpMethod,\n url: response.url(),\n requestHeaders: capturedRequestHeaders,\n requestBody: capturedRequestBody,\n responseStatusCode: response.status(),\n responseStatusText: response.statusText(),\n responseHeaders: capturedResponseHeaders,\n responseBody: capturedResponseBody,\n responseTimeMs: Math.round((endTime - startTime) * 100) / 100,\n isBinary: binary,\n error: null,\n };\n self.capturedCalls.push(record);\n } catch {\n // FR-016: never crash the test — silently skip recording\n }\n\n // Tag the response with the call ID for assertion linking\n try {\n (response as unknown as Record<symbol, string>)[CALL_ID_SYMBOL] = id;\n self.tagResponseMethods(response, id);\n } catch {\n // Tagging failure is non-critical — temporal fallback will be used\n }\n\n return response;\n };\n }\n}\n","/**\n * @testrelic/playwright-analytics/fixture\n *\n * Extended Playwright test fixture that wraps the default `page` fixture\n * to automatically track navigation events and API requests.\n *\n * Exports:\n * - testRelicFixture: Fixture object for use with base.extend()\n * - test: Pre-extended test instance (backward compatibility)\n * - expect: Re-exported from @playwright/test (backward compatibility)\n */\n\nimport { test as base, expect as playwrightExpect } from '@playwright/test';\nimport { NavigationTracker } from './navigation-tracker.js';\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker, createAssertionAwareExpect } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicDataPayload } from '@testrelic/core';\nimport type { ResolvedApiConfig } from './config.js';\n\nexport { playwrightExpect as expect };\n\n/** Annotation type for API config (reporter → fixture). */\nconst API_CONFIG_ANNOTATION = '__testrelic_api_config';\n\n/** Legacy annotation type for backward compatibility. */\nconst LEGACY_TRACK_API_CALLS_ANNOTATION = '__testrelic_config_trackApiCalls';\n\n/**\n * Reads API config from testInfo annotations pushed by the reporter.\n * Returns a default config if no annotation is found (FR-013).\n */\nexport function readApiConfig(testInfo: { annotations: Array<{ type: string; description?: string }> }): ResolvedApiConfig {\n // Try new unified annotation first\n const configAnnotation = testInfo.annotations.find(\n (a) => a.type === API_CONFIG_ANNOTATION && a.description !== undefined,\n );\n if (configAnnotation) {\n try {\n const parsed = JSON.parse(configAnnotation.description!, deserializeRegExp);\n return parsed as ResolvedApiConfig;\n } catch {\n // Fall through to defaults\n }\n }\n\n // Backward compatibility: check legacy trackApiCalls annotation\n const legacyAnnotation = testInfo.annotations.find(\n (a) => a.type === LEGACY_TRACK_API_CALLS_ANNOTATION && a.description !== undefined,\n );\n if (legacyAnnotation) {\n return {\n trackApiCalls: legacyAnnotation.description !== 'false',\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n }\n\n // No annotation found — return sensible defaults (FR-013)\n return {\n trackApiCalls: true,\n captureRequestHeaders: true,\n captureResponseHeaders: true,\n captureRequestBody: true,\n captureResponseBody: true,\n captureAssertions: true,\n redactHeaders: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],\n redactBodyFields: ['password', 'secret', 'token', 'apiKey', 'api_key'],\n apiIncludeUrls: [],\n apiExcludeUrls: [],\n };\n}\n\n/** JSON reviver that reconstructs serialized RegExp objects. */\nfunction deserializeRegExp(_key: string, value: unknown): unknown {\n if (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>).__regexp === true &&\n typeof (value as Record<string, unknown>).source === 'string'\n ) {\n const { source, flags } = value as { source: string; flags: string };\n return new RegExp(source, flags);\n }\n return value;\n}\n\n/**\n * Playwright-compatible fixture object for extending `test.extend()`.\n * Provides `page` (with navigation tracking) and `request` (with API tracking).\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicFixture);\n */\nexport const testRelicFixture = {\n page: async (\n { page }: { page: import('@playwright/test').Page },\n use: (page: import('@playwright/test').Page) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const tracker = new NavigationTracker(page as never);\n try {\n await tracker.init();\n } catch {\n // Graceful degradation: continue without SPA detection\n }\n\n await use(page);\n\n try {\n const { navigations, networkRequests } = await tracker.getData();\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations,\n networkRequests,\n apiCalls: [],\n apiAssertions: [],\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // FR-009: fixture without reporter is harmless\n }\n try {\n await tracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n tracker.dispose();\n },\n\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n\n await use(request);\n\n try {\n const apiCalls = apiTracker.getData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations: [],\n networkRequests: [],\n apiCalls,\n apiAssertions,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n try {\n apiTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n try {\n assertionTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n\n/** Pre-extended test instance for backward compatibility. */\nexport const test = base.extend(testRelicFixture);\n","/**\n * @testrelic/playwright-analytics/api-fixture\n *\n * Standalone API-only fixture for pure API test suites.\n * Wraps only the Playwright `request` fixture with the API request tracker\n * and assertion tracker. Does NOT import any navigation or browser-related code.\n *\n * @example\n * import { test as base } from '\\@playwright/test';\n * import { testRelicApiFixture } from '\\@testrelic/playwright-analytics';\n * export const test = base.extend(testRelicApiFixture);\n */\n\nimport { ApiRequestTracker } from './api-request-tracker.js';\nimport { AssertionTracker } from './assertion-tracker.js';\nimport { PAYLOAD_VERSION, ATTACHMENT_NAME, ATTACHMENT_CONTENT_TYPE } from '@testrelic/core';\nimport type { TestRelicDataPayload } from '@testrelic/core';\nimport { readApiConfig } from './fixture.js';\n\n/**\n * Playwright-compatible fixture object for API-only test suites.\n * Provides only `request` (with API tracking and assertion capture). No browser dependency.\n */\nexport const testRelicApiFixture = {\n request: async (\n { request }: { request: import('@playwright/test').APIRequestContext },\n use: (request: import('@playwright/test').APIRequestContext) => Promise<void>,\n testInfo: import('@playwright/test').TestInfo,\n ): Promise<void> => {\n const apiConfig = readApiConfig(testInfo);\n\n if (!apiConfig.trackApiCalls) {\n await use(request);\n return;\n }\n\n const assertionTracker = new AssertionTracker();\n const apiTracker = new ApiRequestTracker(request, assertionTracker, apiConfig);\n apiTracker.intercept();\n\n await use(request);\n\n try {\n const apiCalls = apiTracker.getData();\n const apiAssertions = apiConfig.captureAssertions ? assertionTracker.getData() : [];\n const payload: TestRelicDataPayload = {\n testRelicData: true,\n version: PAYLOAD_VERSION,\n navigations: [],\n networkRequests: [],\n apiCalls,\n apiAssertions,\n };\n await testInfo.attach(ATTACHMENT_NAME, {\n body: Buffer.from(JSON.stringify(payload)),\n contentType: ATTACHMENT_CONTENT_TYPE,\n });\n } catch {\n // Graceful degradation: fixture without reporter is harmless\n }\n try {\n apiTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n try {\n assertionTracker.flushLegacyAnnotations(testInfo);\n } catch {\n // Backward compatibility: legacy annotations for older reporters\n }\n apiTracker.dispose();\n assertionTracker.dispose();\n },\n};\n","/**\n * @testrelic/playwright-analytics — Main entry point\n *\n * Default export: TestRelicReporter class\n * Named export: recordNavigation helper\n */\n\nexport { default } from './reporter.js';\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n CIMetadata,\n TimelineEntry,\n TestResult,\n FailureDiagnostic,\n NetworkStats,\n ReporterConfig,\n ApiCallRecord,\n ApiAssertion,\n AssertionType,\n AssertionLocation,\n StepTestIdentity,\n ApiCallStepRequest,\n ApiCallStepResponse,\n StepAssertion,\n NavigationStep,\n ApiCallStep,\n TimelineStep,\n TestRelicDataPayload,\n} from './types.js';\n\nexport {\n PAYLOAD_VERSION,\n ATTACHMENT_NAME,\n ATTACHMENT_CONTENT_TYPE,\n isTestRelicDataPayload,\n} from './types.js';\n\nexport { SCHEMA_VERSION } from './schema.js';\n\nexport { testRelicFixture } from './fixture.js';\nexport { testRelicApiFixture } from './api-fixture.js';\n\nimport type { NavigationType, NavigationAnnotation } from '@testrelic/core';\n\nlet warned = false;\n\n/**\n * Records a manual navigation event in the current test's annotations.\n * Use for navigation the auto-tracker cannot detect (iframes, web workers).\n *\n * Requires access to the current test info. If not available, logs a\n * warning once and no-ops.\n */\nexport function recordNavigation(\n testInfo: { annotations: Array<{ type: string; description?: string }> } | null | undefined,\n url: string,\n navigationType: NavigationType = 'manual_record',\n): void {\n if (!testInfo || !testInfo.annotations) {\n if (!warned) {\n warned = true;\n process.stderr.write('[testrelic] recordNavigation: reporter not active, navigation not recorded\\n');\n }\n return;\n }\n\n const annotation: NavigationAnnotation = {\n url,\n navigationType,\n timestamp: new Date().toISOString(),\n };\n\n testInfo.annotations.push({\n type: 'lambdatest-navigation',\n description: JSON.stringify(annotation),\n });\n}\n"]}
|